# Архитектура студии Рублокса Как редактор превращает действия мыши в воксельный мир, GLB-модели, скрипты на JS — и сохраняет в JSON для запуска в плеере. Чтение ~10 минут. ## Общий поток (открытие проекта) ``` URL = /edit/ │ ▼ AuthProvider читает JWT из localStorage │ ▼ KubikonEditor.jsx (~3500 строк) главный контейнер, useParams().id = projectId │ ├── GET /api-storys/kubikon3d/projects/{id} → project_data (JSON) │ ▼ BabylonScene.create() создание Babylon engine, scene, lights, skybox │ ▼ GameRuntime.loadProject() парсит project_data, спавнит: │ - BlockManager.placeBlock() × N (воксели) │ - ModelManager.spawnModel() × N (GLB Kenney) │ - DecoManager.placeDeco() × N (трава, камни) │ - PrimitiveManager.add() × N (box/sphere/cylinder) │ - PlayerController.spawn() (камера-«редактор») │ ▼ ОСНОВНОЙ UI на сцене: │ - TopRibbon (тулбар) │ - HierarchyPanel (дерево объектов слева) │ - InspectorPanel (свойства справа) │ - TerrainPanel (кисти ландшафта) │ - ScriptEditor (Monaco) │ - ToolboxModal (выбор моделей) │ - GuiOverlay/Hotbar (HUD режима игры) │ ▼ ЦИКЛ РЕДАКТИРОВАНИЯ юзер: - кликает мышью → SelectionManager - drag-and-drop → GizmoManager - переключает кисти → TerrainManager - пишет скрипт → ScriptEditor + Babylon-эмулятор - жмёт «Сохранить» → POST /kubikon3d/projects/:id - жмёт «Тест» → embedded preview-player ``` ## Главные модули редактора ### `editor/KubikonEditor.jsx` (3452 строки) Корневой компонент. Хранит ссылки на scene, engine, runtime, managers. Обрабатывает горячие клавиши, undo/redo через `HistoryManager`. Обвязывает все панели вокруг ``. ### `editor/engine/` (66 файлов, ~28к строк) Самодостаточный Babylon-движок. Точно такой же как в плеере, но с дополнительными редакторскими функциями: | Менеджер | Файл | Что делает | |---|---|---| | `BabylonScene` | `BabylonScene.js` | Wrapper над Engine+Scene, освещение | | `GameRuntime` | `GameRuntime.js` | Оркестратор: loadProject/start/pause/dispose | | `PlayerController` | `PlayerController.js` | R15-персонаж, камеры FPV/TPV, контролы | | `BlockManager` | `BlockManager.js` | Воксельные блоки uint16-сетка | | `TerrainVoxelBuilder` | `terrain/*` | Greedy meshing для чанков | | `ModelManager` | `ModelManager.js` | Загрузка GLB через AssetContainer + кеш | | `DecoManager` | `DecoManager.js` | ThinInstances для тысяч пропсов | | `PrimitiveManager` | `PrimitiveManager.js` | Box/sphere/cylinder примитивы | | `SelectionManager` | `SelectionManager.js` | Клик → выделение → highlight | | `GizmoManager` | `GizmoManager.js` | Стрелки перемещения/вращения/масштаба | | `HistoryManager` | `HistoryManager.js` | Undo/redo стек | | `ScriptSandbox` | `scripts/ScriptSandbox.js` | Запуск user JS в Web Worker | | `MultiplayerSync` | `multiplayer/MultiplayerSync.js` | Colyseus 0.16 клиент | | `Audio/Weapon/Zombie/Npc/Dynamics` | `*Manager.js` | Игровые менеджеры | ### `editor/engine/scripts/ScriptSandbox*.js` Безопасный JS-runtime для пользовательских скриптов. Запускается в Web Worker, без доступа к window/document/fetch. Через postMessage-мост даёт API: ```js game.onTick((dt) => { ... }); game.onKey('space', () => { player.jump(); }); scene.findOne('Cube1').rotateY(0.1); ui.set({ score: 42 }); ``` ### `editor/engine/types/` TypeScript-определения для Monaco-автокомплита. Когда юзер пишет скрипт — IDE показывает все доступные методы. ## UI-панели ``` +----------------------------------------------------------+ | TopRibbon: Файл | Правка | Тест | Опубликовать | Помощь | +--------+-----------------------------------+-------------+ | Hierar | | Inspector | | chyPan | | Panel | | el | | (свойства | | | | выделен- | | Дерево | | ного) | | объек- | | | | тов | | | | | | | | +-----------------------------------+ | | | Hotbar / TerrainPanel (внизу) | +--------+-------------------------------------------------+ ``` ## Скриптовый редактор (Monaco) `editor/ScriptEditor.jsx` запускает Monaco (~5МБ vendor chunk, lazy-loaded). Подгружает .d.ts из `engine/types/` для автокомплита. При сохранении скрипт идёт в `ScriptSandbox`, который запускает Worker и шлёт результат обратно. ## Поток данных при сохранении ``` Save кнопка → собрать снимок: - blocks[] (uint16 array → RLE-compress → base64) - models[] {id, type, x, y, z, rx, ry, rz} - primitives[] {id, type, color, size} - deco[] {type, instances[]} - script.code (текст из Monaco) - settings {sky, fog, multiplayer, isGd} - spawnPoint {x, y, z} → JSON.stringify → PUT /api-storys/kubikon3d/projects/ { project_data: "" } ``` ## Чего НЕТ в студии - **Само воспроизведение игры в проде** — это [Плеер](https://git.rublox.pro/rublox/player) (отдельный домен `player.rublox.pro`). В студии есть `preview-player/` для теста «прямо здесь». - **Лента / поиск опубликованных игр** — на главном сайте `rublox.pro/app`. - **Админка модерации** — в `team.rublox.pro/moderator/*` (приватный фронт команды). ## С чего начать новую фичу | Что хочешь добавить | Начни здесь | |---|---| | Новый тип блока | `editor/engine/CONST/blockTypes.js` | | Новая декор-фабрика | `admin-preview/gd<тип>/` (фабрика) + регистрация в DecoManager | | Новое API скриптов | `editor/engine/scripts/ScriptSandboxAPI.js` + .d.ts в `engine/types/` | | Новая GD-механика | `editor/engine/Gd<тип>.js` + регистрация в `GdGameModeRegistry.js` | | Новый UI-блок инспектора | `editor/InspectorPanel.jsx` | | Новая кисть ландшафта | `editor/TerrainPanel.jsx` + `TerrainManager.brush*` | ## Производительность Главные узкие места (что смотреть первым делом): 1. **`game.ui.set()` каждый кадр** — React setState 60Hz убивает FPS. Дросселируй 250мс + diff. 2. **`scene.findOne()` на старте скрипта** — sceneSnapshot приходит через rAF. Звать в `onTick` или setTimeout(0). 3. **`blockMaterialDirtyMechanism=true`** в Babylon — НЕ включать (ломает рендер новых мешей). 4. **GLB через `SceneLoader` вместо `AssetContainer`** — течёт материалами. 5. **Monaco-instance создаётся повторно при ремаунте** — закешируй ссылку. 6. **Воксельный rebuild на каждый блок** — батчить через requestIdleCallback. ## Связь со студией / плеером / минкой - **Бэкенд один на всех** — `api-storys` микросервис (см. `disaster-recovery/` в приватном репо мейнтейнера). - **Авторизация общая** — JWT с rublox.pro и player.rublox.pro работают здесь. - **API-контракт описан в `api/Kubikon3DService.js`** — все эндпоинты `/kubikon3d/*`. ## Лицензионные заметки для контрибьюторов Контрибьютя, ты соглашаешься лицензировать свои изменения под AGPL-3.0 И предоставляешь мейнтейнеру неисключительную безотзывную лицензию на сублицензирование (см. [CLA.md](./CLA.md)). Это нужно чтобы проект мог продавать коммерческие лицензии корпорациям.