phaser-gamedev

Build fast, polished 2D browser games using Phaser 3's scene-based architecture and physics systems. Scene-first architecture organizes games into distinct lifecycle phases (Boot, Menu, Game, Pause, GameOver) with clean state management across transitions Supports Arcade physics for speed and simplicity, Matter physics for realistic simulations, and physics-free scenes for menus and overlays Covers sprites, animations, tilemaps (Tiled integration), input handling (keyboard, pointer, drag), and asset preloading with progress tracking Includes patterns for game objects, collision detection, object pooling, and cross-scene data persistence via registries and scene data stores

INSTALLATION
npx skills add https://github.com/chongdashu/phaserjs-tinyswords --skill phaser-gamedev
Run in your project or agent environment. Adjust flags if your CLI version differs.

SKILL.md

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:

  • Scene-First Architecture: Structure games around scenes, not global state
  • Composition Over Inheritance: Build entities from game objects and components
  • Physics-Aware Design: Choose physics system before coding collisions
  • Asset Pipeline Discipline: Preload everything, reference by key
  • Frame-Rate Independence: Use delta time, not frame counting

Game Configuration

Every Phaser game starts with a configuration object.

Minimal Configuration

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

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

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

// 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

// 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

// 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.

// 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

// 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.

// 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

// 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

// 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

// 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

// 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

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

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

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

// 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

// 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

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

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

// 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.

BrowserAct

Let your agent run on any real-world website

Bypass CAPTCHA & anti-bot for free. Start local, scale to cloud.

Explore BrowserAct Skills →

Stop writing automation&scrapers

Install the CLI. Run your first Skill in 30 seconds. Scale when you're ready.

Start free
free · no credit card