← Все практические скрипты
Игровые системы · Продвинутый · 50 мин

Единый контроллер: скорость, прыжок и размер

Шесть площадок меняют характеристики персонажа без взаимного сброса бонусов.

Иллюстрация: Единый контроллер: скорость, прыжок и размер
🎯 Что получится

Площадки дают временные бонусы: ускорение, замедление, высокий прыжок, уменьшение и увеличение. Эффекты складываются и корректно исчезают.

1. Подготовьте объект

Где разместить: ModuleScript и Script в ServerScriptService; площадки в Workspace.

Структура в Explorer
ServerScriptService
  StatsController (ModuleScript)
  StatsPadsServer (Script)
Workspace
  StatsPads (Folder)
    SpeedPad (Part)
    SlowPad (Part)
    JumpPad (Part)
    SmallPad (Part)
    BigPad (Part)
    ResetPad (Part)
  1. Создайте в ServerScriptService ModuleScript с точным именем StatsController. Замените его содержимое первым блоком кода.
  2. Рядом создайте обычный Script StatsPadsServer. Вставьте второй блок.
  3. В Workspace создайте Folder StatsPads и шесть Anchored Part с именами из схемы. Включите CanTouch.
  4. В Game Settings → Avatar выберите R15 для изменения размера. На R6 будут работать скорость и прыжок, но не масштабирование.
  5. Запустите Play. Наступите на SpeedPad, затем JumpPad: оба эффекта действуют одновременно. Через 10 секунд они исчезнут. ResetPad отменяет их сразу.
  6. В режиме Test → Start проверьте двух игроков: характеристики должны быть независимыми.
  7. Не добавляйте старые скрипты, напрямую меняющие WalkSpeed, JumpPower или ScaleTo, в ту же тестовую карту.
Карта файлов в Explorer

Это пути для скриптов из блоков кода ниже. Остальные объекты создайте по инструкции.

└── ServerScriptService
    ├── StatsController (ModuleScript)
    └── StatsPadsServer (Script)

2. Вставьте код

ServerScriptService/StatsController (ModuleScript)
-- ModuleScript: ServerScriptService/StatsController
-- Одна серверная система для скорости, прыжка и масштаба R15.
local Players = game:GetService("Players")
local Controller = {}
local states = {}

local function recalculate(player)
    local state = states[player]
    if not state or player.Character ~= state.character then return end
    local humanoid = state.character:FindFirstChildOfClass("Humanoid")
    if not humanoid or humanoid.Health <= 0 then return end

    local speed, jump, size = 1, 1, 1
    for _, effect in pairs(state.effects) do
        speed *= effect.speed
        jump *= effect.jump
        size *= effect.size
    end
    humanoid.UseJumpPower = true
    humanoid.WalkSpeed = math.clamp(state.baseSpeed * speed, 8, 60)
    humanoid.JumpPower = math.clamp(state.baseJump * jump, 25, 110)

    if humanoid.RigType == Enum.HumanoidRigType.R15 then
        local targetScale = math.clamp(state.baseScale * size, 0.65, 1.65)
        if math.abs(state.character:GetScale() - targetScale) > 0.01 then
            state.character:ScaleTo(targetScale)
        end
    end
end

function Controller.Apply(player, effectName, values, duration)
    local state = states[player]
    if not state or type(effectName) ~= "string" or type(values) ~= "table" then
        return false
    end
    -- Не списываем монеты за эффект, если персонаж ещё не готов после возрождения.
    if state.character ~= player.Character then return false end
    local humanoid = state.character:FindFirstChildOfClass("Humanoid")
    if not humanoid or humanoid.Health <= 0 then return false end
    local token = {}
    state.effects[effectName] = {
        speed = math.clamp(tonumber(values.speed) or 1, 0.5, 3),
        jump = math.clamp(tonumber(values.jump) or 1, 0.5, 3),
        size = math.clamp(tonumber(values.size) or 1, 0.5, 2),
        token = token,
    }
    recalculate(player)
    if type(duration) == "number" and duration > 0 then
        task.delay(math.min(duration, 300), function()
            if states[player] == state then
                local current = state.effects[effectName]
                if current and current.token == token then
                    state.effects[effectName] = nil
                    recalculate(player)
                end
            end
        end)
    end
    return true
end

function Controller.Remove(player, effectName)
    local state = states[player]
    if not state then return end
    state.effects[effectName] = nil
    recalculate(player)
end

function Controller.Clear(player)
    local state = states[player]
    if not state then return end
    state.effects = {}
    recalculate(player)
end

local function setupCharacter(player, character)
    local humanoid = character:WaitForChild("Humanoid", 10)
    if not humanoid or player.Character ~= character then return end
    -- Короткая пауза: Roblox успевает создать основные части аватара.
    task.wait(0.5)
    if player.Character ~= character then return end
    states[player] = {
        character = character,
        effects = {},
        baseSpeed = humanoid.WalkSpeed,
        baseJump = humanoid.JumpPower,
        baseScale = character:GetScale(),
    }
    recalculate(player)
end

local function setupPlayer(player)
    player.CharacterAdded:Connect(function(character)
        task.spawn(setupCharacter, player, character)
    end)
    if player.Character then task.spawn(setupCharacter, player, player.Character) end
end
Players.PlayerAdded:Connect(setupPlayer)
Players.PlayerRemoving:Connect(function(player) states[player] = nil end)
for _, player in ipairs(Players:GetPlayers()) do setupPlayer(player) end

return Controller
ServerScriptService/StatsPadsServer (Script)
-- Script: ServerScriptService/StatsPadsServer
local Players = game:GetService("Players")
local Controller = require(script.Parent:WaitForChild("StatsController"))
local folder = workspace:WaitForChild("StatsPads")

local modes = {
    SpeedPad = {speed = 1.7},
    SlowPad = {speed = 0.65},
    JumpPad = {jump = 1.6},
    SmallPad = {size = 0.7},
    BigPad = {size = 1.5},
}
local touched = {}

for _, pad in ipairs(folder:GetChildren()) do
    if pad:IsA("BasePart") then
        pad.Touched:Connect(function(hit)
            local character = hit:FindFirstAncestorOfClass("Model")
            local player = character and Players:GetPlayerFromCharacter(character)
            local humanoid = character and character:FindFirstChildOfClass("Humanoid")
            if not player or not humanoid or humanoid.Health <= 0 then return end
            touched[player] = touched[player] or {}
            local now = os.clock()
            if now - (touched[player][pad] or -100) < 1.5 then return end
            touched[player][pad] = now
            if pad.Name == "ResetPad" then
                Controller.Clear(player)
            elseif modes[pad.Name] then
                Controller.Apply(player, pad.Name, modes[pad.Name], 10)
            end
        end)
    end
end
Players.PlayerRemoving:Connect(function(player) touched[player] = nil end)

3. Проверьте в игре

Нажмите Play в Roblox Studio и проверьте действие механики. Если механика не работает, откройте окно Output и проверьте тип скрипта, его расположение и названия объектов.

Ваш прогресс

Когда проверите механику в Roblox Studio, отметьте практику как выполненную. Отметка хранится только в этом браузере, не в личном кабинете.

Если объединяете несколько скриптов

Добавляйте механики по одной и проверяйте окно Output. Не создавайте несколько одинаковых систем монет или интерфейсов; скрипты, меняющие WalkSpeed, прыжок или освещение, могут влиять друг на друга.

⭐ Три шага к собственной игре
1. Повторите и измените

Измените длительность эффекта с 10 до 15 секунд.

2. Улучшите

Добавьте площадку SuperJumpPad с jump = 2.

3. Придумайте своё

Сделайте комнату испытаний с пятью разными усилениями и кнопкой сброса.

4. Проверьте себя

Почему несколько бонусов не сбрасывают друг друга при окончании?

🛠 Если не работает

Названия объектов должны совпадать с Explorer. ModuleScript возвращает Controller. Если размер не меняется, проверьте R15. Сначала проверьте Output. Не используйте одновременно старые скрипты, напрямую меняющие те же свойства. Для пользовательских аватаров масштабирование нужно проверять отдельно.