Разработка на Roblox
Всем
Разработка на Roblox
Платформа Roblox представляет собой экосистему, объединяющую пользователей и разработчиков для создания, публикации и взаимодействия с виртуальными мирами. Её ключевая особенность — возможность разработки трёхмерных игр без необходимости владения профессиональными 3D-редакторами или сложными игровыми движками. Основным инструментом для разработки выступает Roblox Studio — приложение, предоставляющее доступ к редактору сцены, системе физики, средствам визуализации и полнофункциональному языку программирования Lua.
Разработка на Roblox ориентирована как на начинающих, так и на опытных программистов. Низкий порог входа позволяет освоить основы геймдева уже на начальных этапах обучения, однако масштабируемость платформы и поддержка скриптования открывают возможности для реализации сложных механик, многопользовательского взаимодействия и даже экономических систем. Продвинутая тема экономики и магазина — в отдельной статье: Внутриигровая экономика Roblox.
Эта статья — справочник по архитектуре и API. Для первой игры идите по порядку: Studio и Place → практикум "обби" → по желанию королевская битва и механика и продвижение.
Полный маршрут — в о разделе.
Основы
Roblox поддерживает как простые мини-игры (например, паркур или гонки), так и сложные многопользовательские проекты с экономикой, прогрессией, кастомной физикой и серверной логикой. Платформа масштабируется вместе с вашими навыками — начав с перетаскивания блоков, вы можете перейти к проектированию распределённых систем, работающих с тысячами игроков одновременно.
Система Roblox
Roblox — платформа с клиент-серверной архитектурой и интегрированной средой разработки.
Она состоит из трёх уровней:
| Уровень | Роль | Примеры компонентов |
|---|---|---|
| Платформа (Roblox Cloud) | Хостинг, аутентификация, платёжная система, DataStore, DevHub | Marketplace, DataStoreService, Developer Products |
| Сервер игры (Game Server) | Выполняет авторитетную логику: движение, коллизии, экономика | Workspace, ServerScriptService, Players, физика |
| Клиент (Roblox Player) | Отображает игру, обрабатывает ввод, показывает GUI | StarterGui, LocalScript, рендер, звук |
Важно: при запуске игры в Studio вы запускаете локальный сервер + один клиент — это эмуляция реального окружения. Но архитектура остаётся прежней: сервер и клиент — разные процессы.
Roblox Cloud - это глобальная распределённая система, управляемая Roblox Corporation. Не подвергается прямому влиянию разработчика игры, но предоставляет сервисы через API.
Roblox построена по принципу разделения ответственности между облачной инфраструктурой, игровым сервером и клиентом. Эта структура гарантирует безопасность, масштабируемость и согласованность состояния во всех сессиях.
Представьте, что игрок нажимает кнопку "Купить меч". На клиенте отображается надпись "Покупка успешна", и меч появляется в руках. Однако если проверка баланса выполнена только на клиенте, злоумышленник может изменить код через DevConsole и получить предмет бесплатно. Поэтому вся критическая логика — на сервере, а клиент лишь отображает результат.
Платформа (Roblox Cloud)
Это глобальная распределённая система, управляемая Roblox Corporation. Не подвергается прямому влиянию разработчика игры, но предоставляет сервисы через API. Она обеспечивает аутентификацию, хостинг, платёжные операции, хранение данных и аналитику.
Компоненты и их назначение
| Компонент | Описание | Доступ из игры | Примечания |
|---|---|---|---|
| Authentication & Identity | Управление аккаунтами, сессиями, ролевой моделью (RBAC) | game.Players.LocalPlayer.UserId, UserOwnsGamePassAsync() |
Все Player объекты содержат только публичную информацию (Name, DisplayName); приватные данные (email, возраст) недоступны. |
| Game Hosting & Matchmaking | Выделение и управление игровыми серверами (Game Server), балансировка нагрузки, регион-маршрутизация | game.JobId, game.PlaceId |
Сервер игры существует не дольше 6 часов (лимит idle-time); при завершении все данные должны быть сохранены в DataStore. |
| DataStoreService | Постоянное хранилище данных игроков и игры. Поддерживает атомарные обновления (UpdateAsync) с квотами и повторами при сбоях — не полноценная СУБД. |
DataStoreService:GetDataStore() |
Основные типы: DataStore (ранее GlobalDataStore) и OrderedDataStore. Лимиты и квоты см. документацию DataStore. |
| MarketplaceService | Обработка покупок через Robux, возвратов, проверки владения Game Pass. | MarketplaceService:PromptPurchase(), .PromptPurchaseFinished |
Все операции — асинхронные. Подтверждение приходит только после финальной авторизации на сервере Roblox. |
| DevHub & Creator Dashboard | Система управления ассетами, монетизацией, аналитикой, Developer Products. | Только вне игры (веб-интерфейс) | Регистрация Developer Product требует: уникального ProductId, названия, описания, цены. После публикации — изменение цены невозможно (только создание нового продукта). |
| UGC & Asset Delivery | Хостинг и доставка пользовательских ассетов (модели, анимации, текстуры). | rbxassetid://..., rbxthumb://... |
Все ассеты проходят модерацию. Использование непроверенных внешних ресурсов (http://) запрещено политикой. |
| Telemetry & Analytics | Сбор метрик: retention, DAU, crash reports, performance stats. | Только через встроенные события (.ChildAdded, HttpService запрещён для отправки) |
Данные доступны разработчику в DevHub. Прямой экспорт (например, в Google Analytics) блокируется. |
Важные ограничения платформы
- Нет доступа к файловой системе — даже для временных файлов.
- Нет прямого HTTP-выхода —
HttpServiceразрешён только для ограниченного набора whitelisted-хостов (например, Discord webhooks — только с включённой опцией в Settings). - Время на сервере — UTC, без доступа к локальному времени игрока.
- Память ограничена: ~1.5–2.5 GB RAM на сервер (в зависимости от региона и тарифа). Переполнение → OOM-kill.
Сервер игры (Game Server)
Это экземпляр движка Roblox, развернутый в облаке или локально (в Studio). Выполняет авторитетную (authoritative) логику — то есть любое его решение считается окончательным. Например, если сервер говорит, что игрок мёртв, то он мёртв — даже если клиент продолжает показывать анимацию бега.
Компоненты
| Компонент | Назначение | Примеры |
|---|---|---|
| DataModel | Корень иерархии объектов. Содержит: Workspace, Players, Lighting, ReplicatedFirst и др. |
game, DataModel — синонимы. |
| Workspace | Сцена физического мира. Все BasePart, Model, Terrain, персонажи — здесь. |
Физика (PhysX), рендер, коллизии. |
| Players | Коллекция подключённых игроков. Управляет жизненным циклом Player и Character. |
game.Players.PlayerAdded, player.CharacterAdded. |
| ServerScriptService | Контейнер только для серверных скриптов (Script). Автоматически запускается при загрузке. |
Логика победы, экономика, инициализация уровней. |
| ReplicatedStorage | Общий контейнер для объектов, доступных и серверу, и клиентам. | Каталоги предметов, общие ModuleScript, RemoteEvent. |
| PhysicsService | Управление группами коллизий, физическими параметрами. | CreateCollisionGroup, CollisionGroupSetCollidable. |
| LogService | Централизованный логгер для сервера. | LogService.MessageOut:Connect(...). |
Жизненный цикл сервера
-
Инициализация
Загружаются —DataModel,Workspace,Lighting,ReplicatedStorage,ServerScriptService. ВыполняютсяScriptв порядке создания. -
Ожидание игроков
Сервер работает в фоне, обрабатывая события (например, таймеры), но не запускаетCharacterдо подключения. -
Подключение игрока
- Создаётся
Playerвgame.Players. - Вызывается
PlayerAdded. - Клонируются
StarterPlayer,StarterGui,StarterPack→ вplayer. - Сервер создаёт
player.Character(если не отключеноplayer:LoadCharacter()).
- Создаётся
-
Игровой процесс
- Физика, события, скрипты работают синхронно с тиком движка (~30–60 FPS).
- Сервер отправляет обновления клиентам через репликацию (изменения свойств, событий, объектов).
-
Отключение игрока
Characterуничтожается →player.Character = nil.- Вызывается
PlayerRemoving. - Данные игрока сохраняются (если предусмотрено).
-
Завершение сессии
Сервер останавливается через 6 часов бездействия или при ручной остановке. Все несохранённые данные теряются.
Ключевые принципы
-
Репликация идёт только "сверху вниз":
Server → Client, но не наоборот — кроме специально разрешённыхRemoteEvent/RemoteFunction. -
Сервер не имеет GUI — ни
ScreenGui, ниTextLabel, ни рендер.
Всё, что видит игрок, — результат репликации на клиент. -
Нет доступа к
LocalPlayer—game.Players.LocalPlayerвсегдаnilна сервере.
Клиент (Roblox Player)
Это приложение, установленное на устройстве игрока (Windows, macOS, iOS, Android, Xbox, VR). Отвечает за локальную интерактивность и рендер. Клиент — это глаза, уши и руки игрока. Он отображает мир, реагирует на ввод и отправляет запросы на сервер.
Компоненты
| Компонент | Назначение | Примеры |
|---|---|---|
| Renderer | 3D-рендеринг сцены (PBR, динамическое и запечённое освещение, пост-обработка). | Тени, отражения, эффекты камеры. |
| InputManager | Обработка клавиатуры, мыши, тача, геймпада. | UserInputService.InputBegan, ContextActionService. |
| SoundService | Воспроизведение звуков и музыки. | Sound:Play(), SoundService.RespectFilteringEnabled. |
| StarterGui | Шаблон GUI. Не отображается — клонируется в player.PlayerGui при входе. |
ScreenGui, Frame, TextButton. |
| LocalScript | Клиентские скрипты. Должны быть в PlayerScripts, StarterGui, Backpack. |
Анимации камеры, HUD, обработка кликов. |
| PlayerScripts | Специальный контейнер в StarterPlayerScripts. Загружается после Character. |
Animate.lua, пользовательские LocalScript. |
Жизненный цикл клиента
-
Загрузка
Подключается к Game Server, запрашиваетDataModel. -
Инициализация
ПринимаетWorkspace,Lighting, клонируетStarter*→ в свой контекст. -
Создание персонажа
После полученияplayer.Character— запускаютсяLocalScriptвPlayerScripts. -
Интерактивность
- Обработка ввода → отправка
RemoteEvent:FireServer(). - Обновление GUI в ответ на
RemoteEvent.OnClientEvent. - Рендер в реальном времени.
- Обработка ввода → отправка
-
Отключение
При выходе — остановка всех скриптов, очистка памяти.
Ограничения клиента
-
Нет доступа к другим игрокам, кроме
LocalPlayer.
game.Players:GetPlayers()возвращает всех, ноplayer.Characterдругих игроков — только позиция и ориентация (безHumanoid.Health, если не реплицируется). -
Нет авторитетного изменения
Workspaceс клиента — приFilteringEnabledлокальные правки частей не реплицируются на сервер; для игровой логики используйтеRemoteEvent/RemoteFunction. -
Локальные данные не сохраняются —
PlayerGui,Backpack,leaderstatsуничтожаются при выходе, если не синхронизированы с сервером. -
Девконсоль (F9) позволяет:
- Выполнять Lua-код (но только в локальной сессии);
- Изменять свойства объектов (но только нереплицируемые);
- Не даёт доступа к
ServerScriptService,DataStore.
Взаимодействие уровней
Пример — покупка предмета за монеты
| Этап | Платформа | Сервер | Клиент |
|---|---|---|---|
| 1. Инициация | — | — | button:Click() → BuyEvent:FireServer("Item_001", txId) |
| 2. Валидация | — | Проверка txId, баланса, наличия предмета |
— |
| 3. Транзакция | — | CurrencyManager:Spend(); Inventory:Unlock(); DataStore:UpdateAsync() |
— |
| 4. Подтверждение | — | BuyEvent:FireClient(player, "Success") |
Получает событие → обновляет GUI |
| 5. Сохранение | DataStore принимает запись, реплицирует в резервные ноды |
Операция завершена | — |
⚠️ Ни один этап не может быть пропущен. Пропуск валидации на сервере = уязвимость к читерству.
Порядок инициализации
Процесс запуска делится на три фазы — инициализация мира (server-only), подключение игрока (server), инициализация клиента (client). Каждая фаза имеет строгий порядок и набор событий.
Фаза 1. Инициализация сервера (до подключения игроков)
Происходит при запуске game, локально в Studio (Play) или в облаке (Production Server). Выполняется один раз за сессию сервера.
Шаг 1.1. Загрузка DataModel
- Создаётся корневой объект
DataModel(game). - Инициализируются системные сервисы:
WorkspacePlayersLightingReplicatedStorageServerScriptServiceSoundServiceHttpService(если разрешён)- и др.
⚠️
ReplicatedFirst,StarterGui,StarterPlayer— не создаются автоматически. Они появляются только если присутствуют в.rbxlxили были добавлены вручную.
Шаг 1.2. Выполнение Script в ServerScriptService
- Скрипты запускаются в порядке их создания (не алфавитном, не по имени).
- Параллельное выполнение:
- Если скрипт содержит
while true do task.wait(), он не блокирует загрузку других скриптов. task.spawn()иcoroutineпозволяют инициировать фоновые процессы.
- Если скрипт содержит
- Доступные сервисы:
game:GetService("Workspace")✅game.Players:GetPlayers()→{}(пустая таблица)game.Players.LocalPlayer→nil(сервер не имеет LocalPlayer)
Шаг 1.3. Инициализация Workspace
- Физика ещё не запущена:
Workspace:IsLoaded()→false. - Коллизии, гравитация,
Touched— неактивны. - Можно создавать объекты (
Part,Model), но они не участвуют в симуляции, покаWorkspace.Loadedне станетtrue.
Шаг 1.4. Событие Workspace.Loaded
- Флаг
Workspace.Loaded = trueустанавливается. - Запускается физический движок (PhysX).
- Объекты в
Workspaceстановятся физическими:Anchored = false→ начинают падать.Touched,TouchEnded— активны.BasePart:GetConnectedParts()— работает.
✅ Рекомендуется — откладывать физически значимые операции (например,
:MoveTo(),ApplyImpulse()) доWorkspace.Loaded:Wait().
Шаг 1.5. Инициализация Lighting
- Устанавливаются параметры по умолчанию (время суток, туман, Ambient).
- После
Workspace.Loaded— начинает влиять на рендер.
Фаза 2. Подключение игрока (серверная сторона)
Происходит при первом подключении игрока к серверу. Может повторяться для каждого игрока.
Шаг 2.1. Событие Players.PlayerAdded
- Создаётся объект
player(классPlayer) и добавляется вgame.Players. - Свойства
player:player.Name,player.DisplayName✅player.UserId✅player.Character→nilplayer.PlayerGui→nilplayer.Backpack→nil
⚠️ Попытка доступа к
player.Character.Humanoidздесь →attempt to index nil.
Шаг 2.2. Клонирование Starter-контейнеров
Последовательность строго фиксирована:
| Порядок | Действие | Куда клонируется |
|---|---|---|
| 1 | StarterPlayerScripts |
→ player.PlayerScripts |
| 2 | StarterCharacterScripts |
→ player.CharacterScripts (только при создании Character) |
| 3 | StarterPlayer |
→ player (свойства, атрибуты, скрипты) |
| 4 | StarterGui |
→ player.PlayerGui (создаётся пустой PlayerGui, затем клонируются дочерние элементы) |
| 5 | StarterPack |
→ player.Backpack (создаётся Backpack, затем клонируются инструменты) |
🔍 Важно:
StarterGuiиStarterPackне клонируются напрямую — создаются контейнеры (PlayerGui,Backpack), и в них копируются дети.- Если
StarterGuiпуст —player.PlayerGuiвсё равно создаётся (как пустая папка).- Клонирование не включает
LocalScriptвStarterPlayer→ они становятсяScriptвplayer.PlayerScripts.
Шаг 2.3. Инициализация PlayerGui и Backpack
player.PlayerGuiиplayer.Backpackстановятся не-nil.- Но: GUI ещё не отображается — рендер не начался.
Шаг 2.4. Создание персонажа (Character)
- Вызывается внутренне
player:LoadCharacter()(еслиplayer.Character=nilи не отключено в настройках). - Происходит асинхронно — обычно через 0.1–0.5 сек после
PlayerAdded. - Этапы создания персонажа:
- Создаётся
Model→player.Character. - В него клонируется R15/R6 rig (в зависимости от настроек).
- Применяются ассеты (одежда, лицо, тело — из аккаунта игрока).
- Запускается
Animator,Humanoid,HumanoidRootPart. - Вызывается событие
player.CharacterAdded.
- Создаётся
⚠️
player.CharacterAddedне гарантирует, что:
HumanoidRootPartуже существует (может быть задержка из-за загрузки мешей);Humanoid.Health > 0(иногдаHumanoidинициализируется сHealth = 0, затемHealth = 100).
Шаг 2.5. Событие player.CharacterAdded
- Сигнализирует, что
player.Character≠nilи содержит базовую иерархию. - Рекомендуемая практика:
player.CharacterAdded:Connect(function(character)
local hrp = character:WaitForChild("HumanoidRootPart", 5)
if not hrp then return end
local humanoid = character:WaitForChild("Humanoid", 5)
if not humanoid or humanoid.Health <= 0 then
-- Дождаться полной инициализации
humanoid.HealthChanged:Wait()
end
-- Теперь можно: hrp.Position, humanoid:MoveTo(), и т.д.
end)
Фаза 3. Инициализация клиента (локальная машина игрока)
Запускается после того, как сервер отправил клиенту DataModel и player.Character.
Шаг 3.1. Получение DataModel на клиенте
- Клиент получает синхронизированную копию
Workspace,Lighting,ReplicatedStorage. game.ReplicatedFirstклонируется →game.StarterGui→PlayerGui(локальная копия).
Шаг 3.2. Создание LocalPlayer
game.Players.LocalPlayer→ ссылка на собственного игрока.- Доступ к другим игрокам:
game.Players:GetPlayers()✅player.Characterдругих игроков — толькоPrimaryPart,Humanoid.Health(если разрешено),Position(реплицируется).
Шаг 3.3. Запуск LocalScript
LocalScriptвыполняются только в следующих контейнерах:PlayerScriptsPlayerGuiи его детиBackpackи его детиStarterPlayerScripts(клонируется вPlayerScripts)
LocalScriptвWorkspace,ReplicatedStorage,ServerScriptService— игнорируются.
Шаг 3.4. Событие PlayerGui.Initialized
- Гарантирует, что весь GUI загружен и отображается.
- Аналог
CharacterAddedдля интерфейса:
player.PlayerGui.Initialized:Connect(function()
-- Безопасно искать ScreenGui, Frame, Button
end)
Шаг 3.5. Запуск StarterCharacterScripts на клиенте
LocalScript, клонированные изStarterCharacterScripts, запускаются послеCharacterAdded.- Именно здесь работает
Animate.lua— анимации движения/прыжка.
Доступность объектов по времени
| Временная метка | player.Character |
player.PlayerGui |
Humanoid |
HumanoidRootPart |
LocalPlayer |
Workspace.Loaded |
|---|---|---|---|---|---|---|
PlayerAdded |
nil |
nil |
— | — | nil (сервер) / ✅ (клиент) |
? |
После клонирования StarterGui |
nil |
✅ (пустой) | — | — | ✅ | ? |
CharacterAdded |
✅ (Model) | ✅ | ✅ (но Health может быть 0) |
✅ (иногда с задержкой) | ✅ | ✅ |
Humanoid.Health > 0 |
✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
🔎 Примечание:
Workspace.Loadedобычно происходит до первогоPlayerAdded, но в сложных сценах (большие мешы, скрипты инициализации) — может быть позже.
Типичные ошибки и как их избежать
| Ошибка | Причина | Решение |
|---|---|---|
attempt to index nil with 'Humanoid' |
Доступ к player.Character.Humanoid в PlayerAdded |
Использовать player.CharacterAdded:Wait() или :Connect() |
| GUI не отображается | LocalScript ищет ScreenGui сразу после PlayerAdded |
Дождаться player.PlayerGui.Initialized или использовать player.PlayerGui:WaitForChild("MyGui") |
| Скрипт не запускается | LocalScript размещён в ReplicatedStorage |
Перенести в StarterGui или StarterPlayerScripts |
RemoteEvent не срабатывает |
RemoteEvent создан на клиенте, а не в ReplicatedStorage |
Создавать RemoteEvent в ReplicatedStorage на сервере до подключения игроков |
| Предмет не появляется в инвентаре | Попытка tool.Parent = player.Backpack до PlayerAdded |
Выполнять в PlayerAdded или CharacterAdded |
Как проверить порядок самому?
Добавьте в ServerScriptService этот скрипт:
Instance
Instance — базовый класс (в терминах ООП — суперкласс), от которого наследуются все объекты в среде Roblox. Это активный субъект, участвующий в репликации, сериализации, событийной модели и управлении памятью.
Наследуется всё — Part, Model, Script, RemoteEvent, Player, Folder, DataModel.
Техническое определение
- Тип:
DataType.Instance(в Lua —typeof(obj) == "Instance"). - Наследование: иерархия классов реализована в C++ ядре движка; в Lua доступна только через
obj.ClassName,obj:IsA("Part"). - Уникальный идентификатор: каждому
Instanceприсваивается GUID при создании (obj:GetDebugId()→ строка вида"1234567890"). Не путать сobj.Name(не уникален) илиobj.UserId(только дляPlayer).
✅ Проверка:
local part = Instance.new("Part") print(typeof(part)) -- "Instance" print(part.ClassName) -- "Part" print(part:IsA("BasePart"))-- true (Part <: BasePart <: Instance)
Структура Instance
Каждый экземпляр состоит из четырёх взаимосвязанных компонентов. Нарушение целостности одного — делает объект неработоспособным.
| Компонент | Назначение | Реализация | Доступность |
|---|---|---|---|
| Класс (Class Info) | Определяет метаданные: список свойств, методов, событий, наследование. | Загружается из rbxmx-манифестов при старте движка. Неизменяем. |
Только для чтения (ClassName, IsA(), GetFullName()) |
| Состояние (State) | Текущие значения свойств (Position, Name, Enabled и т.д.). |
Хранится в памяти; сериализуется в .rbxlx/.rbxl. |
Чтение/запись через obj.Property = value |
| Поведение (Methods & Events) | Логика: методы (:Clone()) и точки расширения (.Touched). |
Часть класса; методы — вызовы C++ функций. | Вызов через obj:Method() или obj.Event:Connect() |
| Контекст (Hierarchy) | Положение в дереве (Parent, дети, Ancestors). |
Управляется через Parent-ссылку; критично для репликации и сборки мусора. |
Изменение через obj.Parent = parent |
Пример:
local part = Instance.new("Part")
part.Name = "Cube"
-- На этом этапе объект существует в памяти, но не виден и не участвует в физике.
part.Parent = workspace
-- Теперь объект активен: рендерится, реагирует на коллизии, реплицируется.
⚠️ Важно:
- Свойства, методы и события не определяются динамически (как в JavaScript). Они зашиты в класс.
- Попытка
part.NewProperty = 42не создаёт новое свойство — это создаёт локальную переменную в таблице Lua, не связанную сInstance.- Для хранения пользовательских данных используйте
obj:SetAttribute("key", value)илиFolderсIntValue/StringValue.
Класс, экземпляр, переменная
| Понятие | Что это | Где хранится | Жизненный цикл |
|---|---|---|---|
Класс (Part, Script) |
Метаописание: какие свойства/методы есть, как сериализуется, как ведёт себя в физике. | В C++ библиотеке движка (rbx-core.dll и др.). Загружается один раз при старте. |
Вечный — пока работает движок. |
Экземпляр (workspace.Ground) |
Конкретный объект в сцене. Имеет состояние и позицию в иерархии. | В куче (heap) движка. Имеет ссылочный счётчик. | От Instance.new() / клонирования — до :Destroy() или сборки мусора. |
Переменная в скрипте (local p = workspace.Ground) |
Ссылка (указатель) на экземпляр. Не содержит данных — только адрес. | В Lua-стеке/куче. | До выхода из области видимости или переприсваивания. |
Пример, демонстрирующий разницу
🔍 При отладке в Studio:
— Выделениеpart1иpart2в Explorer — разные элементы.
—print(part1, part2)→ разныеDebugId.
Свойства
Свойства доступный через точечную нотацию: part.Size, script.Disabled.
Наиболее важные методы:
:Clone()— создаёт глубокую копию объекта и всех детей. Возвращает новый экземпляр сParent = nil.:Destroy()— немедленно удаляет объект из памяти и отключает все события. Безопасен для повторного вызова.:FindFirstChild(name, recursive?)— ищет ребёнка по имени. Не вызывает ошибку, если не найден.:WaitForChild(name, timeout?)— блокирующий поиск, полезен при инициализации:
local hrp = character:WaitForChild("HumanoidRootPart", 5)
Типы свойств
| Тип | Примеры | Особенности |
|---|---|---|
| Базовые (Base) | Name (string), Parent (Instance?), Archivable (bool) |
Есть у всех Instance. Изменение Name не влияет на репликацию. |
| Класс-специфичные | Part.Size (Vector3), Script.Source (string), RemoteEvent.OnServerEvent (RBXScriptSignal) |
Определены в классе. Недоступны в других классах (Part.Script → ошибка). |
| Сериализуемые | Почти все, кроме LocalScript.Disabled, Player.UserId |
Сохраняются в .rbxlx. Несериализуемые — игнорируются при сохранении. |
| Реплицируемые | Part.Position, StringValue.Value, Player.DisplayName |
Изменения отправляются клиентам. Не реплицируются: LocalScript, Player.UserId, Folder.ChildAdded и др. |
Доступ к свойствам
- Чтение:
obj.Property— всегда возвращает текущее значение (может бытьnil, если свойство не инициализировано). - Запись:
obj.Property = value— валидируется типом:
part.Size = "hello" -- ошибка: "Unable to cast string to Vector3"
- Отсутствующее свойство:
print(part.NonExistent) -- nil (без ошибки)
part.NonExistent = 5 -- игнорируется (не создаёт свойство)
Событие .Changed
Срабатывает после успешного изменения многих свойств (например, Size, Name). Смена родителя отражается в AncestryChanged, а не в Changed.
part.Changed:Connect(function(property)
print("Свойство", property, "изменено на", part[property])
end)
part.Size = Vector3.new(2, 2, 2)
-- Вывод: "Свойство Size изменено на 2, 2, 2"
part.Name = "NewCube"
-- Вывод: "Свойство Name изменено на NewCube"
⚠️ Не используйте
.Changedдля критичной логики (например, сохранения):
— Оно срабатывает при любом изменении, включая репликацию.
— Не гарантирует порядок относительно других событий.
— Для отслеживанияParentподписывайтесь наAncestryChanged.
Методы
Методы — вызовы C++ функций через Lua-обёртку. Они не копируются при клонировании — поведение определяется классом.
Наиболее важные методы
| Метод | Назначение | Особенности |
|---|---|---|
:Clone() |
Создаёт глубокую копию объекта и всех детей. | Возвращает новый экземпляр с Parent = nil. Требует obj.Archivable = true (по умолчанию true). |
:Destroy() |
Уничтожает объект и всех детей. | Немедленно удаляет из памяти; события отключаются; Parent = nil. Безопасно вызывать повторно. |
:FindFirstChild(name, recursive?) |
Поиск ребёнка по имени. | Не вызывает ошибку, если не найден → возвращает nil. recursive = true — поиск во всей подветке. |
:WaitForChild(name, timeout?) |
Блокирующий поиск (с ожиданием). | Используется при инициализации (Character:WaitForChild("HumanoidRootPart")). При timeout → nil. |
:GetChildren() |
Возвращает таблицу прямых детей. | Не рекурсивно. Порядок — как в Explorer (по времени создания). |
:IsDescendantOf(parent) |
Проверка принадлежности к ветке. | Используется для проверки контекста (if tool:IsDescendantOf(player.Backpack) then). |
:ClearAllChildren() |
Удаляет всех детей (но не сам объект). | Эквивалентно for _, c in ipairs(obj:GetChildren()) do c:Destroy() end. |
✅ Рекомендация:
Всегда используйте:Destroy(), а неobj.Parent = nil.
—obj.Parent = nilоставляет объект в памяти, если есть ссылки (утечка).
—:Destroy()гарантирует освобождение ресурсов.
События
События в Roblox — это экземпляры RBXScriptSignal. Они предоставляют два основных метода:
| Метод | Назначение | Возвращаемое значение |
|---|---|---|
:Connect(callback) |
Подписка на событие. | RBXScriptConnection — объект соединения. |
:Wait() |
Блокирующее ожидание события. | Значения, переданные в Fire(...). |
Пример работы с соединением
local connection = workspace.Part.Touched:Connect(function(hit)
print("Коснулся", hit.Name)
end)
-- Отмена подписки
connection:Disconnect() -- событие больше не будет вызываться
-- Проверка состояния
print(connection.Connected) -- false после Disconnect()
⚠️ Утечки памяти:
— Если не вызвать:Disconnect(), соединение остаётся в памяти даже после:Destroy()объекта.
— Особенно критично вLocalScript: при выходе игрока соединения не отключаются автоматически.
Диагностика и отладка Instance
| Задача | Метод | Пример |
|---|---|---|
| Узнать класс | obj.ClassName |
"Part" |
| Проверить тип | obj:IsA("BasePart") |
true для Part, MeshPart |
| Полный путь | obj:GetFullName() |
"Workspace.Ground" |
| Отладочный ID | obj:GetDebugId() |
"123456789" (уникален в сессии) |
| Проверить, уничтожен ли | not obj or not obj.Parent |
Ненадёжно; лучше отслеживать вручную |
| Проверить, клонируемый ли | obj.Archivable |
true по умолчанию для большинства объектов |
🛠️ Совет: при отладке используйте
warn(obj:GetFullName(), obj.ClassName, obj:GetDebugId())— это выводит кликабельную ссылку в Output Studio.
Основные понятия
Instance
В Roblox всё — объект типа Instance:
Workspace,Part,Script,RemoteEvent,Player,Folder,DataModel.
Каждый Instance обладает:
- Классом — определяет возможности объекта (например, у
PartестьSize,Anchored; уRemoteEvent—FireServer,OnServerEvent). - Свойствами — данные, которые можно читать/писать (
Name,Parent,Transparency,Position). - Методами — действия, которые можно вызвать (
:Clone(),:Destroy(),:FindFirstChild()). - Событиями — сигналы, на которые можно подписаться (
.Touched,.Changed,MouseButton1Click).
⚠️ Не путайте:
- Класс — абстрактное описание (например,
Part).- Экземпляр — конкретный объект в сцене (например,
GroundвWorkspace).- Переменная в скрипте — ссылка на экземпляр, а не его копия.
Иерархия
Любой Instance имеет ровно одного родителя (Parent) и может иметь много детей.
-- Создание объекта и привязка к иерархии
local part = Instance.new("Part")
part.Name = "MyCube"
part.Parent = workspace -- ← важнейшая строка: без неё объект "висит в воздухе"
| Контекст | Как работает Parent |
|---|---|
| В Explorer | Дочерние объекты отступлены, скрытие родителя скрывает детей |
| В коде | game.Workspace.MyCube — синтаксический сахар для game:GetService("Workspace"):FindFirstChild("MyCube") |
| При клонировании | :Clone() копирует всю подветку, но отвязывает от родителя — нужно вручную задать Parent. |
✅ Пример ошибки:
local part = Instance.new("Part") part.Position = Vector3.new(0, 10, 0) part.Anchored = true -- ... и ничего не происходитПричина:
part.Parent == nil. Объект создан, но не добавлен в сцену → не рендерится и не участвует в физике.
Контейнеры и сервисы
Что такое контейнер?
Контейнер — это Instance, который может содержать другие объекты.
Основные типы:
| Контейнер | Тип | Роль | Доступ |
|---|---|---|---|
Workspace |
Model |
Все видимые объекты мира | Сервер + клиент |
ReplicatedStorage |
Folder |
Данные и объекты для обмена | Сервер + клиент |
ServerScriptService |
Folder |
Серверные скрипты | Только сервер |
StarterGui |
Folder |
Шаблоны GUI для игроков | Только сервер (клонируется) |
Players |
Players |
Коллекция подключённых игроков | Сервер + клиент (только свой игрок) |
Сервис — специальный контейнер, предоставляющий API (например,
MarketplaceService,PhysicsService).
Доступ к сервисам:local ds = game:GetService("DataStoreService") local phys = game:GetService("PhysicsService")
Как объект попадает в игру?
- Ручное размещение в Studio → сохраняется в
.rbxlx→ загружается сервером при старте. - Клонирование из
Starter*→ при заходе игрока. - Создание через
Instance.new()→ динамически, во время игры.
→ Только объекты с Parent ~= nil и находящиеся в Workspace, Lighting, Players и т.п. становятся активными.
Добавление скрипта к объекту
Этот раздел можно использовать как универсальный алгоритм для любого объекта — Part, Model, Tool, ScreenGui, ImageButton. Следуйте шагам строго — это гарантирует корректную работу и избегание распространённых ошибок.
Шаг 1. Создание объекта в сцене
Действия в Studio
-
Откройте вкладку Home → нажмите Part
→ вWorkspaceпоявитсяPart(по умолчанию —Position = (0, 5, 0),Size = (2, 2, 2),Anchored = false). -
В Explorer найдите объект:
Workspace
└── Part ── [выделите его]
- В Properties задайте:
Name = "Button"(для идентификации в коде),Anchored = true(чтобы не падал),Color = BrickColor.new("Bright green")(визуальная обратная связь),CanCollide = false(чтобы игрок мог пройти сквозь него — опционально).
✅ Почему
Anchored = true?
ЕслиAnchored = false, объект подчиняется физике: упадёт, может улететь при столкновении → поведение непредсказуемо. Для интерактивных элементов (кнопок, триггеров) — почти всегдаtrue.
Шаг 2. Добавление скрипта к объекту
Действия в Studio
- Убедитесь, что
Buttonвыделен в Explorer. - ПКМ по
Button→ Insert Object… → выберите Script
→ создаётсяScriptвнутриButton(в иерархии:Workspace.Button.Script).
⚠️ Критическое правило:
— Скрипт, помещённый внутрь объекта, имеет прямой доступ к нему черезscript.Parent.
— Это надёжнее, чемworkspace:FindFirstChild("Button"), потому что:
• не зависит от имени;
• не требует поиска;
• работает даже при переименовании объекта.
Что произошло технически
- Создан экземпляр класса
Script(неLocalScript, неModuleScript). - Его свойство
Parentустановлено вButton. - Скрипт автоматически запущен (сервером), так как находится в
Workspace— зона видимости сервера.
Шаг 3. Базовый шаблон скрипта
Замените содержимое Script на следующий код — это универсальный шаблон, пригодный для 90 % простых интерактивных объектов:
Разбор шаблона по блокам
| Блок | Зачем нужен | Что будет, если убрать |
|---|---|---|
local object = script.Parent |
Прямая привязка к контексту. Надёжнее workspace.Button. |
При переименовании/копировании — сломается. |
IsA("BasePart"/"Model") |
Защита от размещения скрипта в Folder, IntValue и др. |
Ошибка attempt to index nil при Touched. |
Проверка hit.Parent + Humanoid |
Игнорировать касания частей без персонажа (мусор, снаряды). | Сработает на любом Part, не привязанном к модели игрока. |
hit:IsDescendantOf(object) |
Защита от self-touch (например, при Anchored = false). |
Объект может активировать сам себя. |
enabled + Destroying |
Гарантированная отмена подписки. | Утечка памяти при удалении объекта. |
TweenService / Model:ScaleTo() |
Плавное изменение: у Part — твин свойств, у Model — ScaleTo. |
Резкое Size = ... выглядит дёргано. |
Шаг 4. Расширение — другие типы взаимодействия
Тот же шаблон работает для любого события — нужно только заменить:
| Тип взаимодействия | Что изменить в шаблоне |
|---|---|
| Нажатие мышью (в GUI) | Объект: TextButton в ScreenGui; Событие: MouseButton1Click; Проверка: не нужна (GUI — только клиент) |
| Использование инструмента | Объект: Tool в Backpack; Событие: Activated; Место скрипта: внутри Tool → будет Script, а не LocalScript |
| Вход в зону (Region3) | Объект: Part как триггер; Логика: while true do task.wait(); if hrp.Position inside region then ...; Событие: не используется — polling |
| Клиентская анимация (камера, HUD) | Скрипт: LocalScript, размещённый в StarterGui/Button; Событие: MouseButton1Click |
Пример — кнопка в GUI (клиентская версия)
✅ Правило безопасности:
— Клиент может инициировать действие, но не выполнять его напрямую.
— Реальная логика (начисление монет, выдача предмета) — только послеFireServer→ серверная валидация.
События
События — это точки расширения, в которые можно "подключить" код.
-- Подписка на событие
workspace.Part.Touched:Connect(function(hit)
print("Столкнулся с", hit.Name)
end)
Как это работает:
- Происходит физическое столкновение → движок вызывает событие
.Touched. - Система ищет все активные
:Connect()на этом событии. - Для каждого — создаётся задача, выполняемая в порядке подписки.
✅ Важно:
:Connect()возвращает соединение (RBXScriptConnection) — его можно.Disconnect().- События не блокируют основной поток: обработчики запускаются асинхронно.
Игрок
player.Character — это объект типа Model, размещённый непосредственно в Workspace. Он представляет физическую форму игрока в мире — тело, анимации, коллизии, передвижение. Его наличие необязательно — игрок может быть в игре без персонажа (например, в интерфейсном меню), но любое взаимодействие с 3D-миром требует его загрузки.
Персонаж создаётся, существует и уничтожается в рамках строго определённого цикла, управляемого движком. На каждый этап приходится конкретное событие и состояние объекта.
-
Состояние
player.Character = nil
Это начальное и конечное состояние. Оно возникает:- до первого вызова
player:LoadCharacter()(обычно сразу послеPlayerAdded); - после
player:UnloadCharacter()(например, при смерти, телепортации в меню); - при выходе игрока (
PlayerRemoving).
- до первого вызова
-
Создание персонажа
Происходит вызовомplayer:LoadCharacter(), который инициирует:- Создание пустого
Model→ присваиваетсяplayer.Character. - Клонирование базового скелета (R6 или R15, в зависимости от настроек аккаунта):
- R6 — 6 частей —
Head,Torso,Left/Right Arm,Left/Right Leg; - R15 — 15 частей — добавлены
Upper/LowerTorso,Left/RightHand, суставы.
- R6 — 6 частей —
- Установка
PrimaryPart = HumanoidRootPart— точка привязки для перемещения. - Добавление системных объектов:
Humanoid— контроллер здоровья, состояния (Running, Jumping), смерти;Animator— управление анимациями (черезAnimationTrack);HumanoidDescription— описание внешности (тело, лицо, одежда);StarterGear— экипировка по умолчанию (если настроена вStarterPlayer).
- Создание пустого
-
Событие
CharacterAdded
Генерируется после присвоенияplayer.Character = model, но до полной загрузки всех частей. На момент события:model:FindFirstChild("Humanoid")может вернутьnil, если загрузка мешей задержана;Humanoid.Healthчасто равен0в первый тик, затем устанавливается в100;HumanoidRootPartможет отсутствовать на 1–2 кадра (особенно при медленной загрузке ассетов).
-
Полная инициализация
Состояние "персонаж готов" наступает, когда:Humanoid.Health > 0;HumanoidRootPartсуществует и имеетCFrame;Animatorзагрузил базовые анимации (idle,walk,jump).
Рекомендуемый способ ожидания:
player.CharacterAdded:Connect(function(char)
local hrp = char:WaitForChild("HumanoidRootPart", 5)
local hum = char:WaitForChild("Humanoid", 5)
if not (hrp and hum) then return end
hum.HealthChanged:Connect(function(health)
if health > 0 then
-- Персонаж полностью инициализирован
end
end)
end)
- Уничтожение персонажа
Инициируетсяplayer:UnloadCharacter()или автоматически при:Humanoid.Health <= 0(если включено автовоскрешение — создаётся новый);- телепортации в зону без физики;
- выходе из игры.
Перед уничтожением генерируется событиеCharacterRemoving, позволяющее сохранить состояние (позиция, инвентарь в руках).
Объект player (тип Player) содержит:
| Свойство / Дочерний объект | Назначение |
|---|---|
.UserId |
Уникальный ID в системе Roblox |
.Name |
Отображаемое имя |
.Character |
Модель персонажа (м.б. nil при входе/выходе) |
.PlayerGui |
GUI, отображаемый только этому игроку |
.Backpack |
Инвентарь вне экипировки |
.leaderstats (соглашение) |
Папка для счётчиков (уровень, монеты) |
⚠️
player.Characterможет бытьnilне только при входе, но и при смерти, телепортации, смене персонажа.
Обязательные компоненты персонажа
Ниже — минимальный набор объектов, без которых персонаж не будет функционировать как игровая сущность.
-
HumanoidRootPart
ЭтоPart(обычноBallSocketилиBlock), служащий точкой опоры для физики и перемещения. Именно егоCFrameиспользуется движком для:- расчёта позиции игрока в мире;
- привязки камеры;
- определения центра масс в физике.
ЕслиHumanoidRootPartотсутствует илиnil, методы вродеHumanoid:MoveTo()игнорируются.
-
Humanoid
Контроллер состояния персонажа. Его свойства управляют поведением:Health,MaxHealth— система жизни;WalkSpeed,JumpPower— физические параметры;State(Enum.HumanoidStateType) — текущее действие (Walking, Jumping, Dead);SeatPart— если персонаж сидит, ссылается наSeat.
Методы:TakeDamage(),:Sit(),:MoveTo()вызывают изменения состояния и генерируют события (Touched,Died).
-
Animator
Отвечает за проигрывание анимаций. Работает сAnimation(ассеты, загруженные из библиотеки илиAnimationIds).
Важно:Animatorне создаётся вручную — он добавляется автоматически при загрузке персонажа. -
Скелет (R6/R15)
ИерархияPartиJointInstance(BallSocketConstraint,HingeConstraint).
Пример R15:
Character
├── HumanoidRootPart
├── LowerTorso
│ ├── LeftLeg
│ └── RightLeg
├── UpperTorso
│ ├── LeftArm
│ └── RightArm
│ └── Head
└── ...
Коллизии между частями отключены (CanCollide = false), чтобы избежать самопроизвольных падений.
Разработчик может добавлять в персонажа:
Tool— экипированный предмет (вBackpackили прямо вCharacter);ProximityPrompt— интерактивные подсказки;Script/LocalScript— поведение, привязанное к персонажу (например,Animate.lua— клонируется изStarterCharacterScripts).
Инвентарь
Термин "инвентарь" в Roblox не имеет единого объекта-контейнера. Вместо этого используется двухуровневая система, разделённая на:
Backpack— серверный контейнер для предметов, не экипированных в текущий момент;PlayerGui+Character— клиентские места отображения (GUI) и экипировки (в руках).
Это разделение отражает архитектурный принцип: сервер управляет состоянием, клиент — отображением.
Backpack — серверное хранилище
-
Это объект типа
Backpack, дочерний по отношению кplayer. -
Создаётся автоматически при подключении игрока, если в
StarterPackесть хотя бы один предмет или явно вызваноplayer:FindFirstChild("Backpack")(ленивая инициализация). -
Содержимое — только объекты типа
Tool(и его наследники, например,HopperBin).
Другие типы (Part,Folder,IntValue) игнорируются движком: они не отображаются в интерфейсе, не подчиняются логике экипировки. -
Жизненный цикл предмета в
Backpack:- Игрок подбирает предмет → сервер вызывает
tool.Parent = player.Backpack. - Предмет появляется в клиентском инвентаре (рендерится через
StarterGui/BackpackFrame). - При нажатии — клиент отправляет
tool:Activate(), сервер получаетtool.Activated. - Сервер проверяет:
—tool:IsDescendantOf(player.Backpack)→ можно экипировать;
—player.Characterсуществует → можно поместить в руки. - Сервер вызывает
tool.Parent = player.Character→ предмет переходит в персонаж.
- Игрок подбирает предмет → сервер вызывает
-
Ограничения:
- Максимум 10 предметов в
Backpackпо умолчанию (настраивается черезStarterPlayer.MaxBackpackItems). - Предметы в
Backpackне участвуют в физике, даже еслиCanCollide = true. Backpackдоступен только на сервере; клиент видит его содержимое через репликацию, но не может напрямую изменять.
- Максимум 10 предметов в
Экипировка — предметы в руках
Когда предмет "в руках", он находится внутри Character, обычно в Torso или UpperTorso.
Типичная иерархия:
Character
└── UpperTorso
└── Sword (Tool)
├── Handle (Part) — видимая модель
└── Script — логика атаки
- Активация: вызов
tool:Activate()на клиенте →tool.Activatedна сервере. - Деактивация:
tool:Deactivate()или смена оружия → сервер перемещаетtool.Parent = player.Backpack.
🔐 Безопасность:
— Клиент не может вызватьtool.Parent = workspaceнапрямую — сервер отклонит операцию.
— Сервер должен проверять, чтоtoolпринадлежит игроку:tool.AncestryChanged:Connect(function(_, newParent) if newParent ~= player.Character and newParent ~= player.Backpack then tool.Parent = player.Backpack -- возврат в инвентарь end end)
Визуальное отображение (GUI)
Инвентарь не имеет встроенного GUI. Его отображение — обязанность разработчика.
-
Типичный подход:
- В
StarterGuiсоздаётсяScreenGuiсFrameиImageButtonдля каждого слота. - На клиенте
LocalScriptподписывается на:player.Backpack.ChildAdded→ отобразить новый предмет;player.Backpack.ChildRemoved→ убрать иконку;Humanoid.EquipTool/UnequipTool→ подсветить активный слот.
- Для иконок используются
ImageLabelсrbxassetid://...(ассеты из Marketplace или загруженные через DevHub).
- В
-
Важно:
GUI должен обновляться только на клиенте, но данные — браться с сервера черезRemoteEvent. Например:
-- Сервер
inventoryUpdate:FireClient(player, { "Sword", "Shield", nil, ... })
-- Клиент
inventoryUpdate.OnClientEvent:Connect(function(slots)
for i, itemName in ipairs(slots) do
slotsGUI[i].Icon.Image = getIconId(itemName)
end
end)
Постоянное хранение
Ни Backpack, ни Character не сохраняются между сессиями. Для долговременного инвентаря используется:
DataStore— сохранение спискаToolпоProductIdили имени:
DataStore:SetAsync("Inventory_" .. player.UserId, { "Sword", "Potion" })
leaderstats(соглашение) — временные счётчики в рамках сессии:
local leaderstats = Instance.new("Folder")
leaderstats.Name = "leaderstats"
leaderstats.Parent = player
local coins = Instance.new("IntValue")
coins.Name = "Coins"
coins.Value = 100
coins.Parent = leaderstats
⚠️
leaderstats— это не системный объект, а соглашение сообщества. Его использование не гарантируется, но поддерживается всеми популярными фреймворками (например,StarterPlayerScripts/Chat).
Взаимодействие
Цикл "взять → экипировать → использовать → убрать" выглядит так:
- Сервер создаёт
ToolвWorkspace(например,Sword). - Игрок касается предмета → сервер проверяет расстояние и права →
sword.Parent = player.Backpack. - Клиент получает репликацию → отображает иконку в GUI.
- Игрок нажимает кнопку → клиент вызывает
sword:Activate(). - Сервер получает
sword.Activated→ проверяет:sword.Parent == player.Backpack— можно экипировать;player.Characterсуществует — можно поместить в руки.
- Сервер:
sword.Parent = player.Character.UpperTorso. - Клиент: запускает анимацию, показывает эффекты.
- При использовании (удар) — клиент отправляет
RemoteEvent:FireServer("Attack"), сервер валидирует и применяет урон.
Любой пропущенный шаг (например, отсутствие проверки Parent на сервере) приводит к уязвимости или ошибке.
Транзакции, экономика и магазин
Подробный разбор серверных транзакций, проектирования магазина, DataStore, Developer Products и пошагового гайда вынесен в отдельную статью:
→ Внутриигровая экономика Roblox — идемпотентность покупок, каталог, CurrencyManager, тестирование в Studio.
Кратко: покупки за внутриигровую валюту проверяются и исполняются только на сервере; клиент отправляет запрос через RemoteEvent и обновляет UI после ответа. Покупки за Robux — через MarketplaceService:PromptPurchase и PromptPurchaseFinished (см. Монетизация).
Ландшафт
Ландшафт (объект Terrain) — это специализированный компонент среды, предназначенный для создания непрерывных, объёмных, деформируемых поверхностей крупного масштаба — гор, пещер, рек, рельефа местности. В отличие от сборки из отдельных Part, ландшафт представляет собой единое воксельное поле, управляемое движком на уровне низкоуровневых операций. Его существование обусловлено необходимостью баланса между визуальной сложностью, физической достоверностью и производительностью при масштабах, недостижимых через классический подход с BasePart.
Внутреннее устройство
Ландшафт организован как трёхмерная регулярная сетка ячеек — вокселей (volume pixels). Каждый воксель имеет фиксированный размер — 2 × 2 × 2 метра (это значение неизменно и не настраивается). Вся доступная для редактирования область делится на чанки размером 64 × 64 × 64 вокселя (то есть 128 × 128 × 128 метров в мире). Чанки загружаются и выгружаются динамически по мере перемещения игрока, что позволяет работать с картами размером до 8192 × 8192 × 512 метров (теоретический лимит), не перегружая память.
Каждый воксель хранит несколько атрибутов:
- Материал — определяет визуальный вид и физические свойства (земля, камень, вода, песок и др.; всего 36 встроенных материалов).
- Форма — геометрия внутри вокселя — пусто, полный куб, наклонная плоскость (ramp), угловая поверхность (wedge) и комбинации (например, "четверть куба"). Это позволяет создавать плавные склоны без ступенчатых переходов.
- Цвет — корректирующий оттенок (tint), накладываемый на базовую текстуру материала.
Движок генерирует единый меш на каждый чанк, объединяя все воксели с одинаковым материалом и смежными гранями. Этот процесс называется mesh stitching. Благодаря ему количество отрисовываемых объектов остаётся пропорциональным числу чанков, а не числу вокселей — что даёт решающее преимущество в производительности при больших ландшафтах.
Физическое поведение
Физика ландшафта реализована на основе воксельной коллизионной сетки, а не полигональных мешей. Это означает, что коллизии рассчитываются не по точной геометрии поверхности, а по упрощённой сетке, где каждый воксель представляет собой куб или призму. Точность коллизий зависит от разрешения формы внутри вокселя, но даже в максимальном качестве она уступает MeshPart с высокополигональной коллизией.
Ключевые особенности:
- Объекты с
CanCollide = trueвзаимодействуют с ландшафтом как с твёрдой поверхностью;Anchored = falseчасти падают и останавливаются на ней. TouchedиTouchEndedработают, но с задержкой и возможными "дрожаниями" при движении вдоль наклонных поверхностей (из-за дискретности вокселей).- Физические свойства (трение, упругость) наследуются от материала:
Material.Grassимеет низкое трение,Material.Ice— ещё ниже,Material.Sand— высокое сопротивление. - Вода (
Material.Water) — визуальный эффект с ограниченной глубиной (максимум 20 метров). Объекты под водой получают замедление черезHumanoid.WalkSpeed, но потоков, течений или плавучести нет.
Важно: ландшафт не участвует в динамической физике — его нельзя толкнуть, разрушить или переместить физическим воздействием. Любые изменения должны инициироваться программно или через инструменты редактора.
Редактирование
В Studio ландшафт редактируется через панель Terrain, доступную при выделении объекта Terrain в Workspace. Основные инструменты:
- Raise/Lower — изменение высоты поверхности;
- Smooth — сглаживание рельефа;
- Paint — смена материала;
- Fill — заливка объёма (например, создание куба земли);
- Erase — удаление вокселей (создание пустоты, пещер);
- Region Paste — копирование фрагментов между местами или играми.
Программное управление осуществляется через API объекта Terrain, доступного как workspace.Terrain. Ключевые методы:
-
:FillBlock(region: Region3, material: Enum.Material)
Заполняет указанный куб материалом.Region3должен быть выровнен по воксельной сетке (координаты кратны 2). -
:FillWedge(region: Region3, material: Enum.Material)
Заполняет клин (полезно для склонов). -
:ReadVoxels(region: Region3, resolution: number)
Возвращает трёхмерный массив данных вокселей в заданной области.resolution= 4 — стандартное разрешение (1 воксель = 1 элемент массива). -
:WriteVoxels(region — Region3, resolution: number, Данные: VoxelData)
Записывает данные в ландшафт. Используется для процедурной генерации (например, шум Перлина → высота → материал). -
:PlaceAsync(region: Region3, instances: {Instance})
Встраивает стандартныеPart,MeshPartв ландшафт (например, камни на поверхности), сохраняя их физику и скрипты.
Ограничения API:
- Все операции синхронные и блокирующие — при работе с большими областями (> 1 млн вокселей) возможны фризы.
- Максимальная область в одном вызове — 2048 × 2048 × 256 вокселей.
- Изменения мгновенно реплицируются всем клиентам (в отличие от
Part, где можно скрыть промежуточные состояния).
Производительность и масштабируемость
Ландшафт оптимизирован для статических или медленно меняющихся сцен. Его преимущества проявляются при площади поверхности свыше 500 × 500 метров:
- 1 чанк = 1 draw call для рендера, 1 физический объект для движка;
- в то время как сборка из
Partтого же размера потребовала бы тысяч draw call’ов и объектов физики.
Однако у ландшафта есть узкие места:
- Изменение в реальном времени (например, копание тоннелей игроками) требует частых вызовов
:FillBlock(), что создаёт нагрузку на CPU и сеть (из-за репликации). - LOD (Level of Detail) для ландшафта отсутствует — чанки всегда рендерятся в полном разрешении.
- Ограничения на материалы: нельзя использовать кастомные текстуры напрямую — только через
Terrain:DecorateAsync()с предварительно загруженными ассетами.
Для гибридных решений рекомендуется:
- использовать
Terrainдля базового рельефа (горы, реки); - добавлять детализацию через
Part/MeshPart(камни, деревья, здания); - избегать частого изменения вокселей — кэшировать операции, применять изменения пакетно.
Совместимость и ограничения платформы
- Ландшафт поддерживается на всех платформах, включая мобильные устройства и консоли.
- В мобильных сборках используется упрощённый шейдер рендера — детали текстур и тени могут быть снижены.
- Максимальная высота по оси Y — 500 метров (вокселей выше
y = 250не существует); максимальная глубина — −200 метров. - Невозможно создать "плавающие" острова без опоры — воксели в воздухе удаляются автоматически, если не поддерживаются снизу (настройка
workspace.Terrain.AutoStretchFill).
Когда использовать ландшафт, а когда — Part
Выбор обусловлен задачей, а не предпочтениями:
-
Используйте
Terrain, если:- требуется непрерывный рельеф (горы, долины, пещеры);
- карта превышает 300 × 300 метров;
- важна производительность на слабых устройствах;
- допустимы приблизительные коллизии.
-
Используйте
Part/MeshPart, если:- нужна точная геометрия (архитектура, интерьеры);
- важны детальные коллизии (платформер, паркур);
- объекты должны быть динамическими (движущиеся платформы, разрушаемые стены);
- требуется кастомная текстура на отдельном элементе.
Гибридный подход (ландшафт + декор из Part) — стандартная практика в профессиональных проектах.
Добавление, удаление, изменение объектов
Через интерфейс (Studio)
- Добавить:
Home → Part/Model/Script. - Удалить: выделить → Delete / ПКМ → Delete.
- Изменить свойство: выделить →
Properties→ ввести значение.
Через код
| Действие | Код |
|---|---|
| Создать | local p = Instance.new("Part") |
| Разместить | p.Parent = workspace |
| Изменить свойство | p.Size = Vector3.new(5, 1, 5) |
| Найти по имени | workspace:FindFirstChild("Ground") |
| Найти по пути | game.Players.Player1.PlayerGui.ScoreLabel |
| Удалить | p:Destroy() |
✅
:Destroy()предпочтительнееp.Parent = nil, потому что:
— отключает события;
— освобождает память;
— не оставляет "висячих" ссылок.
Монетизация
Что такое Robux?
- Валюта платформы Roblox.
- Приобретается за реальные деньги.
- Разработчик не управляет балансом Robux игрока — только запрашивает покупку.
Как работает покупка за Robux?
sequenceDiagram
participant Player
participant Client
participant RobloxServer
participant GameServer
Player->>Client: Нажимает “Купить”
Client->>RobloxServer: PromptPurchase(productId)
RobloxServer->>Player: Окно оплаты (Roblox UI)
Player->>RobloxServer: Подтверждает платёж
RobloxServer->>GameServer: PromptPurchaseFinished(player, productId, true)
GameServer->>GameServer: Валидация и выдача предмета
GameServer->>Client: FireClient("PurchaseSuccess")
Client->>Player: Показывает результат
⚠️ Запрещено:
- Запрашивать платёж из
LocalScriptбез подтверждения черезPromptPurchase.- Выдавать предмет до получения
PromptPurchaseFinished.- Использовать
RemoteEventдля имитации покупки за Robux.
Пошаговый магазин на внутриигровых монетах и интеграция с Developer Products — в статье Внутриигровая экономика Roblox.
Архитектура проекта в Roblox Studio
Структура сцены
Каждый проект в Roblox Studio строится вокруг понятия места (Place) — файла, содержащего всю информацию об игровом мире — объекты, скрипты, настройки, ресурсы. Проект хранится в виде .rbxl или .rbxlx, что позволяет сохранять состояние сцены между сессиями редактирования.
Центральным элементом редактора является окно Explorer, отображающее иерархию всех объектов в текущем месте. Объекты организованы в древовидную структуру, где каждый узел может содержать дочерние элементы. Это позволяет группировать связанные компоненты, например, помещая все элементы игровой среды в папку Game Environment.
Пример:
Workspace
├── Game Environment
│ ├── Ground
│ ├── Obstacle
│ └── Platforms
├── StarterGui
└── ReplicatedStorage
Такая организация способствует поддержке чистоты кода и упрощает навигацию по проекту.
Основные контейнеры
- Workspace — корневой контейнер для всех видимых объектов в игровом мире — персонажи, платформы, препятствия.
- StarterGui — содержит элементы графического интерфейса (GUI), которые автоматически клонируются для каждого игрока при подключении.
- ReplicatedStorage — используется для хранения данных и объектов, доступных как на сервере, так и на клиенте.
- ServerScriptService — место для размещения серверных скриптов, управляющих логикой игры.
- Lighting — хранит параметры освещения, времени суток, тумана.
- Players — содержит данные о каждом игроке, включая его персонажа (
Character) и инвентарь.
Правильное использование этих контейнеров обеспечивает предсказуемое поведение объектов и корректную работу системы репликации.
Интерфейс Roblox Studio
Основные панели
Интерфейс Roblox Studio состоит из нескольких ключевых компонентов:
- Главная область (Viewport) — трёхмерное пространство, в котором происходит редактирование сцены.
- Панель инструментов (Toolbar) — расположена сверху, содержит инструменты для выбора, перемещения, изменения размеров объектов, а также запуска и остановки игры.
- Explorer — дерево объектов сцены, позволяющее добавлять, удалять и переупорядочивать элементы.
- Properties — отображает и позволяет изменять свойства выбранного объекта (размер, положение, цвет, прозрачность, коллизии и др.).
- Toolbox — библиотека готовых моделей, эффектов, скриптов и материалов.
- Output — консоль, выводящая сообщения от скриптов, ошибки, предупреждения и результаты выполнения команд.
Доступ к этим панелям осуществляется через меню View. Например, чтобы открыть консоль, необходимо выбрать View → Output.
Управление камерой
Управление вьюпортом аналогично другим 3D-редакторам:
- W, A, S, D — перемещение камеры.
- Колесо мыши — масштабирование.
- ПКМ + движение мыши — вращение камеры.
- Зажатие Q — орбитальное вращение вокруг центра.
Эти действия позволяют эффективно навигировать по сцене и точно размещать объекты.
Создание игровой среды
Базовые объекты — Part и BasePart
Все физические объекты в Roblox основаны на классе BasePart. Наиболее часто используется Part — параллелепипед, который можно масштабировать, вращать и раскрашивать.
Пример — создание земли
- В Explorer создайте папку
Game Environment. - Внутри неё добавьте новый
Partи назовите егоGround. - Используйте инструмент Resize для изменения размеров (например, 100×1×100).
- В Properties установите материал:
GrassилиConcrete. - При необходимости измените цвет через свойство
Color.
Работа с ландшафтом
Для создания естественного рельефа используется встроенный Terrain Editor.
Шаги
- Перейдите в меню
View → Terrain. - Выберите инструмент:
- Raise/Lower — изменение высоты поверхности.
- Smooth — сглаживание переходов.
- Paint — нанесение текстур (трава, песок, камень).
- Для добавления воды используйте режим Water, задав уровень и форму водоёма.
Ландшафт поддерживает многослойную текстуризацию и может быть экспортирован/импортирован для повторного использования.
Программирование на Lua в Roblox
Основы Lua
Roblox использует диалект Lua 5.1 с расширениями API, специфичными для платформы. Язык интерпретируется в реальном времени, что позволяет быстро тестировать изменения.
Типичный скрипт в Roblox:
local part = script.Parent
part.Touched:Connect(function(hit)
print("Объект задет: " .. hit.Name)
end)
Типы скриптов
- Script — выполняется на сервере. Используется для логики, требующей надёжности (например, проверка победы).
- LocalScript — выполняется на клиенте. Подходит для GUI, анимаций, ввода с клавиатуры.
- ModuleScript — содержит переиспользуемый код, импортируется через
require().
Обработка событий
События — основа реактивной архитектуры в Roblox. Большинство объектов имеют встроенные события:
-- Обработка клика по кнопке
script.Parent.MouseButton1Click:Connect(function(player)
print(player.Name .. " нажал кнопку")
end)
-- Изменение положения персонажа
game.Players.PlayerAdded:Connect(function(player)
player.CharacterAdded:Connect(function(char)
char:WaitForChild("HumanoidRootPart").Touched:Connect(function(hit)
-- Логика столкновения
end)
end)
end)
Асинхронность и задержки
Для отложенного выполнения используется task.wait() (аналог delay()):
task.spawn(function()
task.wait(5)
print("Прошло 5 секунд")
end)
Физика и коллизии
Система физики
Roblox включает встроенную физическую модель, основанную на двигателе NVIDIA PhysX. Все BasePart по умолчанию участвуют в симуляции, если не отключено свойство Anchored.
Ключевые свойства
Anchored— фиксирует объект в пространстве.CanCollide— определяет, будет ли объект сталкиваться с другими.Massless— игнорирует влияние массы при столкновениях.Gravity— включение/выключение гравитации для части.
PhysicsService
Для точного управления взаимодействием между объектами используется PhysicsService, позволяющий назначать объекты в разные группы коллизий:
local PhysicsService = game:GetService("PhysicsService")
PhysicsService:CreateCollisionGroup("Player")
PhysicsService:CollisionGroupSetCollidable("Player", "Obstacle", false)
Графический интерфейс (GUI)
Элементы интерфейса
GUI создаётся с помощью объектов внутри StarterGui. Основные типы:
ScreenGui— контейнер для 2D-интерфейса.Frame— прямоугольная область.TextButton,ImageButton— кнопки.TextLabel,TextBox— отображение и ввод текста.
Пример — кнопка старта
local screenGui = Instance.new("ScreenGui")
local button = Instance.new("TextButton")
button.Size = UDim2.new(0, 150, 0, 50)
button.Position = UDim2.new(0.5, -75, 0.5, -25)
button.Text = "Start Game"
button.Parent = screenGui
button.MouseButton1Click:Connect(function()
print("Игра начата!")
end)
screenGui.Parent = game.StarterGui
Координатная система GUI
Используется относительная система координат через UDim2, где:
Scale— часть от размера родителя (0–1).Offset— фиксированный сдвиг в пикселях.
Система уровней и прогресса
Реализация уровней
Система уровней может быть построена на основе накопления опыта (XP). При достижении порога — повышение уровня.
Хранение данных
Для сохранения прогресса используется DataStore:
local DataStoreService = game:GetService("DataStoreService")
local playerStore = DataStoreService:GetDataStore("PlayerData")
-- Сохранение
playerStore:SetAsync("Player_"..player.UserId, playerData)
-- Загрузка
local data = playerStore:GetAsync("Player_"..player.UserId)
Важно: DataStore имеет ограничения на частоту вызовов и требует обработки ошибок.
Мультиплеер и репликация
Сервер-клиент архитектура
Roblox использует авторитетный сервер:
- Все критические решения принимаются на сервере.
- Клиенты получают только визуальные и интерфейсные обновления.
RemoteEvents и RemoteFunctions
Для передачи данных между клиентом и сервером используются:
RemoteEvent— односторонняя отправка (например, клик по кнопке).RemoteFunction— запрос с ответом (например, получение данных игрока).
Пример
ServerScript
local remoteEvent = game.ReplicatedStorage.RemoteEvent
remoteEvent.OnServerEvent:Connect(function(player, action)
if action == "Jump" then
-- Выполнить действие на сервере
end
end)
LocalScript
remoteEvent:FireServer("Jump")
Анимации и моделирование
Создание анимаций
Анимации создаются в Animation Editor:
- Выделите персонажа или часть.
- Откройте
Window → Animation Editor. - Добавьте ключевые кадры для свойств (позиция, вращение).
- Экспортируйте анимацию в
AnimationController.
Скриптовое управление анимациями
local animator = character:WaitForChild("Animator")
local animation = Instance.new("Animation")
animation.AnimationId = "rbxassetid://123456789"
local track = animator:LoadAnimation(animation)
track:Play()
Тестирование и отладка
Консоль вывода (Output)
Окно Output показывает:
- Результаты
print(). - Ошибки выполнения скриптов.
- Предупреждения о производительности.
Инструменты отладки
Debug → Stats— мониторинг FPS, памяти, количества частей.Test → Play— запуск локальной сессии.TeleportService— тестирование перехода между местами.
Публикация проекта
Подготовка к публикации
- Убедитесь, что все скрипты протестированы.
- Проверьте права доступа к ассетам.
- Настройте настройки места — название, описание, теги.
- Установите уровень приватности (публичный, друзья, приватный).
Процесс публикации
- Нажмите Publish to Roblox.
- Выберите существующее место или создайте новое.
- Загрузите изменения.
- Получите ссылку для распространения.
После публикации игра становится доступна другим пользователям. Возможна интеграция с системой monetization (продажа предметов за Robux).
Методология разработки
Итеративный подход
- Прототипирование — создание минимального воспроизводимого примера (MVP).
- Тестирование — проверка на разных устройствах и соединениях.
- Оптимизация — снижение количества частей, использование
MeshPartвместо сложных конструкций. - Сбор обратной связи — анализ действий игроков, исправление багов.
Рекомендации
- Начинайте с простых проектов (платформеры, мини-игры).
- Используйте готовые ресурсы из Toolbox.
- Документируйте код и структуру проекта.
- Регулярно сохраняйте и коммитьте изменения (при использовании внешних систем контроля версий).