This guide provides web game engine-specific guidance (Phaser, PixiJS, Three.js, Babylon.js) for solution architecture generation.
Ask:
Guidance:
Record ADR: Engine selection and build tooling
Ask:
Guidance:
Phaser Pattern:
// MainMenuScene.ts
export class MainMenuScene extends Phaser.Scene {
constructor() {
super({ key: 'MainMenu' });
}
create() {
this.add.text(400, 300, 'Main Menu', { fontSize: '32px' });
const startButton = this.add
.text(400, 400, 'Start Game', { fontSize: '24px' })
.setInteractive()
.on('pointerdown', () => {
this.scene.start('GameScene');
});
}
}
Record ADR: Architecture pattern and scene management
Ask:
Guidance:
Phaser loading:
class PreloadScene extends Phaser.Scene {
preload() {
// Show progress bar
this.load.on('progress', (value: number) => {
console.log('Loading: ' + Math.round(value * 100) + '%');
});
// Load assets
this.load.atlas('sprites', 'assets/sprites.png', 'assets/sprites.json');
this.load.audio('music', ['assets/music.mp3', 'assets/music.ogg']);
this.load.audio('jump', ['assets/sfx/jump.mp3', 'assets/sfx/jump.ogg']);
}
create() {
this.scene.start('MainMenu');
}
}
Record ADR: Asset loading and management strategy
Web-specific considerations:
Object Pooling Pattern:
class BulletPool {
private pool: Bullet[] = [];
private scene: Phaser.Scene;
constructor(scene: Phaser.Scene, size: number) {
this.scene = scene;
for (let i = 0; i < size; i++) {
const bullet = new Bullet(scene);
bullet.setActive(false).setVisible(false);
this.pool.push(bullet);
}
}
spawn(x: number, y: number, velocityX: number, velocityY: number): Bullet | null {
const bullet = this.pool.find((b) => !b.active);
if (bullet) {
bullet.spawn(x, y, velocityX, velocityY);
}
return bullet || null;
}
}
Target Performance:
Multi-input support:
class GameScene extends Phaser.Scene {
private cursors?: Phaser.Types.Input.Keyboard.CursorKeys;
private wasd?: { [key: string]: Phaser.Input.Keyboard.Key };
create() {
// Keyboard
this.cursors = this.input.keyboard?.createCursorKeys();
this.wasd = this.input.keyboard?.addKeys('W,S,A,D') as any;
// Mouse/Touch
this.input.on('pointerdown', (pointer: Phaser.Input.Pointer) => {
this.handleClick(pointer.x, pointer.y);
});
// Gamepad (optional)
this.input.gamepad?.on('down', (pad, button, index) => {
this.handleGamepadButton(button);
});
}
update() {
// Handle keyboard input
if (this.cursors?.left.isDown || this.wasd?.A.isDown) {
this.player.moveLeft();
}
}
}
LocalStorage pattern:
interface GameSaveData {
level: number;
score: number;
playerStats: {
health: number;
lives: number;
};
}
class SaveManager {
private static SAVE_KEY = 'game_save_data';
static save(data: GameSaveData): void {
localStorage.setItem(this.SAVE_KEY, JSON.stringify(data));
}
static load(): GameSaveData | null {
const data = localStorage.getItem(this.SAVE_KEY);
return data ? JSON.parse(data) : null;
}
static clear(): void {
localStorage.removeItem(this.SAVE_KEY);
}
}
Phaser + TypeScript + Vite:
project/
├── public/ # Static assets
│ ├── assets/
│ │ ├── sprites/
│ │ ├── audio/
│ │ │ ├── music/
│ │ │ └── sfx/
│ │ └── fonts/
│ └── index.html
├── src/
│ ├── main.ts # Game initialization
│ ├── config.ts # Phaser config
│ ├── scenes/ # Game scenes
│ │ ├── PreloadScene.ts
│ │ ├── MainMenuScene.ts
│ │ ├── GameScene.ts
│ │ └── GameOverScene.ts
│ ├── entities/ # Game objects
│ │ ├── Player.ts
│ │ ├── Enemy.ts
│ │ └── Bullet.ts
│ ├── systems/ # Game systems
│ │ ├── InputManager.ts
│ │ ├── AudioManager.ts
│ │ └── SaveManager.ts
│ ├── utils/ # Utilities
│ │ ├── ObjectPool.ts
│ │ └── Constants.ts
│ └── types/ # TypeScript types
│ └── index.d.ts
├── tests/ # Unit tests
├── package.json
├── tsconfig.json
├── vite.config.ts
└── README.md
Jest + TypeScript:
// Player.test.ts
import { Player } from '../entities/Player';
describe('Player', () => {
let player: Player;
beforeEach(() => {
// Mock Phaser scene
const mockScene = {
add: { sprite: jest.fn() },
physics: { add: { sprite: jest.fn() } },
} as any;
player = new Player(mockScene, 0, 0);
});
test('takes damage correctly', () => {
player.health = 100;
player.takeDamage(20);
expect(player.health).toBe(80);
});
test('dies when health reaches zero', () => {
player.health = 10;
player.takeDamage(20);
expect(player.alive).toBe(false);
});
});
E2E Testing:
Build for production:
// package.json scripts
{
"scripts": {
"dev": "vite",
"build": "tsc andand vite build",
"preview": "vite preview",
"test": "jest"
}
}
Deployment targets:
Optimization:
// vite.config.ts
export default defineConfig({
build: {
rollupOptions: {
output: {
manualChunks: {
phaser: ['phaser'], // Separate Phaser bundle
},
},
},
minify: 'terser',
terserOptions: {
compress: {
drop_console: true, // Remove console.log in prod
},
},
},
});
When needed: Games with music, sound effects, ambience Responsibilities:
When needed: Mobile web games, complex games Responsibilities:
When needed: F2P web games Responsibilities:
When needed: Mobile wrapper apps (Cordova/Capacitor) Responsibilities:
const config: Phaser.Types.Core.GameConfig = {
type: Phaser.AUTO, // WebGL with Canvas fallback
width: 800,
height: 600,
physics: {
default: 'arcade',
arcade: { gravity: { y: 300 }, debug: false },
},
scene: [PreloadScene, MainMenuScene, GameScene, GameOverScene],
};
const game = new Phaser.Game(config);
const app = new PIXI.Application({
width: 800,
height: 600,
backgroundColor: 0x1099bb,
});
document.body.appendChild(app.view);
const sprite = PIXI.Sprite.from('assets/player.png');
app.stage.addChild(sprite);
app.ticker.add((delta) => {
sprite.rotation += 0.01 * delta;
});
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
const geometry = new THREE.BoxGeometry();
const material = new THREE.MeshBasicMaterial({ color: 0x00ff00 });
const cube = new THREE.Mesh(geometry, material);
scene.add(cube);
function animate() {
requestAnimationFrame(animate);
cube.rotation.x += 0.01;
renderer.render(scene, camera);
}
animate();
ADR-XXX: [Title]
Context: What web game-specific issue are we solving?
Options:
Decision: We chose [Option X]
Web-specific Rationale:
Consequences:
This guide is specific to web game engines. For native engines, see: