Understanding the update/render cycle and frame timing.
The Game class manages the core game loop using requestAnimationFrame. It provides a fixed-timestep update cycle with automatic delta time calculation, ensuring consistent gameplay regardless of frame rate.
┌─────────────────────────────────────────────────────────────────┐
│ Game Loop │
│ │
│ game.start() │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ requestAnimationFrame │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ ┌─────────────────────────────────────────────────┐ │ │
│ │ │ loop(timestamp) │ │ │
│ │ │ │ │ │
│ │ │ 1. Calculate elapsed time │ │ │
│ │ │ elapsed = timestamp - lastTime │ │ │
│ │ │ │ │ │
│ │ │ 2. Accumulate time │ │ │
│ │ │ accumulator += elapsed │ │ │
│ │ │ │ │ │
│ │ │ 3. While accumulator >= frameInterval: │ │ │
│ │ │ │ │ │ │
│ │ │ ├─► update(dt) │ │ │
│ │ │ │ │ │ │ │
│ │ │ │ ├─► Pipeline.update(dt) │ │ │
│ │ │ │ │ └─► GameObject.update(dt) │ │ │
│ │ │ │ │ │ │ │
│ │ │ │ └─► Tweenetik.updateAll(dt) │ │ │
│ │ │ │ │ │ │
│ │ │ └─► accumulator -= frameInterval │ │ │
│ │ │ │ │ │
│ │ │ 4. render() │ │ │
│ │ │ │ │ │ │
│ │ │ ├─► Painter.clear() │ │ │
│ │ │ │ │ │ │
│ │ │ └─► Pipeline.render() │ │ │
│ │ │ └─► GameObject.render() │ │ │
│ │ │ └─► Shape.draw() │ │ │
│ │ │ │ │ │
│ │ │ 5. requestAnimationFrame(loop) │ │ │
│ │ │ │ │ │
│ │ └─────────────────────────────────────────────────┘ │ │
│ │ │ │ │
│ │ └───────────────────────────────┼───┘
│ └─────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
GCanvas uses a fixed timestep pattern. This means:
- Updates happen at a consistent rate (default: 60 FPS)
dt(delta time) is always the same value- Game logic is frame-rate independent
// Default: 60 FPS = 16.67ms per frame
game.setFPS(60);
// dt in update() is always 1/60 = 0.01667 seconds
update(dt) {
// Move 200 pixels per second, regardless of actual frame rate
this.x += 200 * dt;
}class MyGame extends Game {
constructor(canvas) {
super(canvas);
// 1. Constructor: Setup canvas, context, pipeline
}
init() {
super.init();
// 2. Init: Create scenes, objects, set up game state
// Called once before start()
}
update(dt) {
super.update(dt);
// 3. Update: Game logic (called every frame)
// dt = time since last update in seconds
}
render() {
super.render();
// 4. Render: Drawing (called every frame after update)
}
}
const game = new MyGame(canvas);
game.start(); // Calls init() then starts the loopclass Player extends GameObject {
constructor(game) {
super(game);
// 1. Constructor: Create shapes, set initial state
}
update(dt) {
// 2. Update: Movement, physics, game logic
// Called every frame while active
}
render() {
// 3. Render: Draw the object
// Called every frame while visible
}
destroy() {
super.destroy();
// 4. Destroy: Cleanup when removed
}
}The dt parameter represents time elapsed since the last update, in seconds.
update(dt) {
// dt ≈ 0.01667 at 60 FPS
// dt ≈ 0.03333 at 30 FPS
// Movement: pixels per second
this.x += this.speed * dt; // speed = 200 means 200 px/sec
// Rotation: radians per second
this.rotation += Math.PI * dt; // Half rotation per second
// Timers
this.timer += dt;
if (this.timer >= 2.0) { // 2 seconds
this.doSomething();
this.timer = 0;
}
}// Set target FPS
game.setFPS(60); // Default
game.setFPS(30); // Lower for mobile/performance
// Current frame number
console.log(game.frame);
// Pause on blur (tab switch)
game.enablePauseOnBlur(true);The Pipeline manages multiple objects and ensures proper execution order:
Pipeline.update(dt)
├─► Filter active objects
├─► Sort by zIndex
├─► For each object:
│ └─► object.update(dt)
└─► Tweenetik.updateAll(dt)
Pipeline.render()
├─► Filter visible objects
├─► Sort by zIndex (low to high)
└─► For each object:
└─► object.render()
Scenes create nested lifecycles:
// Scene contains GameObjects
const scene = new Scene(game);
scene.add(player);
scene.add(enemy);
game.pipeline.add(scene);
// Lifecycle flows through hierarchy:
// Pipeline.update()
// └─► Scene.update()
// ├─► player.update()
// └─► enemy.update()class MyGame extends Game {
init() {
this.state = 'menu'; // menu, playing, paused, gameover
}
update(dt) {
switch (this.state) {
case 'menu':
this.menuScene.update(dt);
break;
case 'playing':
this.gameScene.update(dt);
break;
case 'paused':
// Don't update game objects
break;
}
}
}- Use
activeflag - SetgameObject.active = falseto skip updates - Use
visibleflag - Setshape.visible = falseto skip rendering - Pool objects - Reuse objects instead of creating/destroying
- Batch similar operations - Group shapes in containers
- Profile with DevTools - Use browser performance tools
- Architecture Overview - Full system architecture
- Two-Layer Architecture - Shape vs Game layer
- Rendering Pipeline - Shape inheritance chain