| name | phaser-gamedev |
| description | Build 2D browser games with Phaser 3 (JS/TS): scenes, sprites, physics (Arcade/Matter), tilemaps (Tiled), animations, input. Trigger: 'Phaser scene', 'Arcade physics', 'tilemap', 'Phaser 3 game'. |
Phaser Game Development
Build 2D browser games using Phaser 3's scene-based architecture and physics systems.
Before Starting A New Game Or Touching An Existing One
Read read-before-implement.md first. Three-layer reading order (skill docs → target game → shared helpers) and the pre-implementation output checklist. Skipping this is the #1 source of invented APIs and wrong assumptions.
Before merging to main, run the relevant checks in pre-release-audit.md — 7 reactive error signatures + 7 proactive consistency checks (asset keys, scene registration, reloadDocument on game links, touch-controls cleanup, viewport restart guard).
When reaching for abstraction, consult game-patterns.md first — it catalogs composition patterns (BehaviorManager, FSM, TurnManager, WaveManager, CardManager, DialogueManager) with explicit "when to extract" vs "stay ad-hoc" signals. Prevents premature abstraction.
STOP: Before Loading Any Spritesheet
Read spritesheets-nineslice.md FIRST.
Spritesheet loading is fragile—a few pixels off causes silent corruption that compounds into broken visuals. The reference file contains the mandatory inspection protocol.
Quick rules (details in reference):
- Measure the asset before writing loader code—never guess frame dimensions
- Character sprites use SQUARE frames: If you calculate frameWidth=56, try 56 for height first
- Different animations have different frame sizes: A run cycle needs wider frames than idle; an attack needs extra width for weapon swing. Measure EACH spritesheet independently
- Check for spacing: Gaps between frames require
spacing: N in loader config
- Verify the math:
imageWidth = (frameWidth × cols) + (spacing × (cols - 1))
Reference Files
Read these BEFORE working on the relevant feature:
| When working on... | Read first |
|---|
| Starting a new game, or editing a scene you haven't touched recently | read-before-implement.md |
| Pre-merge / pre-release audit, or diagnosing a runtime error | pre-release-audit.md |
| Deciding whether to extract an FSM / BehaviorManager / TurnManager / WaveManager / CardManager / DialogueManager | game-patterns.md |
| Loading ANY spritesheet | spritesheets-nineslice.md |
| Nine-slice UI panels | spritesheets-nineslice.md |
| Config, scenes, objects, input, animations | core-patterns.md |
| Tiled tilemaps, collision layers | tilemaps.md |
| Physics tuning, groups, pooling | arcade-physics.md |
| Scrolling platforms, custom body sizes | arcade-physics.md (Static vs Dynamic section) |
| Performance issues, object pooling | performance.md |
| Keyboard input, SPACE key, canvas focus | keyboard-input.md |
| Raycasting (Wolf3D-style FPS), doors, transparent gates, billboard sprites, enemy collision, secret walls, wall-mounted decals, torch lighting, fisheye fix, texture clipping, weapon sprites, enemy animation, level data integrity | raycasting.md |
| Procedural dungeon generation, BSP splitting, corridor carving, door placement, wall-door-wall, level validation | procedural-dungeon-gen.md |
Architecture Decisions (Make Early)
Before building, decide:
- What scenes does this game need? (Boot, Menu, Game, UI overlay, GameOver)
- What are the core entities and how do they interact?
- What physics model fits? (Arcade for speed, Matter for realism, None for menus)
- What input methods? (keyboard/gamepad/touch)
Physics System Choice
| System | Use When |
|---|
| Arcade | Platformers, shooters, most 2D games. Fast AABB collisions |
| Matter | Physics puzzles, ragdolls, realistic collisions. Slower, more accurate |
| None | Menu scenes, visual novels, card games |
Core Principles
- Scene-first architecture: Organize code around scene lifecycle and transitions
- Composition over inheritance: Build entities from sprite/body/controllers, not deep class trees
- Physics-aware design: Choose collision model early; don't retrofit physics late
- Asset pipeline discipline: Preload everything; reference by keys; keep loading deterministic
- Frame-rate independence: Use
delta for motion and timers; avoid frame counting
CRITICAL: Scaled Sprites + Physics Bodies
When textures are larger than display size (e.g. AI-generated assets), body.setSize() is multiplied by sprite scale!
WRONG — body becomes tiny (5px instead of 80px):
sprite.setDisplaySize(80, 12);
sprite.body.setSize(80, 12);
CORRECT — use FRAME (texture) size, scale applies automatically:
sprite.setDisplaySize(80, 12);
const body = sprite.body as Phaser.Physics.Arcade.Body;
body.setSize(sprite.frame.width, sprite.frame.height);
For static bodies (bricks, platforms), also call updateFromGameObject():
brick.setDisplaySize(40, 16);
const body = brick.body as Phaser.Physics.Arcade.StaticBody;
body.setSize(brick.frame.width, brick.frame.height);
body.updateFromGameObject();
CRITICAL: Arcade physics bodies CANNOT rotate
setAngle() / setRotation() rotates the VISUAL sprite only. The physics body stays axis-aligned (AABB). This means rotated wall sprites have wrong collision shapes.
WRONG — body stays 64×32 horizontal even though sprite is rotated 90°:
wall.setAngle(90);
wall.body.setSize(64, 32);
CORRECT — separate visual from collision:
this.add.image(x, y, "wall").setAngle(90);
const zone = this.add.zone(x, y, 16, span);
this.physics.add.existing(zone, true);
wallGroup.add(zone);
Obstacle collision groups pattern
Split obstacles into separate StaticGroups when different entities need different collision rules:
this.walls = this.physics.add.staticGroup();
this.trees = this.physics.add.staticGroup();
this.physics.add.collider(this.player, this.walls);
this.physics.add.collider(this.player, this.trees);
this.physics.add.collider(this.enemyPool, this.trees);
CRITICAL: Keyboard input — canvas focus problem
Read keyboard-input.md for the full solution.
Phaser's keyboard only works when canvas has focus. When embedded in a React page, canvas loses focus constantly (user clicks tabs, buttons, scrolls). keyboard.target: window alone does NOT fix this.
Required: Window-level capture-phase listener that re-focuses canvas + re-dispatches the event. Plus addCapture(SPACE) in each scene that uses SPACE.
Graphics Overlays on Sprites (Brake Lights, Flames, Effects)
When drawing Graphics effects (brake lights, nitro flames, etc.) on top of sprite-based game objects:
Sprite canvas ≠ car body
AI-generated sprites have transparent padding — the visible content is smaller than the PNG canvas. Always crop sprites first (process_sprite with auto-crop) so the PNG edges match the pixel art edges. Otherwise, effects positioned at halfW/halfH will float outside the visible car.
Coordinate alignment
After g.translateCanvas(x, y) + g.rotateCanvas(angle), the local coordinate origin is the sprite center. Position effects relative to the display size (which maps to cropped sprite edges):
const halfW = CAR_DRAW_W / 2;
const halfH = CAR_DRAW_H / 2;
const sideX = halfW * 0.38;
const rearY = halfH - 4;
Multi-layer effects look better
- Brake lights: outer glow circle (large, low alpha) + inner 2×2px rect (bright)
- Nitro flame: 3 layers — outer (orange), mid (yellow), core (white-yellow) with sin-based flicker
- Dual exhaust: 2 separate small flames with independent flicker phases
Sprite rotation offset
If a sprite faces south but angle=0 means north: add Math.PI to sprite rotation, but keep Graphics rotateCanvas(car.angle) as-is (Graphics follow physics convention, sprite gets the visual flip).
Anti-Patterns
| Anti-Pattern | Problem | Solution |
|---|
Global state on window | Scene transitions break state | Use scene data, registries |
Loading in create() | Assets not ready when referenced | Load in preload(), use Boot scene |
| Frame counting | Game speed varies with FPS | Use delta / 1000 |
| Matter for simple collisions | Unnecessary complexity | Arcade handles most 2D games |
| One giant scene | Hard to extend | Separate gameplay/UI/menus |
| Magic numbers | Impossible to balance | Config objects, constants |
| No object pooling | GC stutters | Groups with setActive(false) |
body.setSize(displayW, displayH) on scaled sprites | Body is multiplied by scale → tiny | Use body.setSize(frame.width, frame.height) |
StaticBody + setSize() + updateFromGameObject() | updateFromGameObject() overwrites custom size every frame | Use immovable dynamic body (setImmovable(true)) — see arcade-physics.md |
setAngle() on physics sprites | Visual rotates but body stays axis-aligned (AABB) | Use invisible zones for collision, sprites for visual only |
Constant setAlpha(0.5) for invincibility | Character looks broken/ghostly | Use blink tween (alpha 1↔0.3, duration 80, yoyo) |
Loading non-existent sprites in preload() | Silent 404 errors on every game load | Only load files that exist; use !textures.exists() + programmatic fallback in create() |
2500 individual add.image() for tile grid | 2500 game objects = slow | Use RenderTexture — draw all tiles onto one texture |
Variation Guidance
Outputs should vary based on:
- Genre (platformer vs top-down vs shmup)
- Target platform (mobile touch, desktop keyboard, gamepad)
- Art style (pixel art scaling vs HD smoothing)
- Performance envelope (many sprites → pooling; few sprites → simpler code)
Remember
Phaser provides powerful primitives—scenes, sprites, physics, input—but architecture is your responsibility.
Think in systems: define the scenes, define the entities, define their interactions—then implement.
Codex can build complete, polished Phaser games. These guidelines illuminate the path—they don't fence it.