Roblox · Песочница · урок 7

Голод и выносливость

Сытость падает со временем — на нуле уходит здоровье. Яблоки растут на яблоне. Бег тратит выносливость.

Структура

Синим — что добавляем на этом уроке.

ServerStorage
└─ Food (Folder)
   └─ Apple (Tool) — Handle → Eat (Sound); FoodValue (IntValue 25); FoodScript (Animation)
Workspace
└─ AppleTree (Model) — Trunk, Leaves, Apples (Model)
   └─ PromptPart (Part) — ProximityPrompt → TakingFood
ServerScriptService
└─ HungerServer (Script)
StarterPlayer → StarterCharacterScripts
└─ Run (LocalScript) — заменён: бег + выносливость
StarterGui
├─ HungerGui (ScreenGui) — Background → Bar; HungerLocal
└─ StaminaGui (ScreenGui) — Background → Bar; Enabled выкл.

Шаги

  1. HungerServer: Hunger = 100 у каждого игрока, каждые 4 секунды −2. На нуле — −5 здоровья за раз (в методичке персонаж сразу умирал). После возрождения снова 100.
  2. В методичке сытость сбрасывалась при добавлении любого объекта в игрока, цикл не останавливался после выхода. Исправлено.
  3. Яблоко: Tool «Apple» с IntValue «FoodValue» = 25, звук «Eat», Script «FoodScript». Сытость прибавляет сам сервер — в методичке число присылал клиент, и его можно было подменить.
  4. Яблоня: модель с Trunk, Leaves и моделью «Apples» (красные шарики-декор). PromptPart с ProximityPrompt «Сорвать» и скриптом «TakingFood»: яблоко в рюкзак, декор пропадает на 10 секунд.
  5. HungerGui: Frame «Background» и «Bar» внутри — полоса растёт снизу вверх (AnchorPoint 0, 1, Position {0,0},{1,0}). LocalScript «HungerLocal».
  6. Выносливость: Run заменяется версией с запасом 150 — бег тратит 30 в секунду, отдых возвращает 20. StaminaGui появляется, пока запас не полный.

Скрипты

HungerServer

Где: ServerScriptService · Script
Зачем: Сытость.

-- игроки
local Players = game:GetService("Players")
-- раз в сколько секунд голод растёт
local FREQUENCY = 4
-- на сколько падает сытость
local DECREASE = 2
-- урон от голода, когда сытость 0
local STARVE_DAMAGE = 5

-- игрок зашёл
local function Setup(player)
    -- сытость
    local hunger = Instance.new("IntValue")
    -- имя — его читают интерфейс и еда
    hunger.Name = "Hunger"
    -- сыт
    hunger.Value = 100
    -- в игрока
    hunger.Parent = player
    -- после возрождения — снова сыт
    player.CharacterAdded:Connect(function() hunger.Value = 100 end)
    -- пока игрок в игре
    while player.Parent do
        -- ждём
        task.wait(FREQUENCY)
        -- его Humanoid
        local humanoid = player.Character and player.Character:FindFirstChildOfClass("Humanoid")
        -- жив
        if humanoid and humanoid.Health > 0 then
            -- ещё не голоден
            if hunger.Value > 0 then
                -- сытость падает
                hunger.Value = math.max(hunger.Value - DECREASE, 0)
            -- голоден
            else
                -- здоровье понемногу уходит
                humanoid:TakeDamage(STARVE_DAMAGE)
            -- конец проверки
            end
        -- конец проверки
        end
    -- конец цикла
    end
-- конец функции
end
-- новые игроки
Players.PlayerAdded:Connect(Setup)
-- кто зашёл раньше скрипта
for _, player in ipairs(Players:GetPlayers()) do task.spawn(Setup, player) end

HungerLocal

Где: StarterGui → HungerGui · LocalScript
Зачем: Полоса сытости.

-- игроки
local Players = game:GetService("Players")
-- сытость
local hunger = Players.LocalPlayer:WaitForChild("Hunger")
-- полоса (растёт снизу вверх)
local bar = script.Parent:WaitForChild("Background"):WaitForChild("Bar")

-- обновить полосу
local function Update()
    -- высота = сытость
    bar.Size = UDim2.fromScale(1, math.clamp(hunger.Value / 100, 0, 1))
-- конец функции
end
-- изменилась
hunger.Changed:Connect(Update)
-- сразу
Update()

FoodScript

Где: ServerStorage → Food → Apple · Script
Зачем: Съесть яблоко.

-- яблоко
local tool = script.Parent
-- сколько сытости даёт
local foodValue = tool:WaitForChild("FoodValue")
-- хруст
local eatSound = tool:WaitForChild("Handle"):WaitForChild("Eat")
-- анимация еды
local anim = script:WaitForChild("Animation")
-- уже съедено
local eaten = false

-- клик — съесть
tool.Activated:Connect(function()
    -- кто ест
    local character = tool.Parent
    -- игрок
    local player = game.Players:GetPlayerFromCharacter(character)
    -- Humanoid
    local humanoid = character:FindFirstChildOfClass("Humanoid")
    -- уже, не игрок или погиб
    if eaten or not player or not humanoid or humanoid.Health <= 0 then return end
    -- съедено
    eaten = true
    -- анимация
    if anim.AnimationId ~= "" then humanoid:FindFirstChildOfClass("Animator"):LoadAnimation(anim):Play() end
    -- хруст
    eatSound:Play()
    -- жуём
    task.wait(1)
    -- сытость
    local hunger = player:FindFirstChild("Hunger")
    -- сытость сервер считает сам, не выше 100
    if hunger then hunger.Value = math.min(hunger.Value + foodValue.Value, 100) end
    -- яблока больше нет
    tool:Destroy()
-- конец функции
end)

TakingFood

Где: Workspace → AppleTree → PromptPart → ProximityPrompt · Script
Зачем: Сорвать яблоко.

-- образец яблока
local SS = game:GetService("ServerStorage")
-- ProximityPrompt «Сорвать»
local prompt = script.Parent
-- яблоня: Prompt → PromptPart → AppleTree
local tree = prompt.Parent.Parent
-- яблоко-инструмент
local apple = SS:WaitForChild("Food"):WaitForChild("Apple")
-- через сколько секунд вырастут снова
local REGROW = 10

-- сорвали
prompt.Triggered:Connect(function(player)
    -- яблоки на дереве (декор)
    local apples = tree:FindFirstChild("Apples")
    -- уже сорваны
    if not apples then return end
    -- яблоко в рюкзак
    apple:Clone().Parent = player.Backpack
    -- яблоки пропали с дерева
    apples.Parent = nil
    -- сорвать нечего
    prompt.Enabled = false
    -- растут
    task.wait(REGROW)
    -- снова на дереве
    apples.Parent = tree
    -- можно рвать
    prompt.Enabled = true
-- конец функции
end)

Run

Где: StarterPlayer → StarterCharacterScripts · LocalScript
Зачем: Бег с выносливостью.

-- клавиатура
local UIS = game:GetService("UserInputService")
-- каждый кадр
local RunService = game:GetService("RunService")
-- игроки
local Players = game:GetService("Players")
-- персонаж
local character = script.Parent
-- Humanoid
local humanoid = character:WaitForChild("Humanoid")
-- полоса выносливости
local gui = Players.LocalPlayer:WaitForChild("PlayerGui"):WaitForChild("StaminaGui")
-- полоса
local bar = gui:WaitForChild("Background"):WaitForChild("Bar")
-- запас
local MAX = 150
-- тратится в секунду
local DRAIN = 30
-- восстанавливается в секунду
local REGEN = 20
-- бег
local RUN_SPEED = 25
-- шаг
local WALK_SPEED = 16
-- сейчас
local stamina = MAX
-- Shift зажат
local shift = false
-- бежим
local running = false

-- нажали
UIS.InputBegan:Connect(function(input, processed)
    -- Shift
    if not processed and input.KeyCode == Enum.KeyCode.LeftShift then shift = true end
-- конец функции
end)
-- отпустили
UIS.InputEnded:Connect(function(input)
    -- Shift
    if input.KeyCode == Enum.KeyCode.LeftShift then shift = false end
-- конец функции
end)

-- каждый кадр
RunService.Heartbeat:Connect(function(dt)
    -- погиб или сидит — не бежим
    if humanoid.Health <= 0 or character:GetAttribute("Crouching") then running = false return end
    -- можно бежать
    local want = shift and humanoid.MoveDirection.Magnitude > 0 and stamina > 0
    -- бежим
    if want then
        -- тратим
        stamina = math.max(stamina - DRAIN * dt, 0)
    -- Shift отпущен
    elseif not shift then
        -- отдыхаем
        stamina = math.min(stamina + REGEN * dt, MAX)
    -- конец проверки
    end
    -- состояние сменилось
    if want ~= running then
        -- запоминаем
        running = want
        -- скорость меняем только в этот момент
        humanoid.WalkSpeed = want and RUN_SPEED or WALK_SPEED
    -- конец проверки
    end
    -- полная — полоску прячем
    gui.Enabled = stamina < MAX
    -- длина полосы
    bar.Size = UDim2.fromScale(stamina / MAX, 1)
-- конец функции
end)

Проверка

Запусти Play (или Run) и убедись: