МИН 7ab66fc4c5 feat(09): сочные круглые studs (v4) + color-пикер окрашиваемых блоков
- Текстура studs v4: круглые кружки с усиленным объёмом (normal strength 4.0,
  запечённый блик/тень) + контактная тень от каждого кружка. Фон 0.97 — цвет
  остаётся сочным. emissive 45% от цвета на примитивах (Roblox-look).
- Версионные имена файлов (studs_v4_*) — обход browser-кэша Babylon.
- Color-пикер блоков: в палитре при выборе окрашиваемого блока (studs-block)
  под категориями появляется ряд из 8 лего-цветов + input «свой цвет».
  BabylonScene.setActiveBlockColor → addBlock(...,color) при постановке.
- DEV-хук ?dev=<имя> (localhost): грузит /dev-<имя>.json в редактор для
  локального теста без БД (на проде неактивен).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-31 12:22:03 +03:00
..

Движок студии Рублокс

Это сердце studio.rublox.pro. Тут живёт всё: рендер Babylon-сцены, физика, скрипты пользователей, мультиплеер, ландшафт, ассет-менеджмент.

Для контрибьютора, который впервые видит этот код — читай этот README сверху вниз, дальше доки по подпапкам.

1. Слои движка

Снизу вверх:

┌─────────────────────────────────────────────────────┐
│  React UI (panels, тулбары)                         │
├─────────────────────────────────────────────────────┤
│  GameRuntime              ← оркестратор «игрового   │
│  PlayerController           режима» (когда игрок    │
│  ScriptSandbox(Worker)      жмёт Play в редакторе)  │
├─────────────────────────────────────────────────────┤
│  Менеджеры объектов сцены                           │
│    BlockManager     — блоки 1×1×1                   │
│    PrimitiveManager — сферы/цилиндры/конусы         │
│    ModelManager     — GLB-модели из библиотеки      │
│    UserModelManager — модели юзера (CAD-редактор)   │
│    NpcManager       — NPC и враги                   │
│    DecoManager      — трава/цветы/деревья           │
├─────────────────────────────────────────────────────┤
│  Системы поверх сцены                               │
│    PhysicsWorld     — AABB + интеграция Хейуна      │
│    SelectionManager — клик → подсветка              │
│    GizmoController  — translate/rotate/scale gizmos │
│    HistoryManager   — Ctrl+Z / Ctrl+Y               │
│    MultiplayerSync  — Colyseus state-sync           │
├─────────────────────────────────────────────────────┤
│  Низ: BabylonScene = Engine + Scene + Camera        │
│        TerrainManager + voxel/                      │
└─────────────────────────────────────────────────────┘

BabylonScene.js — точка входа. Создаёт Engine, Scene, UniversalCamera с Roblox-style контролами (ПКМ + WASD-полёт), подключает всех менеджеров.

2. Как игрок ставит блок (полный путь)

Это самый частый кейс. Понимаешь его → понимаешь как работают все остальные менеджеры.

1. User жмёт ЛКМ в редакторе
   ↓
2. BabylonScene.onCanvasPointerDown
   - делает ray из камеры через курсор
   - scene.pickWithRay()  → выясняет, в какую грань какого меша попали
   ↓
3. Если активен инструмент «Кисть блоков»:
   BlockManager.placeBlock(pos, blockType)
   - вычисляет grid-aligned позицию (округление к 1×1×1)
   - создаёт InstancedMesh от шаблонного меша блока (один draw call на тип)
   - регистрирует в внутренней карте: pos.x|y|z → mesh
   - HistoryManager.push({ action: 'place', pos, type })
   ↓
4. Если активен Multiplayer:
   MultiplayerSync.sendBlockPlaced({ pos, type })
   - летит в Colyseus-комнату
   - сервер броадкастит остальным игрокам
   - у них BlockManager.placeBlock() вызывается снова, но без HistoryManager.push

3. Менеджеры — зачем их столько

Каждый менеджер владеет одной категорией объектов. Менеджеры не знают друг о друге — все общаются через BabylonScene (он же scene-объект, проброшен в каждый менеджер при init).

Менеджер Что хранит Ключевой метод
BlockManager блоки grid-aligned placeBlock(pos, type) / removeBlock(pos)
PrimitiveManager сферы/цилиндры/конусы любого размера addPrimitive({ type, pos, size, color })
ModelManager GLB-модели (мечи, деревья, машины) addModel({ modelId, pos, rotation, scaling })
UserModelManager модели созданные юзером в встроенном CAD addUserModel({ userModelId, pos })
NpcManager NPC и враги (zombies — отдельный ZombieManager) spawnNpc({ type, pos })
DecoManager мелкие воксельные декорации (трава 0.05м) paintDeco(pos, brushSize, model)
TerrainManager гладкий ландшафт (legacy, voxel-режим) paintVoxel(pos, material)
VoxelWorld + VoxelRenderer новый chunks-based воксельный движок setVoxel(x,y,z, type)
FolderManager дерево объектов (как Workspace в Roblox) createFolder(name, parentId)
GuiManager UI игры (HUD, меню) setUiElement(id, props)
InventoryManager инвентарь игрока (хотбар, рюкзак) addItem(itemId, count)
WeaponSystem оружие, выстрелы, перезарядка equip(weaponId) / fire()
ScriptSandbox JS-скрипты пользователей (см. §5) runScript(scriptCode, gameApi)

4. Подпапки

  • terrain/ — legacy воксельный ландшафт (TerrainManager). Кисти, материалы, plant-кисти. Соединён с BabylonScene через TerrainManager.js в корне.
  • voxel/ — новый chunks-based движок (16×16×16 чанки + greedy meshing). См. RUBLOX_VOXEL_ENGINE_PLAN.md. Пока работает параллельно с legacy TerrainManager как shadow-копия для замеров.
  • robloxterrain/ — Roblox-style визуальные эффекты для гладкого ландшафта (vertex AO, slope-darken, distance fog).
  • types/ — общие type-определения и enum'ы (BlockTypes, PrimitiveTypes).

5. Скрипты пользователей (sandbox)

ScriptSandbox.js + ScriptSandboxWorker.js — самая опасная часть движка. Тут юзеры пишут JS, который запускается в их браузере при игре.

Каждый скрипт запускается в отдельном Worker (изоляция: нет доступа к DOM, window, fetch). Общение со сценой — только через postMessage и проксированное API game.*:

// что доступно скрипту:
game.player.damage(10)
game.player.kill()
game.scene.spawn('zombie', { x: 0, y: 0, z: 5 })
game.ui.set('score', 42)
game.broadcast('event', payload)  // другим скриптам
game.onMessage(handler)
game.onTick(handler)              // ~60 раз/сек

Полный список API: reference_kubikon_scripting_api.md (приватный, не публикуется в opensource).

Не звать game.ui.set в onTick без throttle! React setState на 60Hz убивает FPS. Дросселировать через 250мс. [feedback_kubikon_ui_set_throttle].

6. Что НЕ трогать (опасные оптимизации)

После пары инцидентов задокументировано:

  • scene.blockMaterialDirtyMechanism = true — ломает рендер новых мешей (трейсеры пуль, debris). Babylon не пересчитывает uniforms для новых материалов.
  • scene.createOrUpdateSelectionOctree() в hot path — O(N²) каждый кадр, лагает на 1000+ мешей.
  • scene.render() вручную — Babylon сам в RequestAnimationFrame, дублирование = double render.
  • setInterval(..., 16) для игровой логики — используй scene.onBeforeRenderObservable.add(fn), тогда привязано к рендер-циклу.

Подробнее в docs/TUTORIAL_DEBUG_BABYLON.md.

7. Game Runtime — особый режим

GameRuntime.js — когда юзер жмёт «Play» в редакторе. Это не отдельный код-путь, а набор флагов на тех же менеджерах:

  • PlayerController.spawn() — создаёт игрового персонажа (R15-скелет) на spawn-блоке
  • PhysicsWorld.enable() — включается AABB-симуляция (в редакторе отключена)
  • ScriptSandbox.startAll() — запускает все скрипты пользователей
  • MultiplayerSync.join() — если игра опубликована, коннектится к Colyseus-комнате

Когда юзер жмёт «Stop» — всё откатывается, состояние из HistoryManager восстанавливается.

8. Производительность — ориентиры

Объект Сколько ок Сколько начинает лагать
Блоки (InstancedMesh) 50 000 200 000+
Примитивы (StandardMesh) 500 2000+
GLB-модели 200 500+ (зависит от вершин)
NPC 50 100+ (физика дорогая)
Активных скриптов 30 100+ (каждый = Worker = поток)
Частицы 5000 20 000+

FPS-цель: 60 FPS на средних ноутбуках 2020+, 30 FPS на школьных машинках 2015+.

9. Что почитать дальше

Вопросы — в канал #разработка на https://team.rublox.pro.