Практикум — обби на Roblox

Roblox

Учебный проект в жанре обби (parkour): игрок проходит этапы, собирает монеты, покупает улучшения. Примеры на Luau, task.* и актуальный Creator Hub.

Перед стартом пройдите Roblox Studio — первая игра. Теория клиент–сервер — Разработка на Roblox; углублённая экономика — Внутриигровая экономика.


Цель проекта

Механика Где выполняется
Прогресс (этап, монеты) Сервер + DataStore
Чекпоинты и kill-блоки Сервер
Сбор наград на Part Сервер (валидация касания)
Магазин за внутриигровую валюту Сервер
Покупка за Robux (Developer Product) Сервер + MarketplaceService
HUD (монеты, этап) Клиент (отображение)
Запросы игрока КлиентRemoteEventСервер

Структура в Studio

Создайте в Explorer:

ServerScriptService
├── GameMain          (Script — точка входа)
ReplicatedStorage
├── Modules
│   └── DataModule    (ModuleScript)
├── Remotes
│   ├── ShopPurchase  (RemoteEvent)
│   └── StatsUpdated  (RemoteEvent)
ServerStorage
└── StageTemplates    (Folder — опционально)
Workspace
├── Stages            (Folder)
│   ├── Stage1 …      (Model с Spawn, Finish, Coins)
├── Lobby
└── SpawnLocation
StarterGui
└── HUD               (ScreenGui → TextLabel)
StarterPlayer
└── StarterPlayerScripts
    └── HUDClient     (LocalScript)
Game Settings для DataStore

В Game Settings → Security включите Enable Studio Access to API Services, иначе DataStore в локальном Play не сохраняется. В опубликованной игре сервис работает при включённых API в Experience.


Загрузчик ServerHandler

Все серверные системы удобно вешать на один Script в ServerScriptService и запускать дочерние ModuleScript параллельно — если один модуль упадёт при require, остальные всё равно подключатся.

ServerScriptService
└── ServerHandler (Script)
    ├── Data          (ModuleScript)
    ├── PartFunctions (ModuleScript)
    ├── Physics       (ModuleScript)
    ├── Monetization  (ModuleScript)
    └── Initialize    (ModuleScript — leaderstats, BindToClose)
--!strict
-- ServerScriptService/ServerHandler

for _, child in script:GetChildren() do
    if child:IsA("ModuleScript") then
        task.spawn(function()
            require(child)
        end)
    end
end

Модули храните в ServerScriptService (или ServerStorage, если клиенту не нужен require по пути). Клиент не должен иметь доступа к серверным модулям с логикой экономики.


Модуль данных DataModule

ReplicatedStorage/Modules/DataModule — единое место для загрузки, сохранения и изменения статистики.

Почему так: все изменения баланса проходят через серверный модуль; клиент не пишет в DataStore напрямую.

Рекурсивное копирование и API get / set / increment

Таблицы в Lua передаются по ссылке. Перед сохранением в DataStore копируйте сессию, чтобы случайно не испортить DEFAULT:

Троттлинг DataStore и повторные попытки

GetAsync / SetAsync могут отклоняться лимитами платформы. Оборачивайте вызов в pcall и повторяйте с паузой; ограничьте число попыток, чтобы не зависнуть:

Дополнительно: автосохранение раз в 120 с и BindToClose (см. GameMain ниже). Для атомарных обновлений в продакшене изучите UpdateAsyncсправочник DataStore.


Точка входа GameMain

ServerScriptService/GameMain:

BindToClose даёт время дописать DataStore при остановке сервера (важно в продакшене и при закрытии Studio-сервера).


Модуль PartFunctions

Один модуль на все "умные" Part в Workspace — kill, урон, чекпоинт, монета, магазин, значок. В Studio разметьте папки (KillParts, SpawnParts, …) и при старте пройдитесь по детям.

playerFromHit

--!strict
local Players = game:GetService("Players")

local function playerFromHit(hit: Instance): (Player?, Model?)
    local model = hit:FindFirstAncestorOfClass("Model")
    if not model then return nil, nil end
    local player = Players:GetPlayerFromCharacter(model)
    return player, model
end

Kill и Damage

Debounce на клиенте не защищает от читов — только снижает шум Touched. Валидация урона остаётся на сервере.

Чекпоинт SpawnParts

На Part — IntValue Stage (номер этапа). Игрок засчитывает этап только если текущий Stage == checkpoint - 1 (нельзя перепрыгнуть вперёд):

Монеты с StringValue Code

Аналог CoinTags из базовой версии: у Part — StringValue Code и IntValue Reward.


Группы коллизий — игроки не толкают друг друга

Тот же PhysicsService используют для головоломок "стена проходима только игроку" — см. Справочник по Roblox.


Значки BadgeService

Создайте значок в Creator Hub → Engagement → Badges, подставьте числовой id.


Этапы и чекпоинты

Разметка уровня

Для каждого Workspace/Stages/StageN (Model):

Объект Тип Роль
Spawn Part Точка респавна
Finish Part Зона завершения этапа
Kill Part Touched → сброс на Spawn
Coin Part + StringValue RewardId Награда один раз

Скрипт финиша (в ServerScriptService или ModuleScript)

Kill-блок: при Touched вызовите player:LoadCharacter() или телепорт на Spawn текущего этапа.


Монеты и защита от повторного сбора

Паттерн "один раз на игрока" — папка-теги у Player:

coinId — уникальное имя Part (например Stage2_Coin03), чтобы нельзя было фармить одну монету бесконечно.


RemoteEvent и клиентский HUD

Сервер уже шлёт StatsUpdated:FireClient(player, coins, stage, wins).

Клиент StarterPlayerScripts/HUDClient:

--!strict

local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")

local player = Players.LocalPlayer
local gui = player:WaitForChild("PlayerGui"):WaitForChild("HUD") :: ScreenGui
local label = gui:WaitForChild("StatsLabel") :: TextLabel
local StatsUpdated = ReplicatedStorage.Remotes.StatsUpdated :: RemoteEvent

StatsUpdated.OnClientEvent:Connect(function(coins: number, stage: number, wins: number)
    label.Text = string.format("Монеты: %d | Этап: %d | Победы: %d", coins, stage, wins)
end)

Запрос покупки из GUI:

-- Клиент — только FireServer с id товара
ShopPurchase:FireServer("SpeedCoil")

Магазин и монетизация

Внутриигровая валюта — ShopParts

Шаблоны Tool лежат в ReplicatedStorage/ShopItems. На Part в мире — StringValue ItemName (имя Tool). При касании сервер проверяет цену и клонирует Tool в Backpack:

Расходуемый Tool (бафф прыжка) логично реализовать LocalScript внутри Tool, который меняет Humanoid.JumpPower на 30 с — эффект локален, покупка остаётся серверной.

Game Pass и Developer Product

Тип Когда использовать
Game Pass Разовая покупка навсегда (VIP, x2 монет)
Developer Product Повторяемая покупка (пак монет, revive)

Касание Part с PromptId + BoolValue IsProduct:

MarketplaceService.PromptGamePassPurchaseFinished:Connect(function(player, passId, purchased)
    if purchased and REWARDS[passId] then
        REWARDS[passId](player)
    end
end)

UserOwnsGamePassAsync кеширует результат на сессию — после покупки в игре выдайте награду в обработчике PromptGamePassPurchaseFinished, а не только при входе.

ProcessReceipt и история покупок

Идемпотентность — отдельный DataStore "уже выдано":

Полная схема транзакций — Внутриигровая экономика Roblox.


Клиентские эффекты

Вращающиеся платформы и декор — клиент (RunService.RenderStepped), чтобы не нагружать сервер:

Trail на персонаже для VIP — косметика в StarterCharacter; логику "кто VIP" сервер реплицирует через атрибут или CollectionService tag.


Анти-эксплойт

Угроза Защита
Накрутка монет с клиента Все начисления только в серверных Touched / OnServerEvent
Повторная покупка Проверка баланса на сервере; списание до выдачи предмета
Спам FireServer Кулдаун os.clock() на игрока (2–5 с)
Подделка этапа SpawnParts проверяет stage - 1; финиш — только сервер
Дубль Robux PurchaseHistory + ProcessReceipt
Эксплойт debounce Debounce не заменяет сервер; урон только в DamageParts на сервере
Читы на скорость Серверная телепортация только через валидные зоны

Клиент никогда не меняет leaderstats и не создаёт награды в Workspace для других игроков.


Тестирование и публикация

Шаг Действие
1 Test → Play — пройдите этап, соберите монету, перезайдите (проверка DataStore)
2 Test → Start Server, 2 Players — второй клиент не ломает прогресс первого
3 Game Settings — API Services, иконка, описание
4 File → Publish to Roblox
5 Creator Hub → Public → пригласите друзей на playtest

Чек-лист практикума

  • ServerHandler подключает все модули через task.spawn
  • DataModule грузит и сохраняет данные; есть retry при ошибке DataStore
  • BindToClose и автосохранение работают
  • SpawnParts не даёт перепрыгнуть этап
  • KillParts / DamageParts только на сервере
  • Группа коллизий ObbyPlayers включена
  • Монеты и значки выдаются один раз
  • Магазин за монеты и ProcessReceipt для Robux протестированы
  • HUD через StatsUpdated
  • Игра опубликована и проверена вне Studio

См. также