# Архитектура плеера Рублокса Как 3D-игра загружается, рендерится и синхронизируется. Чтение ~5 минут. ## Общий поток ``` URL = / например /265 │ ▼ PlayerAuthProvider проверяет JWT в localStorage["player_jwt"] │ ИЛИ обменивает URL #ticket=... на JWT ▼ useAuth().isAuthenticated │ ▼ KubikonPlayer.jsx главный контейнер, читает {projectId} из useParams │ ├── GET /api-storys/kubikon3d/projects/{id} → project_data (JSON) │ ▼ BabylonScene.create() создание движка Babylon, сцены, света, неба │ ▼ GameRuntime.loadProject() парсинг project_data, инстанциация всего: │ - BlockManager.placeBlock() × N (Minecraft-блоки) │ - ModelManager.spawnModel() × N (Kenney GLB) │ - DecoManager.placeDeco() × N (декор ландшафта) │ - PrimitiveManager.add() × N (кубы/сферы/цилиндры) │ - PlayerController.spawn() (R15-персонаж + камера) │ ▼ ScriptSandboxWorker пользовательские JS-скрипты в Web Worker-песочнице. │ Доступный API: game.player, scene, ui, broadcast. │ ▼ MultiplayerSync опционально. Колyseus-комната, синк позиций. │ ▼ ЦИКЛ РЕНДЕРА (60 fps) scene.render() каждый кадр ``` ## Ключевые модули ### `engine/BabylonScene.js` Обёртка над `Engine` + `Scene` из Babylon. Создаёт освещение (HemisphericLight + DirectionalLight + теневой генератор), скайбокс, туман. Единый источник правды для ссылки на `scene`. ### `engine/GameRuntime.js` Оркестратор. Читает `project_data` (JSON, сохранённый редактором студии) и направляет каждый элемент в соответствующий менеджер. Lifecycle-хуки: `loadProject()`, `start()`, `pause()`, `dispose()`. Также обрабатывает «external URL» резолвинг (`_resolveExternalUrl`) — скрипты игры могут вызвать `game.openUrl('/kubikon/play/12')`, и URL корректно резолвится в сам плеер или на главный сайт. ### `engine/PlayerController.js` R15-персонаж (15-костный Mixamo-rig). Камеры от первого и третьего лица. Управление WASD/тач. Прыжки, гравитация, столкновения с воксельным гридом + AABB-моделями. Спавн/респавн. Спец-режимы для GD: `setAutoRun(true)`, `setShipMode(true)` — используются гейммодами Geometry Dash (куб/корабль/волна/НЛО/мяч/паук). ### `engine/BlockManager.js` / `TerrainVoxelBuilder.js` Воксельный ландшафт. Блоки — uint16 type-id'ы в чанковых массивах. `rebuildChunk(chunkId)` делает greedy meshing → один меш на материал на чанк (~40-100× меньше draw call'ов чем наивно). ### `engine/ModelManager.js` Загрузка `.glb`-моделей Kenney (или загруженных дизайнерами GLB из `/api-storys/assets/rublox-designer/models/...`). Кеширует `AssetContainer` по `modelTypeId`, инстанциирует клоны при каждом спавне. ### `engine/DecoManager.js` Лёгкие декоративные пропсы (камни, растения, знаки). Использует `ThinInstanceCount` для огромного перфоманса (тысячи инстансов → один draw call на тип). ### `engine/scripts/ScriptSandboxWorker.js` Пользовательские JS-скрипты выполняются в отдельном Web Worker. Доступная API-поверхность (только чтение): ```js // В скрипте игрока: game.onTick((dt) => { ... }); game.onKey('space', () => { player.jump(); }); game.broadcast('event', payload); game.player.setHealth(100); scene.findOne('Cube1').rotateY(0.1); ui.set({ score: 42 }); ``` Скрипты **НЕ имеют** доступа к `window`, `document`, `fetch`, `localStorage`, сети. Запускаются изолированно в Worker'е через строгий postMessage-мост. ### `engine/multiplayer/MultiplayerSync.js` Colyseus 0.16 клиент. Подключается к комнате per `gameId` если мультиплеер включён. Синхронизирует позиции/повороты/анимации игроков на 20 Hz. Сервер по `VITE_REALTIME_WS`. ### `engine/gd/*` (модули Geometry Dash) 30+ классов реализующих GD-стиль 2D-автораннер-гейммоды в 3D-мире: - `GdCube.js` — обычный прыжок - `GdShip.js` — гравитация-флип, полёт - `GdWave.js` — синусоида-дэш - `GdBall.js` — флип-прыжок мячом - `GdUfo.js` — мульти-тап прыжки - `GdSpider.js` — мгновенный телепорт - + порталы, ускорители, шипы, финишные линии, трейлы, чекпойнт-музыка Фабрики для каждого (`GdSpikeFactory`, `GdPortalFactory`, `GdMusicFactory`...) живут в `AdminPreview/gd*/`. ## Поток данных (один кадр) ``` 1. ВВОД клавиатура/тач/геймпад → PlayerController.onInput() 2. ФИЗИКА гравитация + скорость + столкновения с блоками/моделями 3. СКРИПТЫ onTick(dt) колбэки пользовательских скриптов (в Worker'е, асинхронно) 4. МУЛЬТИПЛЕЕР читаем удалённые позиции, lerp других игроков 5. РЕНДЕР Babylon scene.render() → WebGL2 draw calls 6. UI React ре-рендерится только если вызвали game.ui.set() (throttle 250мс) ``` ## Чего НЕТ в плеере - **Редактирование** — это в [Студии](https://git.rublox.pro/rublox/studio). - **Лента игр / поиск / публикация** — это на главном сайте (`rublox.pro/app`). - **Авторизация-UI** — игроки приходят с JWT/ticket. Плеер только читает их. - **Админка / модерация** — вынесены в приватный репозиторий (`disaster-recovery/` для мейнтейнера). ## Узкие места по производительности (что смотреть первым делом если тормозит) 1. **`game.ui.set()` вызывается каждый кадр** — React setState 60Hz убивает FPS. Дросселируй до 250мс с diff-проверкой. 2. **`scene.findOne()` на старте** — `sceneSnapshot` приходит через rAF; ссылка будет null. Перенеси в `onTick` или `setTimeout(0)`. 3. **`blockMaterialDirtyMechanism=true` в Babylon** — НЕ включать. Ломает рендер новых мешей (debris, трейсеры). 4. **`createOrUpdateSelectionOctree()`** — то же. Ломает превью-призраки редактора. 5. **GLB-загрузка через `SceneLoader` вместо `AssetContainer`** — течёт материалами. Используй `AssetContainer` + `instantiateModelsToScene()`. ## С чего начать новую фичу | Что хочешь добавить | Начни здесь | |---|---| | Новый тип блока | `engine/CONST/blockTypes.js` + текстура в `public/kubikon-assets/blocks/` | | Новое API для скриптов | `engine/scripts/ScriptSandboxAPI.js` (добавь в `apiSurface`) | | Новый GD-гейммод | Скопируй `engine/gd/GdBall.js`, зарегистрируй в `GdGameModeRegistry.js` | | Новый HUD-виджет | `editor-shared/GameHud.jsx` | | Превью-роут для дизайнера | `AdminPreview/` (например `gdSkins/PreviewGdSkins.jsx`) | | Мультиплеер-событие | Colyseus-схема в `engine/multiplayer/schemas/` + хендлер в `MultiplayerSync.js` | ## Лицензионные заметки для контрибьюторов Контрибьютя, ты соглашаешься лицензировать свои изменения под AGPL-3.0 И предоставляешь мейнтейнеру неисключительную безотзывную лицензию на сублицензирование (см. [CLA.md](./CLA.md)). Это нужно чтобы проект мог продавать коммерческие лицензии корпорациям.