Open-source web player for Rublox games, dual-licensed under AGPL-3.0 + Commercial. Highlights: - Babylon.js 7 + React 18 + Vite 5 stack - Self-contained engine (~46k lines): BlockManager, ModelManager, PlayerController, ScriptSandboxWorker, MultiplayerSync, 30+ GD gamemodes - Configurable backend via VITE_API_BASE and friends — works against staging (dev-api.rublox.pro) out of the box - Standalone mode (VITE_STANDALONE=true) loads a bundled sample game for first-run without any backend - Full docs: README, ARCHITECTURE, CONTRIBUTING, SECURITY, CHANGELOG - Lint + format scaffolding (ESLint + Prettier + EditorConfig) - Legal: LICENSE (AGPL-3.0), LICENSE-COMMERCIAL.md, CLA.md, COPYRIGHT.md - Issue templates: bug_report, feature_request, security_disclosure Removed before public release: - frontend_deploy.py (contained production SSH credentials) - ~27 admin endpoints (kept in private repo) - Hard-coded internal URLs and IPs - All previous git history (clean repo init)
146 lines
6.5 KiB
Markdown
146 lines
6.5 KiB
Markdown
# Architecture — Rublox Player
|
||
|
||
How a 3D game is loaded, rendered, and synced. ~5 minutes read.
|
||
|
||
## Top-level flow
|
||
|
||
```
|
||
URL = /<gameId> e.g. /265
|
||
│
|
||
▼
|
||
PlayerAuthProvider checks JWT in localStorage["player_jwt"]
|
||
│ OR exchanges URL #ticket=... for JWT
|
||
▼
|
||
useAuth().isAuthenticated
|
||
│
|
||
▼
|
||
KubikonPlayer.jsx main container, gets {projectId} from useParams
|
||
│
|
||
├── GET /api-storys/kubikon3d/projects/{id} → project_data (JSON)
|
||
│
|
||
▼
|
||
BabylonScene.create() construct Babylon engine, scene, lights, skybox
|
||
│
|
||
▼
|
||
GameRuntime.loadProject() parse project_data, instantiate everything:
|
||
│ - BlockManager.placeBlock() × N (Minecraft-style blocks)
|
||
│ - ModelManager.spawnModel() × N (Kenney prop GLBs)
|
||
│ - DecoManager.placeDeco() × N (terrain decorations)
|
||
│ - PrimitiveManager.add() × N (cubes/spheres/cylinders)
|
||
│ - PlayerController.spawn() (R15 character + camera)
|
||
│
|
||
▼
|
||
ScriptSandboxWorker user JS scripts run in a Web Worker sandbox.
|
||
│ Exposed API: game.player, scene, ui, broadcast.
|
||
│
|
||
▼
|
||
MultiplayerSync optional. Colyseus room joins, syncs positions.
|
||
│
|
||
▼
|
||
RENDER LOOP (60 fps) scene.render() each frame
|
||
```
|
||
|
||
## Key modules
|
||
|
||
### `engine/BabylonScene.js`
|
||
|
||
Wraps Babylon `Engine` + `Scene`. Creates lighting (HemisphericLight + DirectionalLight + shadow generator), skybox, fog. Single source of truth for `scene` reference.
|
||
|
||
### `engine/GameRuntime.js`
|
||
|
||
Orchestrator. Reads `project_data` (the JSON saved by the studio editor) and dispatches each item to the right manager. Has lifecycle hooks: `loadProject()`, `start()`, `pause()`, `dispose()`.
|
||
|
||
Also handles "external URL" resolution (`_resolveExternalUrl`) — game scripts can call `game.openUrl('/kubikon/play/12')` and it correctly resolves to the player itself or the main Rublox site.
|
||
|
||
### `engine/PlayerController.js`
|
||
|
||
R15 character (15-bone Mixamo rig). First-person and third-person cameras. WASD/touch input. Jumping, gravity, collision against block grid + AABB models. Spawning/respawning.
|
||
|
||
Special GD modes: `setAutoRun(true)`, `setShipMode(true)` — used by Geometry Dash gamemodes (cube/ship/wave/UFO/ball/spider).
|
||
|
||
### `engine/BlockManager.js` / `TerrainVoxelBuilder.js`
|
||
|
||
Voxel terrain. Blocks are uint16 type IDs in chunked arrays. `rebuildChunk(chunkId)` does greedy meshing → single mesh per material per chunk (~40-100× fewer draw calls than naive).
|
||
|
||
### `engine/ModelManager.js`
|
||
|
||
Loads `.glb` Kenney models (or designer-uploaded GLBs from `/api-storys/assets/rublox-designer/models/...`). Caches `AssetContainer` per `modelTypeId`, instantiates clones per spawn.
|
||
|
||
### `engine/DecoManager.js`
|
||
|
||
Lightweight decorative props (rocks, plants, signs). Uses `ThinInstanceCount` for massive performance (thousands of instances → single draw call per type).
|
||
|
||
### `engine/scripts/ScriptSandboxWorker.js`
|
||
|
||
User-uploaded JS scripts run in a separate Web Worker. Exposed API surface (read-only):
|
||
|
||
```js
|
||
// In a user script:
|
||
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 });
|
||
```
|
||
|
||
Scripts CANNOT access `window`, `document`, `fetch`, `localStorage`, network. They run isolated in a Worker with a strict postMessage bridge.
|
||
|
||
### `engine/multiplayer/MultiplayerSync.js`
|
||
|
||
Colyseus 0.16 client. Joins a room per `gameId` if multiplayer is enabled. Syncs player positions/rotations/animations at 20 Hz. Server is at `VITE_REALTIME_WS`.
|
||
|
||
### `engine/gd/*` (Geometry Dash modules)
|
||
|
||
30+ classes implementing GD-style 2D autorunner gamemodes within a 3D world:
|
||
- `GdCube.js` — basic jump
|
||
- `GdShip.js` — gravity-flip flight
|
||
- `GdWave.js` — sine-wave dash
|
||
- `GdBall.js` — flip-jump ball
|
||
- `GdUfo.js` — multi-tap jumps
|
||
- `GdSpider.js` — instant teleport
|
||
- + portals, speedpads, spikes, finish lines, trail effects, checkpoint music
|
||
|
||
Factories for each (`GdSpikeFactory`, `GdPortalFactory`, `GdMusicFactory`, ...) live under `AdminPreview/gd*/`.
|
||
|
||
## Data flow (single frame)
|
||
|
||
```
|
||
1. INPUT keyboard/touch/gamepad → PlayerController.onInput()
|
||
2. PHYSICS gravity + velocity + collision against blocks/models
|
||
3. SCRIPTS user script onTick(dt) callbacks (in worker, asynchronous)
|
||
4. MULTIPLAYER read remote positions, lerp other players
|
||
5. RENDER Babylon scene.render() → WebGL2 draw calls
|
||
6. UI React re-renders only if game.ui.set() called (throttled 250ms)
|
||
```
|
||
|
||
## What's NOT in the player
|
||
|
||
- **Editing** — that lives in [Studio](https://git.rublox.pro/rublox/studio).
|
||
- **Project listing / search / publishing** — handled by main site (`rublox.pro/app`).
|
||
- **Authentication UI** — players arrive with JWT/ticket. The player only consumes them.
|
||
- **Admin / moderation** — moved out to private repo (`disaster-recovery/` for owner).
|
||
|
||
## Performance hotspots (places to look first when laggy)
|
||
|
||
1. **`game.ui.set()` called every frame** — React setState 60Hz tanks FPS. Throttle to 250ms with a diff check.
|
||
2. **`scene.findOne()` on init** — `sceneSnapshot` arrives via rAF; reference is null. Move into `onTick` or `setTimeout(0)`.
|
||
3. **Babylon's `blockMaterialDirtyMechanism=true`** — DON'T enable. Breaks new mesh rendering (debris, tracers).
|
||
4. **`createOrUpdateSelectionOctree()`** — same. Breaks editor preview ghosts.
|
||
5. **GLB load via `SceneLoader` instead of `AssetContainer`** — leaks materials. Use `AssetContainer` + `instantiateModelsToScene()`.
|
||
|
||
## Where to start a feature
|
||
|
||
| What you want to add | Start here |
|
||
|---|---|
|
||
| New block type | `engine/CONST/blockTypes.js` + texture in `public/kubikon-assets/blocks/` |
|
||
| New scripting API | `engine/scripts/ScriptSandboxAPI.js` (add to `apiSurface`) |
|
||
| New GD gamemode | Copy `engine/gd/GdBall.js`, register in `GdGameModeRegistry.js` |
|
||
| New HUD widget | `editor-shared/GameHud.jsx` |
|
||
| New preview route for designer | `AdminPreview/` (e.g. `gdSkins/PreviewGdSkins.jsx`) |
|
||
| Multiplayer event | Colyseus schema in `engine/multiplayer/schemas/` + handler in `MultiplayerSync.js` |
|
||
|
||
## License notes for contributors
|
||
|
||
By contributing you agree to license your changes under AGPL-3.0 AND grant the project maintainer a non-exclusive irrevocable license to sublicense (see [CLA.md](./CLA.md)). This is required so we can offer commercial licenses to enterprises.
|