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

Бег с выносливостью через контроллер

Shift или экранная кнопка тратит энергию; после отдыха можно снова ускоряться.

Иллюстрация: Бег с выносливостью через контроллер
🎯 Что получится

Ускорение тратит энергию, восстанавливается во время отдыха и не сбрасывает другие эффекты.

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

Где разместить: ServerScriptService: StatsController, StaminaServer; StarterPlayerScripts: StaminaClient; RemoteEvent создаётся сервером.

Структура в Explorer
ServerScriptService
  StatsController (ModuleScript из практики 91)
  StaminaServer (Script)
StarterPlayer
  StarterPlayerScripts
    StaminaClient (LocalScript)
ReplicatedStorage
  IgroStaminaSprint (RemoteEvent, создаётся сервером)
  1. Сначала установите StatsController из практики 91.
  2. В ServerScriptService создайте Script StaminaServer, вставьте первый блок.
  3. В StarterPlayer → StarterPlayerScripts создайте LocalScript StaminaClient, вставьте второй блок.
  4. Удалите или отключите SprintServer/SprintClient из практики 92: оба скрипта бега не должны работать одновременно.
  5. Нажмите Play. Удерживайте Shift (или экранную кнопку «Бег» на телефоне), чтобы расходовать энергию.
  6. При нулевой энергии ускорение выключается, а в покое запас восстанавливается.
  7. Проверьте гибель и возрождение, двух игроков и совместную работу с SpeedPad.
Карта файлов в Explorer

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

├── ServerScriptService
│   └── StaminaServer (Script)
└── StarterPlayer
    └── StarterPlayerScripts
        └── StaminaClient (LocalScript)

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

ServerScriptService/StaminaServer (Script)
local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local RunService = game:GetService("RunService")
local Controller = require(script.Parent:WaitForChild("StatsController"))
local remote = ReplicatedStorage:FindFirstChild("IgroStaminaSprint")
if not remote then
    remote = Instance.new("RemoteEvent")
    remote.Name = "IgroStaminaSprint"
    remote.Parent = ReplicatedStorage
end
local MAX = 100
local DRAIN = 22
local RECOVER = 14
local LEASE = 1.2 -- сервер выключит бег, если клиент перестал подтверждать удержание
local states = {}

local function setupPlayer(player)
    local state = {energy = MAX, requested = false, active = false, lastCall = 0, lastKeepalive = 0, exhausted = false}
    states[player] = state
    player:SetAttribute("IgroStamina", MAX)
    player.CharacterAdded:Connect(function()
        state.energy = MAX
        state.requested = false
        state.active = false
        state.exhausted = false
        state.lastKeepalive = 0
        player:SetAttribute("IgroStamina", MAX)
    end)
end
Players.PlayerAdded:Connect(setupPlayer)
for _, player in ipairs(Players:GetPlayers()) do setupPlayer(player) end
Players.PlayerRemoving:Connect(function(player) states[player] = nil end)

remote.OnServerEvent:Connect(function(player, wantsSprint)
    if typeof(wantsSprint) ~= "boolean" then return end
    local state = states[player]
    if not state then return end
    local now = os.clock()
    if wantsSprint and now - state.lastCall < 0.08 then return end
    state.lastCall = now
    state.requested = wantsSprint
    if wantsSprint then
        state.lastKeepalive = now
    else
        -- После полного истощения требуется отпустить и нажать кнопку заново.
        state.exhausted = false
    end
end)

local elapsed = 0
RunService.Heartbeat:Connect(function(dt)
    elapsed += dt
    if elapsed < 0.15 then return end
    local step = math.min(elapsed, 0.5)
    elapsed = 0
    for player, state in pairs(states) do
        local character = player.Character
        local humanoid = character and character:FindFirstChildOfClass("Humanoid")
        local alive = humanoid and humanoid.Health > 0
        local leaseValid = os.clock() - state.lastKeepalive <= LEASE
        local wants = alive and state.requested and leaseValid and not state.exhausted and state.energy > 0
        if wants then
            state.energy = math.max(0, state.energy - DRAIN * step)
        else
            state.energy = math.min(MAX, state.energy + RECOVER * step)
        end
        local shouldRun = wants and state.energy > 0
        if shouldRun and not state.active then
            state.active = Controller.Apply(player, "StaminaSprint", {speed = 1.5})
        elseif not shouldRun and state.active then
            Controller.Remove(player, "StaminaSprint")
            state.active = false
        end
        if state.energy <= 0 then
            state.requested = false
            state.exhausted = true
        elseif not leaseValid then
            state.requested = false
        end
        player:SetAttribute("IgroStamina", math.floor(state.energy + 0.5))
    end
end)
StarterPlayer/StarterPlayerScripts/StaminaClient (LocalScript)
local ContextActionService = game:GetService("ContextActionService")
local UserInputService = game:GetService("UserInputService")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Players = game:GetService("Players")
local remote = ReplicatedStorage:WaitForChild("IgroStaminaSprint")
local player = Players.LocalPlayer
local pressed = false

local function setPressed(value)
    if pressed == value then return end
    pressed = value
    remote:FireServer(value)
end
local function sprintAction(_, inputState)
    if inputState == Enum.UserInputState.Begin then
        setPressed(true)
    elseif inputState == Enum.UserInputState.End or inputState == Enum.UserInputState.Cancel then
        setPressed(false)
    end
    return Enum.ContextActionResult.Pass
end
ContextActionService:BindAction("IgroStaminaSprint", sprintAction, true,
    Enum.KeyCode.LeftShift, Enum.KeyCode.ButtonL3)
ContextActionService:SetTitle("IgroStaminaSprint", "Бег")
ContextActionService:SetPosition("IgroStaminaSprint", UDim2.new(1, -100, 1, -190))
player.CharacterAdded:Connect(function()
    pressed = false
    remote:FireServer(false)
end)
UserInputService.WindowFocusReleased:Connect(function()
    setPressed(false)
end)

-- Пока кнопка удерживается, обновляем запрос. Без обновлений сервер
-- отключает ускорение через 1,2 секунды: бег не застрянет из-за потери события.
task.spawn(function()
    while true do
        task.wait(0.35)
        if pressed then remote:FireServer(true) end
    end
end)

-- Простой индикатор энергии: создаётся только для локального игрока.
local gui = Instance.new("ScreenGui")
gui.Name = "IgroStaminaHud"
gui.ResetOnSpawn = false
gui.Parent = player:WaitForChild("PlayerGui")
local background = Instance.new("Frame")
background.Name = "Meter"
background.AnchorPoint = Vector2.new(0.5, 1)
background.Position = UDim2.new(0.5, 0, 1, -24)
background.Size = UDim2.new(0, 240, 0, 24)
background.BackgroundColor3 = Color3.fromRGB(38, 45, 62)
background.BorderSizePixel = 0
background.Parent = gui
local fill = Instance.new("Frame")
fill.Name = "Fill"
fill.Size = UDim2.fromScale(1, 1)
fill.BackgroundColor3 = Color3.fromRGB(80, 210, 150)
fill.BorderSizePixel = 0
fill.Parent = background
local label = Instance.new("TextLabel")
label.Name = "Value"
label.BackgroundTransparency = 1
label.Size = UDim2.fromScale(1, 1)
label.TextColor3 = Color3.new(1, 1, 1)
label.TextScaled = true
label.Font = Enum.Font.GothamBold
label.Parent = background
local function updateHud()
    local energy = math.clamp(tonumber(player:GetAttribute("IgroStamina")) or 100, 0, 100)
    fill.Size = UDim2.fromScale(energy / 100, 1)
    label.Text = string.format("Энергия: %d%%", energy)
end
player:GetAttributeChangedSignal("IgroStamina"):Connect(updateHud)
updateHud()

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

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

Ваш прогресс

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

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

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

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

Поменяйте расход энергии с 22 на 15 единиц в секунду.

2. Улучшите

Добавьте индикатор энергии на экран, используя атрибут IgroStamina.

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

Создайте гонку с зонами отдыха и ограниченным запасом сил.

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

Почему энергия расходуется в серверном Script?

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

Отключите предыдущий SprintServer/SprintClient из практики 92. Если бег не включается, проверьте, что StatsController уже инициализировал персонажа. При нулевой энергии нужно отпустить и снова нажать кнопку после восстановления.