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 World instance
  • Character controllerKinematicCharacterController for physics-driven movement
  • Scene — Three.js Scene (shared between server and client)
  • Tick rate — Fixed update rate (default: 30 ticks/second)
  • WorldBaseWorld instance managing all entities
import { BaseGame } from '@mavonengine/core'

class MyGame extends BaseGame {
  // ...
}

const game = MyGame.instance()

Tick vs update

MethodCalled byPurpose
tick()Fixed interval (tickRate)Physics step, networked state sync
update(delta)Each frame / tickEntity 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:

  • CanvasHTMLCanvasElement for WebGL rendering
  • UI rootHTMLDivElement for overlay UI
  • Sizes — Window size tracking with resize handling
  • CameraPerspectiveCamera with AudioListener
  • RendererWebGLRenderer with 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