# Web Game Engine Architecture Guide This guide provides web game engine-specific guidance (Phaser, PixiJS, Three.js, Babylon.js) for solution architecture generation. --- ## Web Game-Specific Questions ### 1. Engine and Technology Selection **Ask:** - Which engine? (Phaser 3, PixiJS, Three.js, Babylon.js, custom Canvas/WebGL) - TypeScript or JavaScript? - Build tool? (Vite, Webpack, Rollup, Parcel) - Target platform(s)? (Desktop web, mobile web, PWA, Cordova/Capacitor wrapper) **Guidance:** - **Phaser 3**: Full-featured 2D game framework, great for beginners - **PixiJS**: 2D rendering library, more low-level than Phaser - **Three.js**: 3D graphics library, mature ecosystem - **Babylon.js**: Complete 3D game engine, WebXR support - **TypeScript**: Recommended for all web games (type safety, better tooling) - **Vite**: Modern, fast, HMR - best for development **Record ADR:** Engine selection and build tooling --- ### 2. Architecture Pattern **Ask:** - Scene-based architecture? (Phaser scenes, custom scene manager) - ECS (Entity Component System)? (Libraries: bitECS, ecsy) - State management? (Redux, Zustand, custom FSM) **Guidance:** - **Scene-based**: Natural for Phaser, good for level-based games - **ECS**: Better performance for large entity counts (100s+) - **FSM**: Good for simple state transitions (menu → game → gameover) **Phaser Pattern:** ```typescript // 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 --- ### 3. Asset Management **Ask:** - Asset loading strategy? (Preload all, lazy load, progressive) - Texture atlas usage? (TexturePacker, built-in tools) - Audio format strategy? (MP3, OGG, WebM) **Guidance:** - **Preload**: Load all assets at start (simple, small games) - **Lazy load**: Load per-level (better for larger games) - **Texture atlases**: Essential for performance (reduce draw calls) - **Audio**: MP3 for compatibility, OGG for smaller size, use both **Phaser loading:** ```typescript 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 Game-Specific Architecture Sections ### Performance Optimization **Web-specific considerations:** - **Object Pooling**: Mandatory for bullets, particles, enemies (avoid GC pauses) - **Sprite Batching**: Use texture atlases, minimize state changes - **Canvas vs WebGL**: WebGL for better performance (most games) - **Draw Call Reduction**: Batch similar sprites, use sprite sheets - **Memory Management**: Watch heap size, profile with Chrome DevTools **Object Pooling Pattern:** ```typescript 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:** - **Desktop**: 60 FPS minimum - **Mobile**: 60 FPS (high-end), 30 FPS (low-end) - **Profile with**: Chrome DevTools Performance tab, Phaser Debug plugin --- ### Input Handling **Multi-input support:** ```typescript 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(); } } } ``` --- ### State Persistence **LocalStorage pattern:** ```typescript 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); } } ``` --- ### Source Tree Structure **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 ``` --- ### Testing Strategy **Jest + TypeScript:** ```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:** - Playwright for browser automation - Cypress for interactive testing - Test game states, not individual frames --- ### Deployment and Build **Build for production:** ```json // package.json scripts { "scripts": { "dev": "vite", "build": "tsc andand vite build", "preview": "vite preview", "test": "jest" } } ``` **Deployment targets:** - **Static hosting**: Netlify, Vercel, GitHub Pages, AWS S3 - **CDN**: Cloudflare, Fastly for global distribution - **PWA**: Service worker for offline play - **Mobile wrapper**: Cordova or Capacitor for app stores **Optimization:** ```typescript // 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 }, }, }, }); ``` --- ## Specialist Recommendations ### Audio Designer **When needed:** Games with music, sound effects, ambience **Responsibilities:** - Web Audio API architecture - Audio sprite creation (combine sounds into one file) - Music loop management - Sound effect implementation - Audio performance on web (decode strategy) ### Performance Optimizer **When needed:** Mobile web games, complex games **Responsibilities:** - Chrome DevTools profiling - Object pooling implementation - Draw call optimization - Memory management - Bundle size optimization - Network performance (asset loading) ### Monetization Specialist **When needed:** F2P web games **Responsibilities:** - Ad network integration (Google AdSense, AdMob for web) - In-game purchases (Stripe, PayPal) - Analytics (Google Analytics, custom events) - A/B testing frameworks - Economy design ### Platform Specialist **When needed:** Mobile wrapper apps (Cordova/Capacitor) **Responsibilities:** - Native plugin integration - Platform-specific performance tuning - App store submission - Device compatibility testing - Push notification setup --- ## Common Pitfalls 1. **Not using object pooling** - Frequent instantiation causes GC pauses 2. **Too many draw calls** - Use texture atlases and sprite batching 3. **Loading all assets at once** - Causes long initial load times 4. **Not testing on mobile** - Performance vastly different on phones 5. **Ignoring bundle size** - Large bundles = slow load times 6. **Not handling window resize** - Web games run in resizable windows 7. **Forgetting audio autoplay restrictions** - Browsers block auto-play without user interaction --- ## Engine-Specific Patterns ### Phaser 3 ```typescript 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); ``` ### PixiJS ```typescript 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; }); ``` ### Three.js ```typescript 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(); ``` --- ## Key Architecture Decision Records ### ADR Template for Web Games **ADR-XXX: [Title]** **Context:** What web game-specific issue are we solving? **Options:** 1. Phaser 3 (full framework) 2. PixiJS (rendering library) 3. Three.js/Babylon.js (3D) 4. Custom Canvas/WebGL **Decision:** We chose [Option X] **Web-specific Rationale:** - Engine features vs bundle size - Community and plugin ecosystem - TypeScript support - Performance on target devices (mobile web) - Browser compatibility - Development velocity **Consequences:** - Impact on bundle size (Phaser ~1.2MB gzipped) - Learning curve - Platform limitations - Plugin availability --- _This guide is specific to web game engines. For native engines, see:_ - game-engine-unity-guide.md - game-engine-godot-guide.md - game-engine-unreal-guide.md