--- name: phaser-gamedev description: > Build 2D games with Phaser 3 framework. Covers scene lifecycle, sprites, physics (Arcade/Matter), tilemaps, animations, input handling, and game architecture. Trigger: "create phaser game", "add phaser scene", "phaser sprite", "phaser physics", "game development with phaser". --- # Phaser Game Development Build fast, polished 2D browser games using Phaser 3's scene-based architecture and physics systems. ## Philosophy: Games as Living Systems Games are not static UIs—they are **dynamic systems** where entities interact, state evolves, and player input drives everything. Before writing code, think architecturally. **Before building, ask**: - What **scenes** does this game need? (Boot, Menu, Game, Pause, GameOver) - What **entities** exist and how do they interact? - What **state** must persist across scenes? - What **physics** model fits? (Arcade for speed, Matter for realism) - What **input methods** will players use? **Core principles**: 1. **Scene-First Architecture**: Structure games around scenes, not global state 2. **Composition Over Inheritance**: Build entities from game objects and components 3. **Physics-Aware Design**: Choose physics system before coding collisions 4. **Asset Pipeline Discipline**: Preload everything, reference by key 5. **Frame-Rate Independence**: Use delta time, not frame counting --- ## Game Configuration Every Phaser game starts with a configuration object. ### Minimal Configuration ```javascript const config = { type: Phaser.AUTO, // WebGL with Canvas fallback width: 800, height: 600, scene: [BootScene, GameScene] }; const game = new Phaser.Game(config); ``` ### Full Configuration Pattern ```javascript const config = { type: Phaser.AUTO, width: 800, height: 600, parent: 'game-container', // DOM element ID backgroundColor: '#2d2d2d', scale: { mode: Phaser.Scale.FIT, autoCenter: Phaser.Scale.CENTER_BOTH }, physics: { default: 'arcade', arcade: { gravity: { y: 300 }, debug: false // Enable during development } }, scene: [BootScene, MenuScene, GameScene, GameOverScene] }; ``` ### Physics System Choice | System | Use When | |--------|----------| | **Arcade** | Platformers, shooters, most 2D games. Fast, simple AABB collisions | | **Matter** | Physics puzzles, ragdolls, realistic collisions. Slower, more accurate | | **None** | Menu scenes, visual novels, card games | --- ## Scene Architecture Scenes are the fundamental organizational unit. Each scene has a lifecycle. ### Scene Lifecycle Methods ```javascript class GameScene extends Phaser.Scene { constructor() { super('GameScene'); // Scene key for reference } init(data) { // Called first. Receive data from previous scene this.level = data.level || 1; } preload() { // Load assets. Runs before create() this.load.image('player', 'assets/player.png'); this.load.spritesheet('enemy', 'assets/enemy.png', { frameWidth: 32, frameHeight: 32 }); } create() { // Set up game objects, physics, input this.player = this.physics.add.sprite(100, 100, 'player'); this.cursors = this.input.keyboard.createCursorKeys(); } update(time, delta) { // Game loop. Called every frame // delta = milliseconds since last frame this.player.x += this.speed * (delta / 1000); } } ``` ### Scene Transitions ```javascript // Start a new scene (stops current) this.scene.start('GameOverScene', { score: this.score }); // Launch scene in parallel (both run) this.scene.launch('UIScene'); // Pause/resume scenes this.scene.pause('GameScene'); this.scene.resume('GameScene'); // Stop a scene this.scene.stop('UIScene'); ``` ### Recommended Scene Structure ``` scenes/ ├── BootScene.js # Asset loading, progress bar ├── MenuScene.js # Title screen, options ├── GameScene.js # Main gameplay ├── UIScene.js # HUD overlay (launched parallel) ├── PauseScene.js # Pause menu overlay └── GameOverScene.js # End screen, restart option ``` --- ## Game Objects Everything visible in Phaser is a Game Object. ### Common Game Objects ```javascript // Images (static) this.add.image(400, 300, 'background'); // Sprites (can animate, physics-enabled) const player = this.add.sprite(100, 100, 'player'); // Text const score = this.add.text(16, 16, 'Score: 0', { fontSize: '32px', fill: '#fff' }); // Graphics (draw shapes) const graphics = this.add.graphics(); graphics.fillStyle(0xff0000); graphics.fillRect(100, 100, 50, 50); // Containers (group objects) const container = this.add.container(400, 300, [sprite1, sprite2]); // Tilemaps const map = this.make.tilemap({ key: 'level1' }); ``` ### Sprite Creation Patterns ```javascript // Basic sprite const sprite = this.add.sprite(x, y, 'textureKey'); // Sprite with physics body const sprite = this.physics.add.sprite(x, y, 'textureKey'); // From spritesheet frame const sprite = this.add.sprite(x, y, 'sheet', frameIndex); // From atlas const sprite = this.add.sprite(x, y, 'atlas', 'frameName'); ``` --- ## Physics Systems ### Arcade Physics (Recommended Default) Fast, simple physics for most 2D games. ```javascript // Enable physics on sprite this.physics.add.sprite(x, y, 'player'); // Or add physics to existing sprite this.physics.add.existing(sprite); // Configure body sprite.body.setVelocity(200, 0); sprite.body.setBounce(0.5); sprite.body.setCollideWorldBounds(true); sprite.body.setGravityY(300); // Collision detection this.physics.add.collider(player, platforms); this.physics.add.overlap(player, coins, collectCoin, null, this); function collectCoin(player, coin) { coin.disableBody(true, true); // Remove from physics and hide this.score += 10; } ``` ### Physics Groups ```javascript // Static group (platforms, walls) const platforms = this.physics.add.staticGroup(); platforms.create(400, 568, 'ground').setScale(2).refreshBody(); // Dynamic group (enemies, bullets) const enemies = this.physics.add.group({ key: 'enemy', repeat: 5, setXY: { x: 100, y: 0, stepX: 70 } }); enemies.children.iterate(enemy => { enemy.setBounce(Phaser.Math.FloatBetween(0.4, 0.8)); }); ``` ### Matter Physics For realistic physics simulations. ```javascript // Config physics: { default: 'matter', matter: { gravity: { y: 1 }, debug: true } } // Create bodies const ball = this.matter.add.circle(400, 100, 25); const box = this.matter.add.rectangle(400, 400, 100, 50, { isStatic: true }); // Sprite with Matter body const player = this.matter.add.sprite(100, 100, 'player'); player.setFriction(0.005); player.setBounce(0.9); ``` --- ## Input Handling ### Keyboard Input ```javascript // Cursor keys this.cursors = this.input.keyboard.createCursorKeys(); // In update() if (this.cursors.left.isDown) { player.setVelocityX(-160); } else if (this.cursors.right.isDown) { player.setVelocityX(160); } if (this.cursors.up.isDown && player.body.touching.down) { player.setVelocityY(-330); // Jump } // Custom keys this.spaceKey = this.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.SPACE); // Key events this.input.keyboard.on('keydown-SPACE', () => { this.fire(); }); ``` ### Pointer/Mouse Input ```javascript // Click/tap this.input.on('pointerdown', (pointer) => { console.log(pointer.x, pointer.y); }); // Make object interactive sprite.setInteractive(); sprite.on('pointerdown', () => { sprite.setTint(0xff0000); }); sprite.on('pointerup', () => { sprite.clearTint(); }); // Drag this.input.setDraggable(sprite); this.input.on('drag', (pointer, obj, dragX, dragY) => { obj.x = dragX; obj.y = dragY; }); ``` --- ## Animations ### Creating Animations ```javascript // In create() - define once this.anims.create({ key: 'walk', frames: this.anims.generateFrameNumbers('player', { start: 0, end: 3 }), frameRate: 10, repeat: -1 // Loop forever }); this.anims.create({ key: 'jump', frames: [{ key: 'player', frame: 4 }], frameRate: 20 }); // From atlas this.anims.create({ key: 'explode', frames: this.anims.generateFrameNames('atlas', { prefix: 'explosion_', start: 1, end: 8, zeroPad: 2 }), frameRate: 16, hideOnComplete: true }); ``` ### Playing Animations ```javascript // Play animation sprite.anims.play('walk', true); // true = ignore if already playing // Play once sprite.anims.play('jump'); // Stop sprite.anims.stop(); // Animation events sprite.on('animationcomplete', (anim, frame) => { if (anim.key === 'die') { sprite.destroy(); } }); ``` --- ## Asset Loading ### Preload Patterns ```javascript preload() { // Images this.load.image('sky', 'assets/sky.png'); // Spritesheets this.load.spritesheet('player', 'assets/player.png', { frameWidth: 32, frameHeight: 48 }); // Atlases (TexturePacker) this.load.atlas('sprites', 'assets/sprites.png', 'assets/sprites.json'); // Tilemaps this.load.tilemapTiledJSON('map', 'assets/level1.json'); this.load.image('tiles', 'assets/tileset.png'); // Audio this.load.audio('bgm', 'assets/music.mp3'); this.load.audio('sfx', ['assets/sound.ogg', 'assets/sound.mp3']); // Progress tracking this.load.on('progress', (value) => { console.log(`Loading: ${Math.round(value * 100)}%`); }); } ``` ### Boot Scene Pattern ```javascript class BootScene extends Phaser.Scene { constructor() { super('BootScene'); } preload() { // Loading bar const width = this.cameras.main.width; const height = this.cameras.main.height; const progressBar = this.add.graphics(); const progressBox = this.add.graphics(); progressBox.fillStyle(0x222222, 0.8); progressBox.fillRect(width/2 - 160, height/2 - 25, 320, 50); this.load.on('progress', (value) => { progressBar.clear(); progressBar.fillStyle(0xffffff, 1); progressBar.fillRect(width/2 - 150, height/2 - 15, 300 * value, 30); }); // Load all game assets here this.load.image('player', 'assets/player.png'); // ... more assets } create() { this.scene.start('MenuScene'); } } ``` --- ## Tilemaps (Tiled Integration) ### Loading and Creating ```javascript preload() { this.load.tilemapTiledJSON('map', 'assets/map.json'); this.load.image('tiles', 'assets/tileset.png'); } create() { const map = this.make.tilemap({ key: 'map' }); const tileset = map.addTilesetImage('tileset-name-in-tiled', 'tiles'); // Create layers (match names from Tiled) const backgroundLayer = map.createLayer('Background', tileset, 0, 0); const groundLayer = map.createLayer('Ground', tileset, 0, 0); // Enable collision on specific tiles groundLayer.setCollisionByProperty({ collides: true }); // Or by tile index groundLayer.setCollisionBetween(1, 100); // Add collision with player this.physics.add.collider(this.player, groundLayer); } ``` ### Object Layers ```javascript // Spawn points from Tiled object layer const spawnPoint = map.findObject('Objects', obj => obj.name === 'spawn'); this.player = this.physics.add.sprite(spawnPoint.x, spawnPoint.y, 'player'); // Create objects from layer const coins = map.createFromObjects('Objects', { name: 'coin', key: 'coin' }); this.physics.world.enable(coins); ``` --- ## Project Structure ### Recommended Organization ``` game/ ├── src/ │ ├── scenes/ │ │ ├── BootScene.js │ │ ├── MenuScene.js │ │ ├── GameScene.js │ │ └── UIScene.js │ ├── gameObjects/ │ │ ├── Player.js │ │ ├── Enemy.js │ │ └── Collectible.js │ ├── systems/ │ │ ├── InputManager.js │ │ └── AudioManager.js │ ├── config/ │ │ └── gameConfig.js │ └── main.js ├── assets/ │ ├── images/ │ ├── audio/ │ ├── tilemaps/ │ └── fonts/ ├── index.html └── package.json ``` ### ES Module Setup ```javascript // main.js import Phaser from 'phaser'; import BootScene from './scenes/BootScene'; import GameScene from './scenes/GameScene'; import { gameConfig } from './config/gameConfig'; const config = { ...gameConfig, scene: [BootScene, GameScene] }; new Phaser.Game(config); ``` --- ## Anti-Patterns to Avoid ❌ **Global State Soup**: Storing game state on `window` or module globals **Why bad**: Untrackable bugs, scene transitions break state **Better**: Use scene data, registries, or dedicated state managers ❌ **Loading in Create**: Loading assets in `create()` instead of `preload()` **Why bad**: Assets may not be ready when referenced **Better**: Always load in `preload()`, use Boot scene for all assets ❌ **Frame-Dependent Logic**: Using frame count instead of delta time **Why bad**: Game speed varies with frame rate **Better**: `this.speed * (delta / 1000)` for consistent movement ❌ **Physics Overkill**: Using Matter for simple platformer collisions **Why bad**: Performance hit, unnecessary complexity **Better**: Arcade physics handles 90% of 2D game needs ❌ **Monolithic Scenes**: One giant scene with all game logic **Why bad**: Unmaintainable, hard to add features **Better**: Separate scenes for menus, gameplay, UI overlays ❌ **Magic Numbers**: Hardcoded values scattered in code **Why bad**: Impossible to balance, inconsistent **Better**: Config objects, constants files ❌ **Ignoring Object Pooling**: Creating/destroying objects every frame **Why bad**: Memory churn, garbage collection stutters **Better**: Use groups with `setActive(false)` / `setVisible(false)` ❌ **Synchronous Asset Access**: Assuming assets load instantly **Why bad**: Race conditions, undefined textures **Better**: Chain scene starts, use load events ❌ **Assuming Spritesheet Frame Dimensions**: Using guessed frame sizes without verifying **Why bad**: Wrong dimensions cause silent frame corruption; off-by-pixels compounds into broken visuals **Better**: Open asset file, measure frames, calculate with spacing/margin, verify math adds up ❌ **Ignoring Spritesheet Spacing**: Not specifying `spacing` for gapped spritesheets **Why bad**: Frames shift progressively; later frames read wrong pixel regions **Better**: Check source asset for gaps between frames; use `spacing: N` in loader config ❌ **Hardcoding Nine-Slice Colors**: Using single background color for all UI panel variants **Why bad**: Transparent frame edges reveal wrong color for different asset color schemes **Better**: Per-asset background color config; sample from center frame (frame 4) ❌ **Nine-Slice with Padded Frames**: Treating the full frame as the slice region when the art is centered/padded inside each tile **Why bad**: Edge tiles contribute interior fill, showing up as opaque “side bars” inside the panel **Better**: Trim tiles to their effective content bounds (alpha bbox) and composite/cache a texture; add ~1px overlap + disable smoothing to avoid seams ❌ **Scaling Discontinuous UI Art**: Stretching a cropped ribbon/banner row that contains internal transparent gaps **Why bad**: The transparent gutters get stretched, so the UI looks segmented or the fill disappears behind the frame. **Better**: Slice the asset into caps/center, stretch only the center, and stitch the pieces (with ~1px overlap + smoothing disabled) before rendering at pivot sizes. --- ## Variation Guidance **IMPORTANT**: Game implementations should vary based on: - **Game Genre**: Platformer physics differ from top-down shooter physics - **Target Platform**: Mobile needs touch input, desktop can use keyboard - **Art Style**: Pixel art uses nearest-neighbor scaling, HD art uses linear - **Performance Needs**: Many sprites → object pooling; few sprites → simple creation - **Complexity**: Simple games can inline; complex games need class hierarchies **Avoid converging on**: - Always using 800x600 resolution - Always using Arcade physics - Always using the same scene structure - Copy-pasting boilerplate without adaptation --- ## Quick Reference ### Common Physics Properties ```javascript body.setVelocity(x, y) body.setVelocityX(x) body.setBounce(x, y) body.setGravityY(y) body.setCollideWorldBounds(true) body.setImmovable(true) // For static-like dynamic bodies body.setDrag(x, y) body.setMaxVelocity(x, y) ``` ### Useful Scene Properties ```javascript this.cameras.main // Main camera this.physics.world // Physics world this.input.keyboard // Keyboard manager this.sound // Audio manager this.time // Time/clock manager this.tweens // Tween manager this.anims // Animation manager this.registry // Cross-scene data store this.data // Scene-specific data store ``` ### Essential Events ```javascript // Scene events this.events.on('pause', callback) this.events.on('resume', callback) this.events.on('shutdown', callback) // Physics events this.physics.world.on('worldbounds', callback) // Game object events sprite.on('destroy', callback) sprite.on('animationcomplete', callback) ``` --- ## See Also - **references/arcade-physics.md** - Deep dive into Arcade physics - **references/tilemaps.md** - Advanced tilemap techniques - **references/performance.md** - Optimization strategies - **references/spritesheets-nineslice.md** - Spritesheet loading (spacing/margin), nine-slice UI panels, asset inspection --- ## Remember Phaser gives you powerful primitives—scenes, sprites, physics, input—but **architecture is your responsibility**. Think in systems: What scenes do you need? What entities exist? How do they interact? Answer these questions before writing code, and your game will be maintainable as it grows. **Claude is capable of building complete, polished Phaser games. These guidelines illuminate the path—they don't fence it.**