Roblox · RPG · урок 12

Главное меню, сохранение и публикация

Финал: главное меню на фоне карты, сохранение монет, уровня, опыта и класса, звуки локаций — и публикация.

Структура

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

Workspace
├─ CameraPart (Part) — невидимый, ракурс меню
└─ MapFolder → Sound (Part) — Sound: Playing, Looped
ServerScriptService
└─ PlayerData (Script) — + сохранение
StarterGui
└─ MainMenuGui (ScreenGui) — ResetOnSpawn выкл., DisplayOrder 10
   ├─ Background (ImageLabel) — узор фона
   ├─ BackFrame (Frame) — RPG (название), Logo, PlayButton
   └─ MainMenu (LocalScript)

Шаги

  1. MainMenuGui: ImageLabel «Background» с узором (Rotation — наклон), Frame «BackFrame» с TextLabel «RPG», ImageLabel «Logo» и TextButton «PlayButton». Всё через Scale. ResetOnSpawn выкл. — после смерти меню не вернётся. DisplayOrder 10 — поверх окна классов.
  2. Ракурс: встань камерой как надо, скопируй Workspace → Camera → CFrame (Position и Orientation) в невидимый Part «CameraPart».
  3. LocalScript «MainMenu»: пока меню, камера смотрит с CameraPart, персонаж не ходит.
  4. Сохранение: PlayerData сохраняет монеты, уровень, опыт и класс. После загрузки класс сразу выдаёт навыки и оружие, окно классов не появляется.
  5. Для сохранения в Studio: опубликуй игру и включи Game Settings → Security → Enable Studio Access to API Services.
  6. Звуки локаций: невидимый Part со Sound (Playing и Looped вкл.), RollOffMaxDistance — дальность, Volume — громкость.
  7. Детали: ящики, бочки, паутина, монетки — но без перегруза, иначе игра будет тормозить.
  8. Иконка — нейросеть (например, Kandinsky), 768 × 768. Game Settings → Basic Info, Permissions → Public.

Скрипты

MainMenu

Где: StarterGui → MainMenuGui · LocalScript
Зачем: Главное меню.

-- игроки
local Players = game:GetService("Players")
-- каждый кадр
local RunService = game:GetService("RunService")
-- этот игрок
local player = Players.LocalPlayer
-- камера
local camera = workspace.CurrentCamera
-- MainMenuGui
local gui = script.Parent
-- «Играть»
local playButton = gui:WaitForChild("BackFrame"):WaitForChild("PlayButton")
-- откуда смотрит камера меню
local cameraPart = workspace:WaitForChild("CameraPart")
-- управление персонажем
local controls = require(player:WaitForChild("PlayerScripts"):WaitForChild("PlayerModule")):GetControls()

-- в меню персонаж не ходит
controls:Disable()
-- каждый кадр, пока меню
local follow = RunService.RenderStepped:Connect(function()
    -- камерой управляет скрипт
    camera.CameraType = Enum.CameraType.Scriptable
    -- красивый вид на карту
    camera.CFrame = cameraPart.CFrame
-- конец функции
end)

-- «Играть»
playButton.MouseButton1Click:Connect(function()
    -- камера свободна
    follow:Disconnect()
    -- обычная камера за персонажем
    camera.CameraType = Enum.CameraType.Custom
    -- можно ходить
    controls:Enable()
    -- меню прячется (ResetOnSpawn выкл. — после смерти не вернётся)
    gui.Enabled = false
-- конец функции
end)

PlayerData

Где: ServerScriptService · Script
Зачем: Данные игрока и сохранение.

-- игроки
local Players = game:GetService("Players")
-- сохранение
local DSS = game:GetService("DataStoreService")
-- хранилище RPG
local store = DSS:GetDataStore("RPGData")
-- у кого данные загрузились
local loaded = {}

-- создаёт контейнер со значением
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)
    -- загружаем сохранение
    local ok, data = pcall(function() return store:GetAsync("rpg_" .. player.UserId) end)
    -- загрузка удалась
    if ok then
        -- сохранение есть
        if type(data) == "table" then
            -- монеты
            coins.Value = data.Coins or 0
            -- уровень
            lvl.Value = data.Lvl or 1
            -- порог уровня
            required.Value = lvl.Value * 100
            -- опыт
            exp.Value = data.Exp or 0
            -- класс — ClassServer выдаст навыки и оружие
            if data.Class then player:SetAttribute("Class", data.Class) end
        -- конец проверки
        end
        -- можно сохранять
        loaded[player] = true
    -- конец проверки
    end
    -- опыт изменился
    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

-- сохранить игрока
local function Save(player)
    -- не загрузились — не затираем сохранение нулями
    if not loaded[player] then return end
    -- что сохраняем
    local data = {
        -- монеты
        Coins = player.leaderstats["Монеты"].Value,
        -- уровень
        Lvl = player.Lvl.Value,
        -- опыт
        Exp = player.Exp.Value,
        -- класс
        Class = player:GetAttribute("Class"),
    -- конец таблицы
    }
    -- записываем
    pcall(function() store:SetAsync("rpg_" .. player.UserId, data) end)
-- конец функции
end

-- новые игроки
Players.PlayerAdded:Connect(Setup)
-- кто зашёл раньше скрипта
for _, player in ipairs(Players:GetPlayers()) do task.spawn(Setup, player) end
-- игрок вышел
Players.PlayerRemoving:Connect(function(player)
    -- сохраняем
    Save(player)
    -- забываем
    loaded[player] = nil
-- конец функции
end)
-- сервер закрывается
game:BindToClose(function()
    -- сохраняем всех
    for _, player in ipairs(Players:GetPlayers()) do Save(player) end
-- конец функции
end)

Проверка

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