4.3 KiB
Game Architecture Patterns & Practices
Reference guide for implementing board games using
boardgame-phaserframework. Explorepackages/onitama-game/andpackages/sample-game/for concrete implementations.
Architecture Overview
Games follow a layered architecture separating logic, presentation, and UI state:
packages/my-game/
├── src/
│ ├── game/ # Pure game logic (re-exported from boardgame-core)
│ ├── scenes/ # Phaser scenes (MenuScene, GameScene)
│ ├── spawners/ # Data-driven object lifecycle
│ ├── renderers/ # Renderers for game objects
│ ├── state/ # UI-only reactive state
│ ├── config.ts # Centralized layout & style constants
│ └── ui/App.tsx # Preact root component
| Layer | Responsibility |
|---|---|
game/ |
State, commands, prompts, validation (pure logic) |
scenes/ |
Phaser lifecycle, input handling, visual composition |
spawners/ |
Reactive game object spawn/update/despawn |
renderers/ |
Phaser visual representation of game objects |
state/ |
UI-only reactive state (selection, hover, etc.) |
ui/ |
Preact components bridging Phaser and DOM |
Core Patterns
1. ReactiveScene / GameHostScene
Extend ReactiveScene(packages\framework\src\scenes\ReactiveScene.ts) to use reactive integration features.
- Access game context for scene navigation
- Use
this.disposablesfor auto-cleanup on shutdown.
2. Spawner Pattern
Implement Spawner<TData, TObj> for data-driven objects.
*getData(): Yield objects that should exist.getKey(): Unique identifier for diffing.onSpawn(): Create Phaser objects.onUpdate(): Handle data changes (animate if needed).onDespawn(): Clean up with optional animation.- See:
packages/onitama-game/src/spawners/
3. Reactive UI State
Use MutableSignal for UI-only state, separate from game state.
- Mutate via
.produce(). - React via
effect()from@preact/signals-core. - Clean up effects on object
'destroy'to prevent leaks. - See:
packages/onitama-game/src/state/ui.ts
4. Custom Containers
Extend Phaser.GameObjects.Container to encapsulate visuals and state.
- Store logical state as private signals.
- Use
effect()for reactive highlights/selections.
5. Tween Interruption
Always register state-related tweens: this.scene.addTweenInterruption(tween).
Prevents visual glitches when game state changes mid-animation.
6. Scene Navigation
Use await this.sceneController.launch('SceneKey').
Register scenes in App.tsx via <PhaserScene>. Pass data via data prop.
Best Practices
- Centralize Config: Keep
CELL_SIZE,BOARD_OFFSET, colors, etc., insrc/config.tswithas const. Avoid magic numbers. - Type Imports: Use
import type { Foo } from 'bar'for type-only imports. - Input Handling: Use
this.add.zone()for grid/cell-based input zones. - Cleanup: Always dispose
effect()on'destroy'. Usethis.disposables.add()for scene-level resources.
Common Pitfalls
| Pitfall | Solution |
|---|---|
| Duplicate constants across files | Export from single config.ts or shared spawner |
| Missing tween interruptions | Always call addTweenInterruption() |
| Effect memory leaks | this.on('destroy', () => dispose()) |
| Hardcoded magic numbers | Extract to src/config.ts |
| UI state staleness | Clear related selections on state change |
Testing
Add Vitest for UI state transitions, coordinate conversions, and spawner logic.
See packages/framework/ for test setup examples.
Quick Reference
| Pattern | Purpose |
|---|---|
GameHostScene |
Connect Phaser to game host |
Spawner<T, TObj> |
Reactive object lifecycle |
MutableSignal |
UI-only reactive state |
effect() |
React to signal changes in Phaser objects |
addTweenInterruption() |
Prevent animation race conditions |
sceneController.launch() |
Navigate between scenes |
spawnEffect() |
Register spawners |
this.disposables.add() |
Auto-cleanup resources |
Related Documents
AGENTS.md— Project overview, commands, and code styledocs/GameModule.md— GameModule implementation guidepackages/framework/src/—boardgame-phasersource codepackages/onitama-game/src/— Complete game implementation reference