name: dev-phaser-fundamentals description: Phaser game configuration, scenes, and lifecycle management
Phaser Fundamentals
"2D web games made simple – scenes, sprites, and physics."
When to Use This Skill
Use when:
- Setting up a new Phaser game
- Creating game scenes
- Implementing the scene lifecycle (preload/create/update)
- Configuring game settings (scale, physics, renderer)
- Setting up the game container and canvas
Quick Start
import Phaser from "phaser";
import { MainScene } from "./scenes/MainScene";
const config: Phaser.Types.Core.GameConfig = {
type: Phaser.AUTO,
width: 800,
height: 600,
parent: "game-container",
backgroundColor: "#000000",
scale: {
mode: Phaser.Scale.FIT,
autoCenter: Phaser.Scale.CENTER_BOTH,
},
physics: {
default: "arcade",
arcade: {
gravity: { y: 300 },
debug: false,
},
},
scene: [MainScene],
};
new Phaser.Game(config);
Before/After: Manual DOM vs Phaser Pattern
❌ Before: Manual DOM Manipulation
// Manual game loop without Phaser
const canvas = document.getElementById("game") as HTMLCanvasElement;
const ctx = canvas.getContext("2d")!;
let playerX = 400;
let playerY = 300;
// Manual asset loading
const playerImg = new Image();
playerImg.src = "assets/player.png";
playerImg.onload = () => {
gameLoop();
};
// Manual game loop
function gameLoop() {
// Clear canvas
ctx.clearRect(0, 0, canvas.width, canvas.height);
// Draw player
ctx.drawImage(playerImg, playerX, playerY);
// Handle input manually
document.addEventListener("keydown", (e) => {
if (e.key === "ArrowLeft") playerX -= 5;
// ... more manual input handling
});
requestAnimationFrame(gameLoop);
}
Problems:
- Manual asset loading (no preload queue)
- No sprite management
- Manual collision detection
- No physics engine
- Scene management is manual
✅ After: Phaser Pattern
// Phaser handles everything
export class GameScene extends Phaser.Scene {
private player!: Phaser.Physics.Arcade.Sprite;
preload() {
// Built-in asset loading with progress tracking
this.load.image("player", "assets/player.png");
}
create() {
// Physics-enabled sprite
this.player = this.physics.add.sprite(400, 300, "player");
// Built-in input handling
this.cursors = this.input.keyboard!.createCursorKeys();
}
update() {
// Frame-independent movement
if (this.cursors.left!.isDown) {
this.player.setVelocityX(-160);
}
// Built-in physics handles position updates
}
}
const config = {
// Phaser manages the game loop, canvas, rendering
scene: [GameScene],
physics: { default: "arcade" },
};
new Phaser.Game(config);
Benefits:
- Asset preloading with progress events
- Built-in physics engine
- Scene lifecycle management
- Input handling abstraction
- Optimized rendering pipeline
Decision Framework
| Need | Use |
|---|---|
| Basic 2D game | Phaser.AUTO renderer type |
| Physics platformer | Arcade physics with gravity |
| Physics puzzle | Matter physics for realism |
| Responsive layout | Phaser.Scale.FIT with CENTER_BOTH |
| Multiple scenes | Array of scene classes in config |
Progressive Guide
Level 1: Basic Scene Setup
export class MainScene extends Phaser.Scene {
constructor() {
super({ key: "MainScene" });
}
preload() {
// Load assets before create()
this.load.image("player", "assets/player.png");
this.load.image("background", "assets/bg.png");
}
create() {
// Create game objects
this.add.image(400, 300, "background");
this.player = this.physics.add.sprite(400, 300, "player");
}
update(time: number, delta: number) {
// Game loop (60fps)
// delta = time since last frame in ms
}
}
Level 2: Scene Configuration with Data
export class GameScene extends Phaser.Scene {
constructor() {
super({ key: "GameScene", active: false });
}
init(data: { level: number; score: number }) {
// Receive data from previous scene
this.level = data.level || 1;
this.score = data.score || 0;
}
preload() {
// Load level-specific assets
this.load.image(`level${this.level}`, `assets/level${this.level}.png`);
}
create() {
// Setup game objects
this.createPlayer();
this.createEnemies();
}
update(time: number, delta: number) {
this.updatePlayer();
this.updateEnemies();
}
}
Level 3: Game Config with Multiple Scenes
const config: Phaser.Types.Core.GameConfig = {
type: Phaser.AUTO,
width: 800,
height: 600,
parent: "game-container",
backgroundColor: "#2d2d2d",
scale: {
mode: Phaser.Scale.FIT,
autoCenter: Phaser.Scale.CENTER_BOTH,
width: 800,
height: 600,
},
physics: {
default: "arcade",
arcade: {
gravity: { x: 0, y: 1000 },
debug: false,
},
},
scene: [BootScene, PreloadScene, TitleScene, GameScene, UIScene],
pipeline: { CustomPipeline: CustomPipeline },
};
Level 4: Custom Game Config Extension
interface ExtendedGameConfig extends Phaser.Types.Core.GameConfig {
customSettings: {
maxPlayers: number;
gameMode: "deathmatch" | "capture";
};
}
class CustomGame extends Phaser.Game {
constructor(config: ExtendedGameConfig) {
super(config);
this.customSettings = config.customSettings;
}
}
Level 5: Scene Manager Control
export class MainScene extends Phaser.Scene {
create() {
// Scene transitions
this.scene.start("GameScene", { level: 1 });
// Launch parallel scene (UI overlay)
this.scene.launch("UIScene");
// Pause current scene
this.scene.pause();
// Sleep scene (stops update but keeps rendering)
this.scene.sleep("BackgroundScene");
// Stop and remove scene
this.scene.stop("OldScene");
}
}
Anti-Patterns
❌ DON'T:
- Load assets in
create()- usepreload() - Create new objects in
update()- causes GC pressure - Use
this.scene.restart()frequently - expensive operation - Forget to call
super()in scene constructor - Use hardcoded screen dimensions - use scale manager
- Mix physics and non-physics objects without planning
✅ DO:
- Preload all assets in
preload()before use - Reuse objects with object pooling in
update() - Use scene data for passing values between scenes
- Clean up event listeners in
shutdown()method - Use
scale.managerfor responsive sizing - Group related game objects with
this.add.group()
Code Patterns
Scene Lifecycle Management
export class GameScene extends Phaser.Scene {
private player!: Phaser.Physics.Arcade.Sprite;
private enemies!: Phaser.GameObjects.Group;
private cursors!: Phaser.Types.Input.Keyboard.CursorKeys;
constructor() {
super({ key: "GameScene" });
}
create() {
// Always setup shutdown for cleanup
this.events.once(Phaser.Scenes.Events.SHUTDOWN, this.shutdown, this);
}
shutdown() {
// Clean up listeners
this.input.keyboard!.off("keydown-ESC");
}
update(time: number, delta: number) {
// Use delta for frame-independent movement
const dt = delta / 1000; // Convert to seconds
}
}
Scale Manager Pattern
const config: Phaser.Types.Core.GameConfig = {
scale: {
mode: Phaser.Scale.FIT,
autoCenter: Phaser.Scale.CENTER_BOTH,
width: 800,
height: 600,
},
};
// In scene, get scale info
this.scale.width; // Actual canvas width
this.scale.height; // Actual canvas height
Checklist
Before completing Phaser setup:
- Game config has proper scale settings
- Physics configured correctly (arcade or matter)
- Scene keys are unique
- Assets preloaded before use in create()
- Update loop is performant (no object creation)
- Shutdown handlers for cleanup
- Responsive scaling configured
- Scene transitions use proper data passing
Common Issues
| Issue | Solution |
|---|---|
| Assets not loading | Check preload() runs before create() |
| Physics not working | Verify physics config and body type |
| Scene not updating | Check scene is active: true |
| Canvas size wrong | Configure scale manager |
| Memory leaks | Clean up in shutdown() |
Reference
- Phaser 3 Documentation — Official API docs
- Phaser Examples — Code examples
- Phaser TypeScript Types — Type definitions