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

Монеты и улучшения

Монеты на трассе пополняют кошелёк игрока. За монеты в меню улучшений покупаются ускорение, высокий прыжок и щит от препятствий на 10 секунд.

Структура

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

Workspace
└─ Coins (Folder)
   └─ Coin (Part) — монета — одна деталь, Anchored, CanCollide выкл.
ReplicatedStorage
└─ Events (Folder)
   └─ BoostEvent (RemoteEvent)
ServerScriptService
├─ Killbrick (Script) — дополняем
├─ CoinsServer (Script)
└─ BoostServer (Script)
StarterGui
├─ CoinsGui (ScreenGui)
│  └─ CoinsValue (TextLabel)
│     └─ CoinsLocal (LocalScript)
└─ BoostGui (ScreenGui)
   └─ Frame (Frame)
      ├─ Speed, Jump, Shield (ImageButton) — имя кнопки = название улучшения
      ├─ SpeedPrice, JumpPrice, ShieldPrice (TextLabel) — цены
      └─ BoostLocal (LocalScript)

Шаги

  1. Workspace → «+» → Folder «Coins». Сделай монету — одну деталь (цилиндр, Anchored — вкл., CanCollide — выкл.) и положи в Coins. Скрипт работает со всей папкой, поэтому сразу расставь копии по трассе.
  2. ServerScriptService → «+» → Script «CoinsServer». Он добавляет в leaderstats кошелёк Coins; подобранная монета исчезает и через 10 секунд появляется снова — в следующем забеге её можно взять ещё раз.
  3. StarterGui → «+» → ScreenGui «CoinsGui» → TextLabel «CoinsValue» → «+» → LocalScript «CoinsLocal».
  4. ReplicatedStorage → Events → «+» → RemoteEvent «BoostEvent».
  5. StarterGui → «+» → ScreenGui «BoostGui» → Frame. В Frame три ImageButton с именами Speed, Jump, Shield (имена важны — сервер узнаёт по ним улучшение) и TextLabel с ценами: 50, 100, 150.
  6. В Frame «+» → LocalScript «BoostLocal», в ServerScriptService «+» → Script «BoostServer».
  7. Обнови Killbrick: если на персонаже метка щита ShieldOn, препятствие не убивает.
  8. Как работает: кнопка отправляет серверу своё имя. Сервер проверяет цену и кошелёк, списывает монеты, включает улучшение только этому игроку и через 10 секунд выключает. Пока улучшение действует, второе такое же не купить.
  9. Высокий прыжок не работает — StarterPlayer → CharacterUseJumpPower: галочки быть не должно (скрипт меняет JumpHeight).

Скрипты

CoinsServer

Где: ServerScriptService · Script
Зачем: Кошелёк каждого игрока и подбор монет.

-- папка со всеми монетами
local coinsFolder = workspace:WaitForChild("Coins")
-- через сколько секунд монета возвращается
local RESPAWN = 10

-- игрок зашёл
game.Players.PlayerAdded:Connect(function(player)
    -- его кошелёк
    local coins = Instance.new("IntValue")
    -- имя
    coins.Name = "Coins"
    -- в таблицу очков (её создаёт ScoreServer)
    coins.Parent = player:WaitForChild("leaderstats")
-- конец обработчика
end)

-- каждая монета
for _, coin in pairs(coinsFolder:GetChildren()) do
    -- это деталь
    if coin:IsA("BasePart") then
        -- монеты что-то коснулось
        coin.Touched:Connect(function(otherPart)
            -- её уже подобрали в этот же момент
            if not coin.CanTouch then return end
            -- чей это персонаж
            local player = game.Players:GetPlayerFromCharacter(otherPart.Parent)
            -- не игрок
            if not player then return end
            -- его кошелёк
            local coins = player.leaderstats:FindFirstChild("Coins")
            -- ещё не создан
            if not coins then return end
            -- больше не подбирается
            coin.CanTouch = false
            -- и не видна
            coin.Transparency = 1
            -- плюс монета
            coins.Value += 1
            -- ждём
            task.wait(RESPAWN)
            -- монета снова видна
            coin.Transparency = 0
            -- и снова подбирается
            coin.CanTouch = true
        -- конец обработчика
        end)
    -- конец проверки
    end
-- конец перебора
end

CoinsLocal

Где: StarterGui → CoinsGui → CoinsValue · LocalScript
Зачем: Показывает, сколько монет в кошельке.

-- игрок на этом компьютере
local player = game.Players.LocalPlayer
-- его кошелёк
local coins = player:WaitForChild("leaderstats"):WaitForChild("Coins")
-- надпись с монетами
local text = script.Parent

-- обновить надпись
local function Update()
    -- сколько монет
    text.Text = coins.Value .. " $"
-- конец функции
end

-- показываем сразу
Update()
-- монет стало больше или меньше — обновляем
coins.Changed:Connect(Update)

BoostLocal

Где: StarterGui → BoostGui → Frame · LocalScript
Зачем: Отправляет серверу название нажатого улучшения.

-- рамка с кнопками
local frame = script.Parent
-- событие «купить улучшение»
local BoostEvent = game.ReplicatedStorage:WaitForChild("Events"):WaitForChild("BoostEvent")

-- всё внутри рамки
for _, button in pairs(frame:GetChildren()) do
    -- это кнопка
    if button:IsA("ImageButton") then
        -- на неё нажали
        button.MouseButton1Click:Connect(function()
            -- просим сервер продать улучшение с именем кнопки
            BoostEvent:FireServer(button.Name)
        -- конец обработчика
        end)
    -- конец проверки
    end
-- конец перебора
end

BoostServer

Где: ServerScriptService · Script
Зачем: Продаёт улучшения и включает их на 10 секунд.

-- просьбы купить улучшение
local BoostEvent = game.ReplicatedStorage:WaitForChild("Events"):WaitForChild("BoostEvent")
-- цена каждого улучшения; другие названия не продаём
local PRICES = {Speed = 50, Jump = 100, Shield = 150}
-- сколько секунд действует улучшение
local DURATION = 10

-- включить (on = true) или выключить улучшение
local function Apply(character, boost, on)
    -- персонаж
    local humanoid = character:FindFirstChildOfClass("Humanoid")
    -- персонажа уже нет
    if not humanoid then return end
    -- скорость
    if boost == "Speed" then
        -- 30 с улучшением, 16 — обычная
        humanoid.WalkSpeed = on and 30 or 16
    -- прыжок
    elseif boost == "Jump" then
        -- 12 с улучшением, 7.2 — обычная высота
        humanoid.JumpHeight = on and 12 or 7.2
    -- щиту ничего менять не нужно — его проверяет Killbrick
    end
    -- метка на персонаже: SpeedOn, JumpOn, ShieldOn
    character:SetAttribute(boost .. "On", on or nil)
-- конец функции
end

-- игрок нажал кнопку
BoostEvent.OnServerEvent:Connect(function(player, boost)
    -- цена этого улучшения
    local price = PRICES[boost]
    -- такого улучшения нет
    if not price then return end
    -- его персонаж
    local character = player.Character
    -- персонажа нет
    if not character then return end
    -- это улучшение уже действует
    if character:GetAttribute(boost .. "On") then return end
    -- его кошелёк
    local coins = player.leaderstats.Coins
    -- не хватает монет
    if coins.Value < price then return end
    -- списываем монеты
    coins.Value -= price
    -- включаем
    Apply(character, boost, true)
    -- ждём
    task.wait(DURATION)
    -- выключаем
    Apply(character, boost, false)
-- конец обработчика
end)

Killbrick

Где: ServerScriptService · Script
Зачем: Как раньше, но персонажа со щитом не трогает.

-- папка со всеми опасными препятствиями
local killFolder = workspace:WaitForChild("Kill")

-- все детали, в том числе внутри моделей
for _, part in pairs(killFolder:GetDescendants()) do
    -- это деталь, а не папка или модель
    if part:IsA("BasePart") then
        -- детали что-то коснулось
        part.Touched:Connect(function(otherPart)
            -- модель, в которой коснувшаяся деталь
            local character = otherPart.Parent
            -- это персонаж?
            local humanoid = character:FindFirstChildOfClass("Humanoid")
            -- нет — пропускаем
            if not humanoid then return end
            -- куплен щит — препятствие не убивает
            if character:GetAttribute("ShieldOn") then return end
            -- персонаж погибает
            humanoid.Health = 0
        -- конец обработчика
        end)
    -- конец проверки
    end
-- конец перебора
end

Проверка

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