Roblox · RPG · урок 5

Уровни и классы

Ролевая система: опыт и уровни с полосой на экране и выбор класса — мечник, лучник или маг. Класс даёт свои навыки и оружие и сохраняется после смерти.

Структура

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

ServerStorage
├─ Classes (Folder)
│  ├─ Swordsman (Folder) — навыки мечника
│  ├─ Archer (Folder) — навыки лучника
│  └─ Mage (Folder) — навыки мага
└─ Weapons (Folder) — классовое оружие (следующие уроки)
ReplicatedStorage
└─ ClassEvent (RemoteEvent)
ServerScriptService
├─ PlayerData (Script) — + Lvl, Exp, RequiredExp
└─ ClassServer (Script)
StarterGui
├─ ClassGui (ScreenGui) — ResetOnSpawn выкл.
│  └─ Frame (Frame) — Swordsman, Archer, Mage (TextButton)
│     └─ ClassLocal (LocalScript)
└─ ExpGui (ScreenGui) — ResetOnSpawn выкл.
   └─ ExpFrame (Frame) — ExpBar, ExpText, LevelText
      └─ ExpLocal (LocalScript)

Шаги

  1. Опыт: PlayerData создаёт в игроке Lvl (1), Exp (0) и RequiredExp (100). Набрал порог — уровень +1, лишний опыт остаётся (в методичке пропадал), порог = уровень × 100.
  2. ExpGui → ExpFrame (подложка полосы) → Frame «ExpBar» (Size {0,0},{1,0}), TextLabel «ExpText», TextLabel «LevelText». LocalScript «ExpLocal» в ExpFrame.
  3. ClassGui → Frame с тремя TextButton, названными как классы: Swordsman, Archer, Mage (текст — по-русски). LocalScript «ClassLocal» во Frame.
  4. ServerStorage → Folder «Classes» с папками Swordsman, Archer, Mage — сюда положим навыки. Folder «Weapons» — оружие.
  5. ReplicatedStorage → RemoteEvent «ClassEvent». ServerScriptService → Script «ClassServer».
  6. Класс хранится атрибутом Class у игрока, выбирается один раз. Папка навыков копируется в персонажа при каждом появлении, оружие — в StarterGear (Roblox выдаёт его после каждой смерти).

Скрипты

PlayerData

Где: ServerScriptService · Script
Зачем: Кошелёк, уровень и опыт.

-- игроки
local Players = game:GetService("Players")

-- создаёт контейнер со значением
local function Value(class, name, value, parent)
    -- контейнер
    local v = Instance.new(class)
    -- имя
    v.Name = name
    -- значение
    v.Value = value
    -- на место
    v.Parent = parent
    -- отдаём
    return v
-- конец функции
end

-- игрок зашёл
local function Setup(player)
    -- таблица по Tab
    local leaderstats = Instance.new("Folder")
    -- имя строго такое
    leaderstats.Name = "leaderstats"
    -- в игрока
    leaderstats.Parent = player
    -- кошелёк
    local coins = Value("IntValue", "Монеты", 0, leaderstats)
    -- уровень
    local lvl = Value("IntValue", "Lvl", 1, player)
    -- опыт
    local exp = Value("IntValue", "Exp", 0, player)
    -- сколько опыта до следующего уровня
    local required = Value("IntValue", "RequiredExp", 100, player)
    -- опыт изменился
    exp.Changed:Connect(function()
        -- хватает на уровень (и даже на несколько)
        while exp.Value >= required.Value do
            -- лишний опыт не пропадает
            exp.Value -= required.Value
            -- новый уровень
            lvl.Value += 1
            -- следующий порог выше
            required.Value = lvl.Value * 100
        -- конец цикла
        end
    -- конец функции
    end)
-- конец функции
end

-- новые игроки
Players.PlayerAdded:Connect(Setup)
-- кто зашёл раньше скрипта
for _, player in ipairs(Players:GetPlayers()) do task.spawn(Setup, player) end

ExpLocal

Где: StarterGui → ExpGui → ExpFrame · LocalScript
Зачем: Полоса опыта.

-- игроки
local Players = game:GetService("Players")
-- этот игрок
local player = Players.LocalPlayer
-- опыт
local exp = player:WaitForChild("Exp")
-- уровень
local lvl = player:WaitForChild("Lvl")
-- порог уровня
local required = player:WaitForChild("RequiredExp")
-- ExpFrame — подложка полосы
local frame = script.Parent
-- полоса опыта
local bar = frame:WaitForChild("ExpBar")
-- «40 / 100»
local expText = frame:WaitForChild("ExpText")
-- «Ур. 2»
local lvlText = frame:WaitForChild("LevelText")

-- обновить полосу и надписи
local function Update()
    -- доля до следующего уровня
    local k = math.clamp(exp.Value / required.Value, 0, 1)
    -- плавно меняем длину
    bar:TweenSize(UDim2.fromScale(k, 1), Enum.EasingDirection.Out, Enum.EasingStyle.Quad, 0.3, true)
    -- числа
    expText.Text = exp.Value .. " / " .. required.Value
    -- уровень
    lvlText.Text = "Ур. " .. lvl.Value
-- конец функции
end
-- опыт изменился
exp.Changed:Connect(Update)
-- новый уровень
lvl.Changed:Connect(Update)
-- новый порог
required.Changed:Connect(Update)
-- сразу при входе
Update()

ClassLocal

Где: StarterGui → ClassGui → Frame · LocalScript
Зачем: Окно выбора класса.

-- игроки
local Players = game:GetService("Players")
-- общее хранилище
local RS = game:GetService("ReplicatedStorage")
-- этот игрок
local player = Players.LocalPlayer
-- окно выбора класса
local frame = script.Parent
-- сообщить серверу выбор
local classEvent = RS:WaitForChild("ClassEvent")

-- показать окно, только если класса ещё нет
local function Update()
    -- класс выбран (или загружен) — окно не нужно
    frame.Visible = player:GetAttribute("Class") == nil
-- конец функции
end
-- все объекты окна
for _, button in ipairs(frame:GetChildren()) do
    -- кнопки классов
    if button:IsA("TextButton") then
        -- нажали
        button.MouseButton1Click:Connect(function()
            -- прячем окно
            frame.Visible = false
            -- имя кнопки = имя класса
            classEvent:FireServer(button.Name)
        -- конец функции
        end)
    -- конец проверки
    end
-- конец перебора
end
-- класс появился — окно прячется
player:GetAttributeChangedSignal("Class"):Connect(Update)
-- сразу
Update()

ClassServer

Где: ServerScriptService · Script
Зачем: Класс, навыки и оружие.

-- игроки
local Players = game:GetService("Players")
-- общее хранилище
local RS = game:GetService("ReplicatedStorage")
-- образцы классов и оружия
local SS = game:GetService("ServerStorage")
-- выбор класса
local classEvent = RS:WaitForChild("ClassEvent")
-- папки навыков: Swordsman, Archer, Mage
local classes = SS:WaitForChild("Classes")
-- классовое оружие
local weapons = SS:WaitForChild("Weapons")
-- класс → его оружие
local WEAPON = {Swordsman = "Sword", Archer = "Bow", Mage = "Staff"}

-- папка навыков в персонажа
local function GiveSkills(player)
    -- класс игрока
    local className = player:GetAttribute("Class")
    -- персонаж
    local character = player.Character
    -- нет класса, персонажа или уже выдано
    if not className or not character or character:FindFirstChild(className) then return end
    -- папка класса
    local folder = classes:FindFirstChild(className)
    -- навыки (локальные скрипты) работают в персонаже
    if folder then folder:Clone().Parent = character end
-- конец функции
end

-- классовое оружие
local function GiveWeapon(player)
    -- образец
    local weapon = weapons:FindFirstChild(WEAPON[player:GetAttribute("Class")] or "")
    -- предметы, которые Roblox выдаёт при каждом появлении
    local gear = player:FindFirstChild("StarterGear")
    -- нет оружия или уже выдано
    if not weapon or not gear or gear:FindFirstChild(weapon.Name) then return end
    -- будет у игрока после каждой смерти
    weapon:Clone().Parent = gear
    -- рюкзак — прямо сейчас
    local backpack = player:FindFirstChildOfClass("Backpack")
    -- в рюкзак
    if backpack then weapon:Clone().Parent = backpack end
-- конец функции
end

-- игрок выбрал класс
classEvent.OnServerEvent:Connect(function(player, className)
    -- класс выбирается один раз
    if player:GetAttribute("Class") then return end
    -- нет такого класса
    if typeof(className) ~= "string" or not classes:FindFirstChild(className) then return end
    -- запоминаем — дальше сработает подписка ниже
    player:SetAttribute("Class", className)
-- конец функции
end)

-- игрок зашёл
local function Setup(player)
    -- класс выбран или загружен из сохранения
    player:GetAttributeChangedSignal("Class"):Connect(function()
        -- навыки
        GiveSkills(player)
        -- оружие
        GiveWeapon(player)
    -- конец функции
    end)
    -- каждое появление
    player.CharacterAdded:Connect(function(character)
        -- ждём, пока персонаж соберётся
        character:WaitForChild("Humanoid")
        -- навыки снова (старые удалились вместе с персонажем)
        GiveSkills(player)
    -- конец функции
    end)
    -- класс загрузился раньше, чем мы подписались
    if player:GetAttribute("Class") then
        -- навыки
        GiveSkills(player)
        -- оружие
        GiveWeapon(player)
    -- конец проверки
    end
-- конец функции
end
-- новые игроки
Players.PlayerAdded:Connect(Setup)
-- кто зашёл раньше скрипта
for _, player in ipairs(Players:GetPlayers()) do Setup(player) end

Проверка

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