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)
6.5 KiB
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):
// 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 jumpGdShip.js— gravity-flip flightGdWave.js— sine-wave dashGdBall.js— flip-jump ballGdUfo.js— multi-tap jumpsGdSpider.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.
- 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)
game.ui.set()called every frame — React setState 60Hz tanks FPS. Throttle to 250ms with a diff check.scene.findOne()on init —sceneSnapshotarrives via rAF; reference is null. Move intoonTickorsetTimeout(0).- Babylon's
blockMaterialDirtyMechanism=true— DON'T enable. Breaks new mesh rendering (debris, tracers). createOrUpdateSelectionOctree()— same. Breaks editor preview ghosts.- GLB load via
SceneLoaderinstead ofAssetContainer— leaks materials. UseAssetContainer+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). This is required so we can offer commercial licenses to enterprises.