Roblox · 2D Раннер · урок 3

Метры и рекорд

Сервер считает, сколько метров пробежал игрок от старта, и запоминает рекорд. Метры и рекорд видны на экране и в таблице игроков по Tab — у каждого свои.

Структура

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

ReplicatedStorage
└─ Events (Folder) — все события
   └─ GameStateEvent (RemoteEvent)
ServerScriptService
└─ ScoreServer (Script)
StarterPlayer
└─ StarterPlayerScripts
   └─ ControlScript (LocalScript) — дополняем
StarterGui
└─ ScoreGui (ScreenGui)
   ├─ Score (TextLabel)
   ├─ Highscore (TextLabel)
   └─ ScoreLocal (LocalScript)

Шаги

  1. ReplicatedStorage → «+» → Folder «Events», внутри RemoteEvent «GameStateEvent». В отдельной папке события проще искать, когда их станет много.
  2. Обнови ControlScript: на старте он сообщает серверу true, после смерти — false. Новый текст — в «Скриптах».
  3. ServerScriptService → «+» → Script «ScoreServer».
  4. Как работает: при входе игрока ScoreServer создаёт ему leaderstats с Score и Highscore. На старте запоминает, где стоял персонаж, и 10 раз в секунду пишет в Score, сколько метров пройдено. Больше рекорда — рекорд обновляется.
  5. Метры считаются по настоящему расстоянию: если персонаж упёрся в стену, метры не растут.
  6. StarterGui → «+» → ScreenGui «ScoreGui», внутри два TextLabel «Score» и «Highscore». Расположи их и оформи.
  7. В ScoreGui «+» → LocalScript «ScoreLocal».

Скрипты

ControlScript

Где: StarterPlayer → StarterPlayerScripts · LocalScript
Зачем: Как в прошлом уроке, плюс сообщения серверу о старте и конце забега.

-- игрок на этом компьютере
local player = game.Players.LocalPlayer
-- события каждого кадра
local RunService = game:GetService("RunService")
-- клавиатура и экран
local UIS = game:GetService("UserInputService")
-- стандартные скрипты управления
local PlayerModule = require(player:WaitForChild("PlayerScripts"):WaitForChild("PlayerModule"))
-- событие «забег начался / закончился» для сервера
local GameStateEvent = game.ReplicatedStorage:WaitForChild("Events"):WaitForChild("GameStateEvent")
-- персонаж, которым управляем
local humanoid = nil
-- идёт ли забег
local gameStarted = false

-- выключаем WASD и стандартный прыжок — управляем сами
PlayerModule:GetControls():Disable()

-- забег окончен
local function StopRun()
    -- больше не бежим
    gameStarted = false
    -- сообщаем серверу: стоп
    GameStateEvent:FireServer(false)
    -- перестаём толкать персонажа вперёд
    RunService:UnbindFromRenderStep("Move")
-- конец функции
end

-- новый персонаж
local function Setup(character)
    -- запоминаем его
    humanoid = character:WaitForChild("Humanoid")
    -- стоит на месте, пока не нажали старт
    character:WaitForChild("HumanoidRootPart").Anchored = true
    -- погиб — забег окончен
    humanoid.Died:Connect(StopRun)
-- конец функции
end

-- каждый новый персонаж
player.CharacterAdded:Connect(Setup)
-- персонаж уже появился до запуска скрипта
if player.Character then Setup(player.Character) end

-- нажали клавишу или тапнули — подписка одна на всю игру
UIS.InputBegan:Connect(function(input, gp)
    -- нажатие ушло в чат или на кнопку интерфейса
    if gp then return end
    -- пробел или тап по экрану телефона
    local pressed = input.KeyCode == Enum.KeyCode.Space or input.UserInputType == Enum.UserInputType.Touch
    -- другая клавиша
    if not pressed then return end
    -- персонажа нет или он погиб
    if not humanoid or humanoid.Health <= 0 then return end
    -- уже бежим
    if gameStarted then
        -- прыжок
        humanoid.Jump = true
    -- ещё стоим
    else
        -- старт
        gameStarted = true
        -- сообщаем серверу: побежали
        GameStateEvent:FireServer(true)
        -- отпускаем персонажа
        humanoid.Parent.HumanoidRootPart.Anchored = false
        -- каждый кадр
        RunService:BindToRenderStep("Move", Enum.RenderPriority.Character.Value, function()
            -- бежим по оси X в сторону минуса
            humanoid:Move(Vector3.new(-1, 0, 0), false)
        -- конец функции кадра
        end)
    -- конец проверки
    end
-- конец обработчика
end)

ScoreServer

Где: ServerScriptService · Script
Зачем: Считает метры каждого игрока и обновляет рекорд.

-- старт и стоп от игроков
local GameStateEvent = game.ReplicatedStorage:WaitForChild("Events"):WaitForChild("GameStateEvent")
-- текущий забег каждого игрока
local runs = {}

-- игрок зашёл
game.Players.PlayerAdded:Connect(function(player)
    -- его таблица очков
    local leaderstats = Instance.new("Folder")
    -- имя, которое понимает Roblox — видно по Tab
    leaderstats.Name = "leaderstats"
    -- в игрока
    leaderstats.Parent = player
    -- метры в этом забеге
    local score = Instance.new("IntValue")
    -- имя
    score.Name = "Score"
    -- в таблицу
    score.Parent = leaderstats
    -- лучший результат
    local highscore = Instance.new("IntValue")
    -- имя
    highscore.Name = "Highscore"
    -- в таблицу
    highscore.Parent = leaderstats
-- конец обработчика
end)

-- игрок вышел
game.Players.PlayerRemoving:Connect(function(player)
    -- его забег окончен
    runs[player] = nil
-- конец обработчика
end)

-- игрок прислал старт (true) или стоп (false)
GameStateEvent.OnServerEvent:Connect(function(player, started)
    -- стоп
    if not started then
        -- забег окончен — цикл ниже остановится
        runs[player] = nil
        -- больше ничего не делаем
        return
    -- конец проверки
    end
    -- уже бежит — второй подсчёт не нужен
    if runs[player] then return end
    -- центр персонажа
    local hrp = player.Character and player.Character:FindFirstChild("HumanoidRootPart")
    -- персонажа нет
    if not hrp then return end
    -- его метры
    local score = player.leaderstats.Score
    -- его рекорд
    local highscore = player.leaderstats.Highscore
    -- метка этого забега
    local run = {}
    -- запоминаем
    runs[player] = run
    -- откуда стартовали
    local startX = hrp.Position.X
    -- новый забег — с нуля
    score.Value = 0
    -- пока идёт именно этот забег
    while runs[player] == run do
        -- сколько пробежали от старта (бежим в сторону минуса X)
        score.Value = math.max(0, math.floor(startX - hrp.Position.X))
        -- дальше рекорда
        if score.Value > highscore.Value then
            -- новый рекорд
            highscore.Value = score.Value
        -- конец проверки
        end
        -- 10 раз в секунду
        task.wait(0.1)
    -- конец цикла
    end
-- конец обработчика
end)

ScoreLocal

Где: StarterGui → ScoreGui · LocalScript
Зачем: Показывает метры и рекорд на экране.

-- игрок на этом компьютере
local player = game.Players.LocalPlayer
-- его таблица (создаёт ScoreServer)
local leaderstats = player:WaitForChild("leaderstats")
-- метры
local score = leaderstats:WaitForChild("Score")
-- рекорд
local highscore = leaderstats:WaitForChild("Highscore")
-- надпись с метрами
local scoreText = script.Parent:WaitForChild("Score")
-- надпись с рекордом
local highscoreText = script.Parent:WaitForChild("Highscore")

-- обновить обе надписи
local function Update()
    -- метры
    scoreText.Text = score.Value .. " м"
    -- рекорд
    highscoreText.Text = "Рекорд: " .. highscore.Value .. " м"
-- конец функции
end

-- показываем сразу, не дожидаясь первого изменения
Update()
-- метры поменялись — обновляем
score.Changed:Connect(Update)
-- рекорд поменялся — обновляем
highscore.Changed:Connect(Update)

Проверка

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