Практикум — обби на 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 → 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