Core Concepts
Game Loop
How BaseGame and Game drive the engine's update and render loops.
The engine has two game classes that share the same underlying loop: BaseGame (headless) and Game (client with rendering).
BaseGame
BaseGame is the headless core used by both the server and the client. It manages:
- Physics world — Rapier3D
Worldinstance - Character controller —
KinematicCharacterControllerfor physics-driven movement - Scene — Three.js
Scene(shared between server and client) - Tick rate — Fixed update rate (default: 30 ticks/second)
- World —
BaseWorldinstance managing all entities
import { BaseGame } from '@mavonengine/core'
class MyGame extends BaseGame {
// ...
}
const game = MyGame.instance()
Tick vs update
| Method | Called by | Purpose |
|---|---|---|
tick() | Fixed interval (tickRate) | Physics step, networked state sync |
update(delta) | Each frame / tick | Entity updates, physics simulation |
Registering update callbacks
game.onUpdate((delta) => {
// Called every frame/tick with delta time in seconds
})
// Unregister when no longer needed
game.unregisterOnUpdate(callback)
Environment checks
game.isDevMode() // true when NODE_ENV !== 'production'
game.isProductionMode() // true when NODE_ENV === 'production'
Game
Game extends BaseGame for the client. In addition to everything in BaseGame, it sets up:
- Canvas —
HTMLCanvasElementfor WebGL rendering - UI root —
HTMLDivElementfor overlay UI - Sizes — Window size tracking with resize handling
- Camera —
PerspectiveCamerawithAudioListener - Renderer —
WebGLRendererwith shadow mapping - Resources — Asset loader
- InputManager — Keyboard and mouse input
- ParticleSystem — Particle effects
- LoadingScreen — Asset loading progress screen
- Debug — Tweakpane debug panel (activated via
#debug) - Editor — In-dev editor (dev mode only)
import { Game } from '@mavonengine/core'
const game = Game.instance()
// Canvas and UI are automatically appended to document.body
The render loop runs via requestAnimationFrame and calls update(delta) every frame.
Singleton pattern
Both classes use a static instance() method. Call it anywhere to get the existing instance or create one on first call.
const game = Game.instance()
// Always returns the same instance