# Phaser User Guide

Phaser is a fast, free, and fun open-source HTML5 game framework that renders with WebGL and Canvas across desktop and mobile web browsers. This guide is the complete, hands-on reference for building games with **Phaser 4** — from your first sprite on screen to physics, animation, input, audio, cameras, and a full multi-scene game loop.

You write your game in JavaScript or TypeScript, and Phaser handles the rendering, game loop, asset loading, input, sound, and physics for you. Games run in the browser and can be wrapped for iOS, Android, Steam, YouTube Playables, Discord Activities, and more using third-party tools.

> [!NOTE]
> This guide documents **Phaser 4.1.0** ("Salusa"). Phaser 4 is a major release built on a brand-new WebGL renderer. If you're coming from Phaser 3, the public API is mostly familiar, but several things changed under the hood — see [Migrating from Phaser 3](#migrating-from-phaser-3) near the end.

---

## Table of Contents

1. [Quick Start](#quick-start)
2. [Installation](#installation)
3. [Your First Game, Step by Step](#your-first-game-step-by-step)
4. [Core Concepts](#core-concepts)
5. [Configuring Your Game](#configuring-your-game)
6. [Working with Scenes](#working-with-scenes)
7. [Loading Assets](#loading-assets)
8. [Game Objects: Sprites and Images](#game-objects-sprites-and-images)
9. [Text and Bitmap Text](#text-and-bitmap-text)
10. [Animations](#animations)
11. [Tweens](#tweens)
12. [Arcade Physics](#arcade-physics)
13. [Input: Keyboard, Mouse, Touch, Gamepad](#input-keyboard-mouse-touch-gamepad)
14. [Cameras](#cameras)
15. [Audio and Sound](#audio-and-sound)
16. [Groups, Containers, and Layers](#groups-containers-and-layers)
17. [Common Workflows (Recipes)](#common-workflows-recipes)
18. [Advanced Topics](#advanced-topics)
19. [How It Works](#how-it-works)
20. [Migrating from Phaser 3](#migrating-from-phaser-3)
21. [Troubleshooting](#troubleshooting)
22. [Frequently Asked Questions](#frequently-asked-questions)
23. [Quick Reference](#quick-reference)

---

## Quick Start

The smallest possible Phaser game loads an image and displays it. Create an `index.html`:

```html
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>My Phaser Game</title>
    <script src="//cdn.jsdelivr.net/npm/phaser@4.1.0/dist/phaser.js"></script>
</head>
<body>
    <script>
        class MyScene extends Phaser.Scene {
            preload() {
                this.load.image('logo', 'assets/logo.png');
            }

            create() {
                this.add.image(400, 300, 'logo');
            }
        }

        const config = {
            type: Phaser.AUTO,
            width: 800,
            height: 600,
            scene: MyScene
        };

        const game = new Phaser.Game(config);
    </script>
</body>
</html>
```

Open `index.html` in a browser (served over HTTP — see the warning below) and you'll see your logo centered on an 800×600 canvas.

> [!WARNING]
> Loading assets from the local filesystem (`file://`) will usually fail because of browser CORS restrictions. Always run your game from a local web server. The quickest option is `npx serve` or `python3 -m http.server` in your project folder, then open `http://localhost:8000`.

> [!TIP]
> `Phaser.AUTO` tells Phaser to use WebGL if the browser supports it and fall back to Canvas if not. This is the recommended renderer setting for almost every game.

---

## Installation

There are three ways to add Phaser to a project: a CDN script tag, an npm install, or the official scaffolding tool. Pick the one that matches how you build.

=== "CDN (no build step)"

    Drop a script tag into your HTML. Phaser becomes available as the global `Phaser`.

    ```html
    <!-- Full, readable build -->
    <script src="//cdn.jsdelivr.net/npm/phaser@4.1.0/dist/phaser.js"></script>

    <!-- Minified production build -->
    <script src="//cdn.jsdelivr.net/npm/phaser@4.1.0/dist/phaser.min.js"></script>
    ```

    Phaser is also on Cloudflare's cdnjs:

    ```html
    <script src="https://cdnjs.cloudflare.com/ajax/libs/phaser/4.1.0/phaser.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/phaser/4.1.0/phaser.min.js"></script>
    ```

=== "npm (bundlers / frameworks)"

    Install from npm and import it into your project:

    ```bash
    npm install phaser
    ```

    ```js
    import Phaser from 'phaser';
    ```

    This works with Vite, Webpack, Rollup, Parcel, ESBuild, and every major front-end framework (React, Vue, Angular, Svelte, SolidJS, Next.js).

=== "create-phaser-game (scaffolding)"

    The fastest way to a fully configured project. The CLI presents an interactive selection of official templates and demo games:

    ```bash
    npm create @phaserjs/game@latest
    # or
    npx @phaserjs/create-game@latest
    # or with other package managers:
    yarn create @phaserjs/game
    pnpm create @phaserjs/game@latest
    bun create @phaserjs/game@latest
    ```

    Answer a few questions and it downloads and configures the right template for your chosen framework and bundler, in JavaScript or TypeScript.

### Build sizes

Don't be alarmed if the full `phaser.js` file is over 8 MB — over 84% of that is inline documentation (JSDoc comments and type annotations) that powers your IDE autocomplete and the TypeScript definitions. It is not shipped to your players.

| Build | Raw size | Compressed (gzip) |
|---|---|---|
| `phaser.js` (with docs) | 8.23 MB | — |
| `phaser.min.js` (full) | 1.29 MB | 345 KB |
| `phaser-arcade.min.js` | 1.18 MB | 313 KB |

The minified production build is **345 KB gzipped** with every feature included — smaller than most title-screen images.

### TypeScript

Full TypeScript definitions ship inside the package's `types` folder and are referenced by the `types` entry in `package.json`, so editors like VS Code detect them automatically. Depending on your project, you may need to add the following to `tsconfig.json`:

```json
{
    "compilerOptions": {
        "lib": ["es6", "dom", "dom.iterable", "scripthost"],
        "typeRoots": ["./node_modules/phaser/types"],
        "types": ["Phaser"]
    }
}
```

### Alternative builds

If you don't need every feature, smaller pre-built entry points reduce bundle size:

| Build file | What it includes |
|---|---|
| `phaser.js` | Everything (Arcade + Matter physics, all features) |
| `phaser-arcade-physics.js` | Excludes Matter.js physics (Arcade only) |
| `phaser-no-physics.js` | Excludes both Arcade and Matter physics |
| `phaser-core.js` | Minimal core (basic game objects, no Geom/Tilemaps/full math) |
| `phaser-esm.js` | ES module build for tree-shaking bundlers |

> [!TIP]
> If your game only uses Arcade Physics (the simpler, faster of the two physics systems), importing `phaser-arcade-physics.js` saves roughly 30 KB gzipped over the full build.

---

## Your First Game, Step by Step

This walkthrough builds a tiny but complete game: a player sprite you move with the arrow keys, with platforms and gravity. It introduces the four ideas you'll use in every Phaser project — the game config, scenes, the loader, and game objects.

### 1. Create the Game and a Scene

Everything starts with a **game config** passed to `new Phaser.Game()`, and at least one **Scene**.

```js
class GameScene extends Phaser.Scene {
    constructor() {
        super('GameScene');   // a unique key for this scene
    }

    preload() { /* load assets */ }
    create()  { /* build the level */ }
    update()  { /* per-frame logic */ }
}

const config = {
    type: Phaser.AUTO,
    width: 800,
    height: 600,
    physics: {
        default: 'arcade',
        arcade: { gravity: { y: 300 } }
    },
    scene: GameScene
};

const game = new Phaser.Game(config);
```

### 2. Load your assets in `preload()`

The `preload()` method queues files. Phaser downloads them all before `create()` runs.

```js
preload() {
    this.load.image('sky', 'assets/sky.png');
    this.load.image('ground', 'assets/platform.png');
    this.load.spritesheet('dude', 'assets/dude.png', {
        frameWidth: 32,
        frameHeight: 48
    });
}
```

### 3. Build the level in `create()`

```js
create() {
    // Background — top-left origin would be (0,0); image origin defaults to center
    this.add.image(400, 300, 'sky');

    // Static platforms the player can stand on
    this.platforms = this.physics.add.staticGroup();
    this.platforms.create(400, 568, 'ground').setScale(2).refreshBody();
    this.platforms.create(600, 400, 'ground');
    this.platforms.create(50, 250, 'ground');

    // The player — a physics sprite affected by gravity
    this.player = this.physics.add.sprite(100, 450, 'dude');
    this.player.setBounce(0.2);
    this.player.setCollideWorldBounds(true);

    // Make the player land on platforms
    this.physics.add.collider(this.player, this.platforms);

    // Read keyboard arrow keys
    this.cursors = this.input.keyboard.createCursorKeys();
}
```

### 4. Move the player in `update()`

```js
update() {
    if (this.cursors.left.isDown) {
        this.player.setVelocityX(-160);
    } else if (this.cursors.right.isDown) {
        this.player.setVelocityX(160);
    } else {
        this.player.setVelocityX(0);
    }

    // Jump only when touching the ground
    if (this.cursors.up.isDown && this.player.body.touching.down) {
        this.player.setVelocityY(-330);
    }
}
```

That's a working platformer skeleton. The rest of this guide expands each of these systems in depth.

> [!TIP]
> The official ["Making your first Phaser Game" tutorial](https://phaser.io/tutorials/making-your-first-phaser-3-game) walks through a complete version of this game with scoring and enemies. There are over 700 tutorials and 2,000+ code examples on the Phaser website.

---

## Core Concepts

Phaser is organized around a handful of concepts. Understanding these makes everything else fall into place.

### The Game

`Phaser.Game` is the single top-level controller. You create exactly one per game. It parses your config, creates the renderer, sets up all the global systems (input, sound, textures, animations, scenes), and runs the main loop. You rarely touch it directly after creation — you work through Scenes instead.

### Scenes

A **Scene** is the fundamental organizational unit. Think of scenes as the screens or states of your game: a boot scene, a preloader, a main menu, the gameplay level, a pause overlay, a game-over screen. Each Scene has:

- Its own lifecycle methods: `init()`, `preload()`, `create()`, `update()`.
- Its own display list, cameras, input handling, and loader.
- A set of **injected systems** accessed via `this.` (for example `this.add`, `this.load`, `this.physics`, `this.tweens`).

Multiple scenes can run at once — for example, a gameplay scene with a separate UI overlay scene on top.

### Game Objects

Anything you put on screen is a **Game Object**: a Sprite, an Image, Text, a Graphics object, a particle emitter, a tilemap layer. You create them through the **Game Object Factory** (`this.add.*`), which builds the object and adds it to the scene's display list.

```js
this.add.image(x, y, 'texture');     // a static image
this.add.sprite(x, y, 'texture');    // a sprite (supports animation)
this.add.text(x, y, 'Hello');        // canvas text
```

### The Game Loop

Every frame, Phaser:

1. Updates global managers (input, sound).
2. Calls `update(time, delta)` on each active scene.
3. Renders every visible scene through the renderer.

You put per-frame logic in your scene's `update()` method. The `delta` value (milliseconds since the last frame) lets you write frame-rate-independent movement.

### Textures and the Cache

Loaded assets live in **global caches** shared by every scene. Images and sprite sheets go into the **Texture Manager** (`this.textures`); JSON, audio, XML, and other data go into typed sub-caches under `this.cache` (`this.cache.json`, `this.cache.audio`, and so on). Because they're global, an image loaded in one scene is instantly available in every other scene.

### Coordinate System

- The canvas origin `(0, 0)` is the **top-left** corner; `x` increases right, `y` increases down.
- Most game objects have their **origin** (anchor point) at their center `(0.5, 0.5)` by default — so `this.add.image(400, 300, ...)` places the image's center at `(400, 300)`. (Text is the exception: its origin defaults to top-left.)
- Rotation is measured clockwise; `angle` is in degrees, `rotation` in radians.

---

## Configuring Your Game

The game config object you pass to `new Phaser.Game(config)` controls every fundamental aspect of your game. Most properties have sensible defaults, so you only set what you need.

### The most common options

```js
const config = {
    type: Phaser.AUTO,        // renderer: AUTO, WEBGL, CANVAS, or HEADLESS
    width: 800,               // canvas width in pixels (default 1024)
    height: 600,              // canvas height in pixels (default 768)
    parent: 'game-container', // id of the DOM element to inject the canvas into
    backgroundColor: '#2d2d2d',
    scene: [BootScene, GameScene],   // one scene or an array
    physics: {
        default: 'arcade',
        arcade: { gravity: { y: 300 }, debug: false }
    }
};
```

### Renderer types

| Constant | Value | Behavior |
|---|---|---|
| `Phaser.AUTO` | `0` | WebGL if supported, otherwise falls back to Canvas. **Recommended.** |
| `Phaser.CANVAS` | `1` | Force the Canvas renderer. |
| `Phaser.WEBGL` | `2` | Force WebGL. **No fallback** — fails if WebGL is unavailable. |
| `Phaser.HEADLESS` | `3` | No rendering (for unit testing; still requires a DOM). |

> [!CAUTION]
> Using `Phaser.WEBGL` directly will **not** fall back to Canvas if WebGL is unavailable, and the game can fail silently. Prefer `Phaser.AUTO` unless you have a specific reason to force a renderer.

### Pixel-art games

For crisp, non-blurry pixel art, set `pixelArt: true`. Phaser automatically disables texture smoothing and snaps to whole pixels.

```js
const config = {
    type: Phaser.AUTO,
    width: 320,
    height: 240,
    pixelArt: true,
    scale: {
        mode: Phaser.Scale.FIT,
        autoCenter: Phaser.Scale.CENTER_BOTH,
        zoom: Phaser.Scale.ZOOM_2X
    },
    scene: MyScene
};
```

Setting `pixelArt: true` automatically sets `antialias: false`, `antialiasGL: false`, and `roundPixels: true`.

> [!TIP]
> Phaser 4 adds `smoothPixelArt` (WebGL only). It preserves blocky pixels but smooths the edges between them when scaled up — a nicer look for some art styles. Setting it forces `antialias: true` and `pixelArt: false`.

### Scaling and responsiveness

The `scale` sub-object controls how the canvas resizes to fit its container. The base game size stays constant; the scale mode decides how it's displayed.

```js
const config = {
    type: Phaser.AUTO,
    scale: {
        mode: Phaser.Scale.FIT,
        parent: 'game-container',
        autoCenter: Phaser.Scale.CENTER_BOTH,
        width: 1280,
        height: 720,
        min: { width: 640, height: 360 },
        max: { width: 1920, height: 1080 }
    }
};
```

**Scale modes:**

| Constant | Behavior |
|---|---|
| `Phaser.Scale.NONE` | No scaling. Canvas stays at config size. |
| `Phaser.Scale.WIDTH_CONTROLS_HEIGHT` | Height adjusts based on width. |
| `Phaser.Scale.HEIGHT_CONTROLS_WIDTH` | Width adjusts based on height. |
| `Phaser.Scale.FIT` | Fit inside the parent, keeping aspect ratio (letterboxing). |
| `Phaser.Scale.ENVELOP` | Cover the parent, keeping aspect ratio (may overflow). |
| `Phaser.Scale.RESIZE` | Canvas resizes to fill the parent, ignoring aspect ratio. |
| `Phaser.Scale.EXPAND` | New in v4 — RESIZE-style visible area combined with FIT canvas scaling. |

**Center modes:** `Phaser.Scale.NO_CENTER`, `CENTER_BOTH`, `CENTER_HORIZONTALLY`, `CENTER_VERTICALLY`.

**Zoom constants:** `Phaser.Scale.NO_ZOOM` (1), `ZOOM_2X` (2), `ZOOM_4X` (4), `MAX_ZOOM` (-1, largest integer zoom that fits).

### Frame rate

```js
const config = {
    fps: {
        target: 60,            // advisory only
        limit: 30,             // actually caps the frame rate (0 = no cap)
        forceSetTimeOut: false,
        smoothStep: true
    }
};
```

> [!NOTE]
> `fps.target` is informational and does not enforce a frame rate. Use `fps.limit` to actually cap it. The browser's display refresh rate is always the upper bound — setting `limit: 120` on a 60 Hz display still gives 60 FPS.

### Transparent canvas over HTML

```js
const config = {
    transparent: true,    // forces backgroundColor to rgba(0,0,0,0)
    parent: 'game-container'
};
```

### Disabling input subsystems

```js
const config = {
    input: {
        keyboard: true,
        mouse: true,
        touch: false,
        gamepad: false,
        activePointers: 1
    },
    disableContextMenu: true   // disable right-click menu over the canvas
};
```

For the complete list of every config option and its default, see the [Admin Guide's Configuration Mastery section](phaserjs__phaser__AdminGuide.md).

---

## Working with Scenes

Scenes are where your game lives. This section covers the lifecycle, switching between scenes, running them in parallel, and passing data.

### The Scene lifecycle

A scene moves through a defined sequence of states. The methods you override are called automatically:

```js
class GameScene extends Phaser.Scene {
    constructor() {
        super('GameScene');
    }

    init(data) {
        // Called FIRST. Receives data passed from scene.start('GameScene', data).
        // Reset per-run state here (NOT in the constructor).
        this.level = data.level || 1;
        this.score = 0;
    }

    preload() {
        // Called after init. Queue assets to load.
        this.load.image('logo', 'assets/logo.png');
    }

    create(data) {
        // Called after preload completes. Build the scene.
        // 'data' is the same object passed to init.
        this.add.image(400, 300, 'logo');
    }

    update(time, delta) {
        // Called every frame while the scene is RUNNING.
    }
}
```

The full state progression: **PENDING → INIT → START → LOADING → CREATING → RUNNING**, with **PAUSED**, **SLEEPING**, **SHUTDOWN**, and **DESTROYED** as later states.

- **With a `preload()`:** `init()` → `preload()` → loader runs → `create()` → `update()` loop.
- **Without a `preload()`:** `init()` → `create()` → `update()` loop (loading is skipped).

> [!IMPORTANT]
> Reset state in `init()`, **not** the constructor. The constructor runs only once when the scene is first created. `init()` runs every time the scene starts or restarts — so it's the right place to zero out scores, flags, and arrays.

### Scene-injected properties

Every scene gets a rich set of systems injected as `this.` properties:

| Property | What it is |
|---|---|
| `this.add` | Game Object Factory — creates and adds objects (`this.add.sprite(...)`) |
| `this.make` | Game Object Creator — creates objects **without** adding them to the display list |
| `this.load` | Loader — queue and load assets |
| `this.input` | Input plugin — keyboard, mouse, touch, gamepad |
| `this.physics` | Arcade Physics (if configured) |
| `this.matter` | Matter Physics (if configured) |
| `this.tweens` | Tween Manager |
| `this.time` | Clock — timers and delayed calls |
| `this.cameras` | Camera Manager |
| `this.anims` | Global Animation Manager |
| `this.sound` | Sound Manager |
| `this.scene` | Scene Plugin — start/stop/launch other scenes |
| `this.data` | This scene's local Data Manager |
| `this.registry` | Global Data Manager (shared across all scenes) |
| `this.children` | This scene's display list |
| `this.events` | This scene's event emitter |

### Switching between scenes

The Scene Plugin (`this.scene`) controls all multi-scene orchestration.

```js
// start() — shuts down the current scene and starts the target
this.scene.start('LevelTwo', { score: 100 });

// restart() — shuts down and restarts the SAME scene
this.scene.restart({ score: 0 });

// switch() — sleeps the current scene (state preserved), starts/wakes the target
this.scene.switch('PauseMenu');

// transition() — animated transition over a duration
this.scene.transition({
    target: 'LevelTwo',
    duration: 1000,
    moveAbove: true,
    data: { score: 100 }
});
```

> [!WARNING]
> `this.scene.start('X')` **shuts down the calling scene**. If you want both scenes running at the same time, use `launch()` or `run()` instead.

### Running scenes in parallel

```js
// launch() — start another scene WITHOUT stopping this one
this.scene.launch('UIScene', { lives: 3 });

// run() — smart launcher: starts if not running, resumes if paused, wakes if sleeping
this.scene.run('UIScene');

// Control render order of parallel scenes
this.scene.bringToTop('UIScene');
this.scene.sendToBack('Background');
this.scene.moveAbove('GameScene', 'UIScene');
```

A classic pattern is a gameplay scene with a UI overlay launched alongside it:

```js
class UIScene extends Phaser.Scene {
    constructor() {
        super({ key: 'UIScene', active: true });   // active: true auto-starts it
    }

    create() {
        this.score = 0;
        this.scoreText = this.add.text(10, 10, 'Score: 0');

        // Listen for events from the gameplay scene
        const gameScene = this.scene.get('GameScene');
        gameScene.events.on('addScore', (points) => {
            this.score += points;
            this.scoreText.setText('Score: ' + this.score);
        });
    }
}
```

### Passing data between scenes

There are several ways to share data:

```js
// 1. Pass data on start/launch/restart (available in init AND create)
this.scene.start('LevelScene', { level: 5, score: 1200 });

// 2. The global registry — shared across ALL scenes
this.registry.set('playerHP', 100);
const hp = this.registry.get('playerHP');

// 3. This scene's local data manager
this.data.set('localValue', 42);

// 4. Direct reference to another scene
const other = this.scene.get('OtherScene');
other.somePublicProperty;

// 5. React to registry changes via events
this.registry.events.on('changedata-playerHP', (parent, value, prev) => {
    // update the UI
});
```

### Pausing, sleeping, and stopping

```js
this.scene.pause();    // stops update loop, still RENDERS
this.scene.resume();
this.scene.sleep();    // stops update AND render, keeps state in memory
this.scene.wake();
this.scene.stop();     // full shutdown, clears display list and timers

// State checks
this.scene.isActive('OtherScene');
this.scene.isPaused('OtherScene');
this.scene.isSleeping('OtherScene');
```

> [!NOTE]
> Paused scenes still render — `pause()` only stops the update loop. Use `sleep()` to stop both updating and rendering while preserving the scene's state.

### Cleaning up

Listen for the `shutdown` event to free resources that should be recreated on restart, and `destroy` for final cleanup:

```js
create() {
    this.events.once('shutdown', () => {
        this.enemies = [];   // clear arrays holding game object references
    });
}
```

> [!CAUTION]
> Stale references to destroyed game objects cause errors on scene restart. Always clear arrays and remove external event listeners on `shutdown`.

---

## Loading Assets

The Loader (`this.load`) fetches all external content — images, audio, JSON, tilemaps, fonts, atlases, and more. Assets are queued in `preload()`, downloaded in parallel, and placed into the global caches.

### The preload pattern

```js
preload() {
    this.load.image('sky', 'assets/sky.png');
    this.load.spritesheet('dude', 'assets/dude.png', { frameWidth: 32, frameHeight: 48 });
    this.load.audio('jump', 'assets/jump.mp3');
}

create() {
    // Everything above is guaranteed ready here
    this.add.image(400, 300, 'sky');
    this.add.sprite(100, 450, 'dude');
    this.sound.play('jump');
}
```

### Loading outside preload

If you queue files in `create()` or in response to a user action, the loader does **not** auto-start. Call `this.load.start()` yourself:

```js
create() {
    this.load.image('extra', 'assets/extra.png');
    this.load.once('complete', () => {
        this.add.image(400, 300, 'extra');
    });
    this.load.start();
}
```

### File types reference

Every `this.load` method accepts positional arguments or a single config object, and most accept an array to batch-load.

**Textures:** `image`, `spritesheet`, `atlas`, `atlasXML`, `multiatlas`, `unityAtlas`, `aseprite`, `svg`, `htmlTexture`, `texture` (compressed)
**Audio/Video:** `audio`, `audioSprite`, `video`
**Data:** `json`, `xml`, `text`, `binary`, `html`, `css`, `glsl`
**Fonts:** `bitmapFont`, `font`
**Tilemaps:** `tilemapTiledJSON`, `tilemapCSV`, `tilemapImpact`
**Other:** `animation`, `pack`, `script`, `scripts`, `plugin`, `scenePlugin`, `sceneFile`

### Common loads

```js
preload() {
    // Single image
    this.load.image('star', 'assets/star.png');

    // Sprite sheet (fixed-size frame grid)
    this.load.spritesheet('explosion', 'assets/explosion.png', {
        frameWidth: 64, frameHeight: 64, startFrame: 0, endFrame: 23
    });

    // Texture atlas (packed, named frames — e.g. from TexturePacker)
    this.load.atlas('sprites', 'assets/sprites.png', 'assets/sprites.json');

    // Audio with multiple formats for cross-browser support
    this.load.audio('bgm', ['assets/music.ogg', 'assets/music.mp3']);

    // JSON data
    this.load.json('levelData', 'assets/level1.json');

    // Tiled tilemap
    this.load.tilemapTiledJSON('map', 'assets/map.json');

    // Bitmap font (needs texture + XML/JSON font data)
    this.load.bitmapFont('pixels', 'assets/font.png', 'assets/font.xml');

    // Video
    this.load.video('intro', 'assets/intro.mp4');
}
```

### URL resolution

The final URL is `baseURL + path + filename`. Set them once and load relative paths:

```js
preload() {
    this.load.setBaseURL('https://cdn.example.com/');
    this.load.setPath('assets/images/');
    this.load.setPrefix('LEVEL1.');     // prepended to the CACHE KEY, not the URL

    // Loads from https://cdn.example.com/assets/images/hero.png, cached as 'LEVEL1.hero'
    this.load.image('hero', 'hero.png');

    // Absolute URLs bypass baseURL/path
    this.load.image('cloud', 'https://other.com/cloud.png');
}
```

### Loading progress bar

```js
preload() {
    const bar = this.add.graphics();

    this.load.on('progress', (value) => {
        bar.clear();
        bar.fillStyle(0xffffff, 1);
        bar.fillRect(250, 280, 300 * value, 30);   // value is 0..1
    });

    this.load.on('complete', () => bar.destroy());

    // ...queue your assets...
}
```

### Loader events

| Event | Fires when |
|---|---|
| `'start'` | Loader begins |
| `'progress'` | Overall progress updates (0–1) |
| `'fileprogress'` | Per-file download progress (if available) |
| `'load'` | A single file finishes downloading |
| `'filecomplete-{type}-{key}'` | A specific file is processed (e.g. `filecomplete-image-hero`) |
| `'loaderror'` | A file failed to load |
| `'complete'` | Everything in the queue is done |

> [!NOTE]
> While assets are loading, your scene's `update()` does **not** run (it's paused during preload). The `preupdate`, `postupdate`, and `render` events still fire, so you can animate a loading screen.

---

## Game Objects: Sprites and Images

Sprites and Images are the workhorses of most games. Both display a texture; the difference is that a **Sprite** can play frame-by-frame animations and an **Image** cannot.

### Image vs Sprite

| Feature | Image | Sprite |
|---|---|---|
| Display a texture | Yes | Yes |
| Tint, alpha, flip, scale, rotate | Yes | Yes |
| Physics body | Yes | Yes |
| Input / hit area | Yes | Yes |
| Frame-by-frame animation (`play`) | **No** | **Yes** |

> [!TIP]
> Use `Image` for anything that doesn't need to animate — backgrounds, static props, UI. It skips the per-frame animation update and is slightly cheaper. Reach for `Sprite` only when you need the Animation component.

### Creating and positioning

```js
// Static image
const bg = this.add.image(400, 300, 'background');

// Sprite (optionally with a starting frame)
const player = this.add.sprite(100, 200, 'player', 'idle-0');

// Position
player.setPosition(200, 300);
player.x = 250;
player.setRandomPosition();   // random within the game area

// Size
player.displayWidth = 128;    // scaled width (adjusts scaleX)
player.setDisplaySize(128, 128);
```

### Scaling and rotation

```js
player.setScale(2);           // uniform 2x
player.setScale(2, 1);        // stretch horizontally
player.scaleX = 1.5;

player.setAngle(45);          // degrees
player.setRotation(Math.PI);  // radians
player.angle += 1;            // spin a degree per frame in update()
```

### Tint and alpha

```js
// Tint (WebGL only) — multiply the texture color
player.setTint(0xff0000);                          // red tint
player.setTint(0xff0000, 0x00ff00, 0x0000ff, 0xffff00);  // per-corner
player.clearTint();

// Phaser 4 tint modes: MULTIPLY (default), FILL, ADD, SCREEN, OVERLAY, HARD_LIGHT
player.setTint(0xff0000).setTintMode(Phaser.TintModes.FILL);

// Alpha (transparency), 0..1
player.setAlpha(0.5);
player.alpha = 1;
```

> [!IMPORTANT]
> In Phaser 4, `setTintFill()` is removed. Use `setTint(color).setTintMode(Phaser.TintModes.FILL)` instead.

### Flipping

```js
player.setFlipX(true);     // face left
player.flipY = false;
player.setFlip(true, false);
player.toggleFlipX();
```

Flipping is rendering-only — it does not affect physics bodies or hit areas.

### Origin and depth

```js
// Origin is the anchor point, normalized 0..1 (default center 0.5, 0.5)
player.setOrigin(0, 0);      // top-left
player.setOrigin(0.5, 1);    // bottom-center (good for characters)

// Depth controls render order (higher = on top)
player.setDepth(10);
player.setToTop();           // move to top of the display list (no depth change)
```

### Visibility and scroll factor

```js
player.setVisible(false);    // skips rendering, still updates

// Scroll factor: 0 = fixed to the camera (HUD), 1 = moves with the world (default)
hudIcon.setScrollFactor(0);
```

### Chaining

Every setter returns the object, so you can chain:

```js
this.add.sprite(100, 100, 'coin')
    .setScale(0.5)
    .setTint(0xffff00)
    .setDepth(5)
    .play('spin');
```

### Other built-in game objects

Beyond Sprite and Image, the factory (`this.add.*`) creates many specialized objects:

| Factory call | What it makes |
|---|---|
| `this.add.text(x, y, str, style)` | Canvas-based styled text |
| `this.add.bitmapText(x, y, font, str, size)` | Fast pre-rendered bitmap text |
| `this.add.tileSprite(x, y, w, h, key)` | Repeating/scrolling texture (parallax) |
| `this.add.nineslice(...)` | Scalable UI panel with fixed corners |
| `this.add.graphics()` | Vector drawing (rectangles, circles, lines, paths) |
| `this.add.rectangle(x, y, w, h, color)` | A simple filled rectangle shape |
| `this.add.container(x, y)` | A transform-inheriting parent for children |
| `this.add.group()` | A logical collection / object pool |
| `this.add.layer()` | A render-order bucket |
| `this.add.particles(x, y, key, config)` | A particle emitter |
| `this.add.video(x, y, key)` | Video playback |
| `this.add.gradient(...)` | GPU color gradient (new in v4) |

The `this.make.*` equivalents create the same objects **without** adding them to the display list — useful when you want to configure an object before placing it, or build something off-screen.

---

## Text and Bitmap Text

Phaser offers two text systems with different trade-offs.

### Text vs BitmapText

| Feature | `Text` | `BitmapText` |
|---|---|---|
| Rendering | Canvas 2D, uploaded as a texture | Pre-rendered font texture |
| Web/CSS fonts | Yes | No (uses a bitmap font file) |
| Shadows, gradients, strokes | Yes | Drop shadow only (WebGL) |
| Word wrap | Built-in | `setMaxWidth` |
| Performance | Slower (re-renders on change) | Fast (batched) |

> [!TIP]
> Use `Text` for UI that needs flexible styling or web fonts and changes infrequently. Use `BitmapText` for scores, timers, damage numbers, and anything that updates every frame — it just updates vertex data instead of re-rendering a canvas.

### Canvas Text

```js
const title = this.add.text(400, 50, 'Hello World', {
    fontFamily: 'Arial',
    fontSize: '32px',
    color: '#ffffff',
    stroke: '#000000',
    strokeThickness: 4,
    shadow: { offsetX: 2, offsetY: 2, color: '#000', blur: 4, fill: true }
});

title.setOrigin(0.5);   // Text origin defaults to (0,0), so center it manually

// Update content
title.setText('Score: 100');
title.setText(['Line 1', 'Line 2']);   // array = multi-line

// Word wrap
const para = this.add.text(50, 50, longString, {
    fontSize: '20px', color: '#fff', wordWrap: { width: 300 }
});

// Restyle after creation (all setters chain)
title.setColor('#00ffff').setFontSize(48).setAlign('center');
```

> [!CAUTION]
> Phaser does **not** load web fonts for you. The Text object uses the browser's `fillText`, so the font must already be available via CSS `@font-face` or a font loader before you create the text. Font names with digits or spaces must be double-quoted inside the string: `fontFamily: '"Press Start 2P"'`.

### Bitmap Text

```js
// Load in preload: this.load.bitmapFont('pixelFont', 'font.png', 'font.xml');

const score = this.add.bitmapText(100, 100, 'pixelFont', 'Score: 0', 32);

score.setText('Score: 500');
score.setFontSize(48);
score.setCenterAlign();

// WebGL extras
score.setDropShadow(2, 2, 0x000000, 0.5);
score.setCharacterTint(0, 5, Phaser.TintModes.MULTIPLY, 0xff0000);
```

---

## Animations

Animation in Phaser means playing a sequence of texture frames over time on a Sprite. There are two pieces: the global **Animation Manager** (`this.anims`) where you define animations, and the per-sprite **Animation State** (`sprite.anims`) that controls playback.

### Defining and playing

```js
// In preload — load a spritesheet
this.load.spritesheet('dude', 'dude.png', { frameWidth: 32, frameHeight: 48 });

// In create — define a GLOBAL animation (available to all sprites)
this.anims.create({
    key: 'run',
    frames: this.anims.generateFrameNumbers('dude', { start: 0, end: 7 }),
    frameRate: 10,
    repeat: -1          // -1 = loop forever
});

// Play it on a sprite
const player = this.add.sprite(100, 300, 'dude');
player.play('run');
```

### Frames from a spritesheet vs an atlas

```js
// Spritesheet: numeric frame indices
frames: this.anims.generateFrameNumbers('dude', { start: 0, end: 7 })
frames: this.anims.generateFrameNumbers('dude', { frames: [0, 1, 2, 1] })  // custom order

// Atlas: string frame names
frames: this.anims.generateFrameNames('gems', {
    prefix: 'ruby_', start: 1, end: 6, zeroPad: 4   // ruby_0001 ... ruby_0006
})
```

### Controlling playback

```js
player.play('walk', true);        // ignore if already playing this anim
player.playReverse('walk');
player.anims.playAfterDelay('walk', 1000);
player.anims.pause();
player.anims.resume();
player.anims.stop();
player.chain('idle');             // play 'idle' after the current anim finishes
player.chain(['fall', 'land', 'idle']);
```

### Yoyo and repeat

```js
this.anims.create({
    key: 'pulse',
    frames: this.anims.generateFrameNumbers('orb', { start: 0, end: 5 }),
    frameRate: 10,
    yoyo: true,           // forward then backward
    repeat: -1,
    repeatDelay: 500
});
```

### Animation events

```js
player.on('animationcomplete', (anim) => {
    console.log('done:', anim.key);
});

// Key-specific: only fires for 'explode'
sprite.on('animationcomplete-explode', (anim, frame, gameObject) => {
    gameObject.destroy();
});
```

Available events: `animationstart`, `animationupdate`, `animationrepeat`, `animationcomplete`, `animationcomplete-{key}`, `animationstop`, `animationrestart`.

> [!IMPORTANT]
> An animation with `repeat: -1` never fires `animationcomplete` because it never ends. To end an infinite animation, call `stop()` and listen for `animationstop` instead.

> [!NOTE]
> Animations are global by default. Don't recreate the same `this.anims.create()` in every scene — it logs a warning and returns the existing one. Guard with `if (!this.anims.exists('walk')) { ... }` if you might run a scene's `create()` more than once.

---

## Tweens

A **tween** smoothly changes one or more properties of an object over time — position, scale, alpha, rotation, anything numeric. Tweens are perfect for UI motion, easing, and juicy effects.

### Basic tween

```js
const logo = this.add.image(100, 300, 'logo');

this.tweens.add({
    targets: logo,
    x: 600,
    duration: 2000,
    ease: 'Power2'
});
```

`this.tweens.add()` creates the tween and starts it immediately. Tweens **auto-destroy** when they complete — you don't need to keep a reference unless you want to control them later.

### Property values

```js
this.tweens.add({
    targets: sprite,
    x: 400,                  // absolute value
    y: '-=100',              // relative (subtract 100 from current)
    angle: '+=180',          // relative
    alpha: { value: 0, duration: 300, ease: 'Cubic.easeIn' },   // per-property config
    scale: [0.5, 1.5, 1],    // array: interpolate through these values
    duration: 1000
});
```

### Yoyo, repeat, and hold

```js
this.tweens.add({
    targets: coin,
    y: '-=50',
    duration: 600,
    ease: 'Sine.easeInOut',
    yoyo: true,        // return to the start value
    hold: 200,         // pause at the end before yoyo
    repeat: -1,        // infinite
    repeatDelay: 300
});
```

### Multiple targets and stagger

```js
this.tweens.add({
    targets: [a, b, c],
    y: 100,
    duration: 1000,
    delay: this.tweens.stagger(100)              // 100ms between each target
    // delay: this.tweens.stagger(200, { from: 'center' })
});
```

### Tween chains (sequences)

```js
this.tweens.chain({
    targets: player,
    tweens: [
        { x: 300, duration: 1000, ease: 'Power2' },
        { y: 500, duration: 800, ease: 'Bounce.easeOut' },
        { scale: 2, duration: 500 },
        { alpha: 0, duration: 400 }
    ],
    onComplete: () => console.log('chain done')
});
```

### Number tweens

Tween a plain number with no target object — great for counters:

```js
this.tweens.addCounter({
    from: 0,
    to: 100,
    duration: 2000,
    onUpdate: (tween) => {
        const value = Math.floor(tween.getValue());
        this.scoreText.setText('Score: ' + value);
    }
});
```

### Easing functions

Use a string name in the `ease` property (case-insensitive). Types: `Quad`, `Cubic`, `Quart`, `Quint`, `Sine`, `Expo`, `Circ`, `Elastic`, `Back`, `Bounce` — each with `.easeIn`, `.easeOut`, `.easeInOut` variants (the bare name defaults to Out). Plus `Linear`, `Stepped`, and the `Power0`–`Power4` aliases.

```js
ease: 'Sine.easeInOut'
ease: 'Bounce.easeOut'
ease: 'Back'                       // = Back.easeOut
ease: (t) => t * t                 // custom: t is 0..1
```

### Callbacks and control

```js
const tween = this.tweens.add({
    targets: sprite, x: 600, duration: 2000, persist: true,
    onStart: () => {}, onComplete: () => {}
});

tween.pause();   tween.resume();   tween.restart();   tween.stop();
this.tweens.killTweensOf(sprite);
```

> [!NOTE]
> Tweens auto-destroy after completing. If you need to replay one, set `persist: true` (then call `tween.destroy()` yourself when done). A tween skips any property whose name starts with `_`.

---

## Arcade Physics

Arcade Physics is Phaser's fast, lightweight physics system built for AABB (axis-aligned bounding box) and circular collisions. It's ideal for platformers, top-down games, and arcade-style action. (Phaser also ships Matter.js for full rigid-body physics — see the Admin Guide.)

### Enabling physics

Turn it on in the game config:

```js
const config = {
    physics: {
        default: 'arcade',
        arcade: {
            gravity: { y: 300 },
            debug: false
        }
    }
};
```

### Creating physics objects

Use `this.physics.add.*` to create objects with bodies:

```js
const player = this.physics.add.sprite(100, 300, 'player');   // dynamic body
const coin = this.physics.add.image(300, 100, 'coin');        // dynamic body
const wall = this.physics.add.staticImage(400, 300, 'wall');  // static body
const platforms = this.physics.add.staticGroup();             // static group

// Add a body to an existing object
this.physics.add.existing(mySprite);          // dynamic
this.physics.add.existing(mySprite, true);     // static
```

### Velocity, acceleration, gravity

```js
player.setVelocity(200, -300);        // x and y pixels/sec
player.setVelocityX(160);
player.setAcceleration(100, 0);
player.setMaxVelocity(300, 600);

player.setBounce(0.2);                // rebound factor
player.setDrag(300);                  // deceleration when acceleration is 0
player.setCollideWorldBounds(true);   // stay inside the world

player.body.gravity.y = 200;          // extra per-body gravity
player.body.allowGravity = false;     // exempt from world gravity
```

### Collisions and overlaps

There are two checks: **collide** separates the bodies (pushes them apart); **overlap** only detects intersection without moving anything (use it for pickups and triggers).

```js
// Persistent collider — checked automatically every frame (preferred)
this.physics.add.collider(player, platforms);

// Persistent overlap with a callback
this.physics.add.overlap(player, coins, collectCoin, null, this);

function collectCoin(player, coin) {
    coin.disableBody(true, true);   // disable the body and hide the coin
}

// Self-collision within a group
this.physics.add.collider(enemies, enemies);

// One-shot checks (call each frame in update)
this.physics.collide(player, platforms);
this.physics.overlap(player, coins, collectCoin);
```

> [!TIP]
> A persistent `this.physics.add.collider()` is checked automatically every frame. `this.physics.collide()` is a one-shot that you must call yourself every frame in `update()`. Prefer the persistent collider for things that always collide.

### Physics groups

```js
// Dynamic group — members get dynamic bodies automatically
const bullets = this.physics.add.group({
    maxSize: 20,
    collideWorldBounds: true,
    allowGravity: false
});

// Static group — immovable platforms
const platforms = this.physics.add.staticGroup();
platforms.create(400, 568, 'ground').setScale(2).refreshBody();
```

> [!IMPORTANT]
> After changing the position, scale, or origin of a **static** body's game object, you must call `gameObject.refreshBody()` (or `body.reset()`). Static bodies do not auto-sync to transform changes.

### World bounds and events

```js
this.physics.world.setBounds(0, 0, 1600, 1200);

// Physics events require opt-in per body
player.body.onWorldBounds = true;
this.physics.world.on('worldbounds', (body, up, down, left, right) => {
    console.log('hit edge');
});
```

> [!NOTE]
> World events (`'collide'`, `'overlap'`, `'worldbounds'`) only fire if you set the relevant body flag (`onCollide`, `onOverlap`, `onWorldBounds`) to `true`.

### Useful helpers

```js
this.physics.moveTo(sprite, targetX, targetY, 200);          // move toward a point at speed
this.physics.accelerateTo(sprite, x, y, 300);
this.physics.velocityFromAngle(angle, speed, sprite.body.velocity);
const nearest = this.physics.closest(source, targets);
```

> [!CAUTION]
> `arcade.debug: true` draws body outlines and velocity vectors — invaluable while developing, but expensive. Always set it to `false` for release builds.

---

## Input: Keyboard, Mouse, Touch, Gamepad

Phaser provides a unified input system via `this.input` in any scene. You can handle input with event listeners or by polling state each frame.

### Keyboard

```js
create() {
    // Cursor keys: { up, down, left, right, space, shift } Key objects
    this.cursors = this.input.keyboard.createCursorKeys();

    // Specific keys
    this.keyW = this.input.keyboard.addKey('W');
    this.keys = this.input.keyboard.addKeys('W,A,S,D');

    // Custom-named mapping
    this.wasd = this.input.keyboard.addKeys({
        up: Phaser.Input.Keyboard.KeyCodes.W,
        left: Phaser.Input.Keyboard.KeyCodes.A,
        down: Phaser.Input.Keyboard.KeyCodes.S,
        right: Phaser.Input.Keyboard.KeyCodes.D
    });

    // Event-driven
    this.input.keyboard.on('keydown-SPACE', (event) => { /* fire */ });
}

update() {
    // Polling — true every frame the key is held
    if (this.cursors.left.isDown) { /* move left */ }

    // JustDown — true only once per press
    if (Phaser.Input.Keyboard.JustDown(this.cursors.space)) { /* jump */ }
}
```

> [!TIP]
> To stop the browser scrolling when arrow keys or space are pressed, capture them: `this.input.keyboard.addCapture('SPACE,UP,DOWN,LEFT,RIGHT')`.

### Mouse and pointer

```js
// Scene-wide pointer events (anywhere on the canvas)
this.input.on('pointerdown', (pointer) => {
    console.log('clicked at', pointer.x, pointer.y);
    if (pointer.rightButtonDown()) { /* right click */ }
});
this.input.on('pointermove', (pointer) => {});
this.input.on('wheel', (pointer, over, dx, dy, dz) => {});

// Make a specific object clickable
const button = this.add.sprite(400, 300, 'button');
button.setInteractive();
button.on('pointerdown', (pointer, localX, localY, event) => {});
button.on('pointerover', () => button.setTint(0xcccccc));
button.on('pointerout', () => button.clearTint());
```

Custom hit areas and pixel-perfect hit testing:

```js
sprite.setInteractive(new Phaser.Geom.Circle(32, 32, 32), Phaser.Geom.Circle.Contains);
sprite.setInteractive({ useHandCursor: true });
sprite.setInteractive(this.input.makePixelPerfect());   // expensive — use sparingly
```

### Drag and drop

```js
const item = this.add.sprite(400, 300, 'item').setInteractive();
this.input.setDraggable(item);

this.input.on('dragstart', (pointer, obj) => obj.setTint(0xff0000));
this.input.on('drag', (pointer, obj, dragX, dragY) => {
    obj.x = dragX;
    obj.y = dragY;
});
this.input.on('dragend', (pointer, obj) => obj.clearTint());

// Drop zones
const zone = this.add.zone(600, 300, 200, 200).setRectangleDropZone(200, 200);
this.input.on('drop', (pointer, obj, dropZone) => {
    obj.x = dropZone.x;
    obj.y = dropZone.y;
});
```

### Touch

Touch works through the same pointer events. For multi-touch, reserve extra pointers:

```js
this.input.addPointer(2);   // now pointer1..pointer3 available (mouse is mousePointer)
```

### Gamepad

Enable it in the config (`input: { gamepad: true }`), then:

```js
this.input.gamepad.once('connected', (pad) => {
    console.log('connected:', pad.id);
});

update() {
    const pad = this.input.gamepad.pad1;
    if (!pad) return;

    if (pad.left)  { /* d-pad left */ }
    if (pad.A)     { /* bottom face button */ }
    if (pad.R2 > 0) { /* right trigger, 0..1 */ }

    const lx = pad.leftStick.x;   // -1..1
    const ly = pad.leftStick.y;
}
```

---

## Cameras

Every scene has a Camera Manager (`this.cameras`) and a default main camera (`this.cameras.main`). Cameras control what part of your game world is visible and where it renders on screen.

### Scroll, zoom, and centering

```js
const cam = this.cameras.main;

cam.setScroll(200, 100);      // look at a different part of the world
cam.centerOn(400, 300);       // center on a world coordinate
cam.setZoom(2);               // 2x zoom in (< 1 zooms out)
cam.setBounds(0, 0, 2048, 2048);   // constrain scrolling to the world
```

### Following a target

```js
// Instant follow
cam.startFollow(player);

// Smooth follow with lerp (lower = smoother), and an offset
cam.startFollow(player, true, 0.08, 0.08, 0, -50);

// Dead zone: camera only scrolls when the target leaves this rectangle
cam.setDeadzone(200, 150);

cam.stopFollow();
```

### Camera effects

All effects return the camera for chaining:

```js
cam.fadeIn(1000);                 // fade in from black
cam.fadeOut(1000, 0, 0, 0);       // fade out to black (r,g,b)
cam.flash(250);                   // white flash
cam.shake(300, 0.02);             // screen shake (duration, intensity)
cam.pan(800, 600, 2000, 'Sine.easeInOut');
cam.zoomTo(2, 1000);
```

Listen for completion:

```js
cam.on(Phaser.Cameras.Scene2D.Events.FADE_OUT_COMPLETE, () => {
    this.scene.start('GameOver');
});
```

### Multiple cameras (HUD / minimap)

```js
const main = this.cameras.main;

// A minimap in the corner
const minimap = this.cameras.add(600, 0, 200, 150).setZoom(0.2).setName('minimap');

// A non-scrolling HUD camera
const hud = this.cameras.add(0, 0, 800, 600).setScroll(0, 0);

// Each camera ignores the other's objects
main.ignore(hudGroup);
hud.ignore(worldGroup);
```

### Screen-to-world conversion

When the camera is scrolled or zoomed, raw pointer coordinates are screen space. Convert them:

```js
const worldPoint = cam.getWorldPoint(pointer.x, pointer.y);
// or use pointer.worldX / pointer.worldY directly
```

> [!CAUTION]
> Never set camera zoom to exactly `0` — it's clamped to a minimum of `0.001` but a zero zoom is a logic error.

---

## Audio and Sound

Phaser's sound system (`this.sound`) abstracts over the Web Audio API and HTML5 Audio, automatically choosing the best backend and handling mobile autoplay restrictions.

### Loading and playing

```js
preload() {
    this.load.audio('coin', ['assets/coin.ogg', 'assets/coin.mp3']);
    this.load.audio('bgm', 'assets/music.mp3');
}

create() {
    // Fire-and-forget (auto-destroys when done)
    this.sound.play('coin');
    this.sound.play('coin', { volume: 0.5, rate: 1.2 });

    // Retained reference for ongoing control
    this.music = this.sound.add('bgm', { loop: true, volume: 0.4 });
    this.music.play();
}
```

> [!TIP]
> Always provide multiple audio formats (OGG + MP3 at minimum). No single format works in every browser — Phaser picks the first one the browser supports. MP3 has the broadest support; OGG lacks Safari support; AAC/M4A works well on Safari/iOS.

### Controlling sound

```js
this.music.setVolume(0.5);
this.music.setRate(1.5);          // 0.5 = half speed, 2 = double
this.music.setLoop(true);
this.music.setPan(-1);            // -1 left, 0 center, 1 right (WebAudio)
this.music.pause();
this.music.resume();
this.music.stop();

// Global controls (affect all sounds)
this.sound.volume = 0.8;
this.sound.setMute(true);
this.sound.stopAll();
```

### Mobile autoplay

Browsers block audio until the user interacts. Phaser handles unlocking automatically, but if you want to start music immediately on load, check the locked state:

```js
if (this.sound.locked) {
    this.sound.once('unlocked', () => this.sound.play('bgm'));
} else {
    this.sound.play('bgm');
}
```

> [!NOTE]
> There is **one** SoundManager per game, shared by all scenes. Looping sounds continue across scene changes unless you explicitly stop them. Sounds are not auto-cleaned on scene shutdown — stop or remove them yourself if needed.

### Spatial audio (WebAudio only)

```js
this.sound.setListenerPosition(player.x, player.y);
const roar = this.sound.add('roar', {
    source: { x: 800, y: 300, refDistance: 50, maxDistance: 2000, follow: enemySprite }
});
roar.play();
```

---

## Groups, Containers, and Layers

These three constructs organize collections of game objects, but they serve different purposes.

| | Group | Container | Layer |
|---|---|---|---|
| Purpose | Logical collection / object pool | Visual parent with shared transform | Render-order bucket |
| Has position/scale/rotation | No | Yes (children inherit) | No |
| On the display list | No (children are) | Yes (renders children) | Yes (renders children) |
| Object pooling | Yes | No | No |
| Factory | `this.add.group()` | `this.add.container(x, y)` | `this.add.layer()` |

### Groups (and object pooling)

Use a Group to manage many similar objects — enemies, bullets, coins — and to **pool** them (reuse inactive objects instead of creating/destroying).

```js
const bullets = this.add.group({
    classType: Phaser.GameObjects.Sprite,
    defaultKey: 'bullet',
    maxSize: 30
});

function fireBullet(x, y) {
    const bullet = bullets.get(x, y);   // finds an inactive member or creates one
    if (bullet) {
        bullet.setActive(true).setVisible(true);
        bullet.body.velocity.y = -300;
    }
}

function killBullet(bullet) {
    bullets.killAndHide(bullet);        // active=false, visible=false, kept for reuse
}
```

Batch operations across the whole group:

```js
enemies.setVisible(false);
enemies.setTint(0xff0000);
enemies.playAnimation('walk');
enemies.incX(5);                  // add 5 to every member's x
enemies.setXY(100, 200, 50, 0);   // first at (100,200), each +50 x
```

> [!NOTE]
> A Group is **not** on the display list — its children render individually. Moving a Group does nothing visually. If you need children to move together, use a Container.

### Containers

A Container is a visual parent: its children inherit its position, rotation, scale, and alpha. Great for composite UI (health bars, inventory slots) and clusters that move as one.

```js
const hud = this.add.container(10, 10);
hud.setScrollFactor(0);   // pin to the camera

const bar = this.add.rectangle(0, 0, 200, 20, 0x00ff00);
const label = this.add.text(210, -5, '100 HP');
hud.add([bar, label]);

hud.setPosition(50, 50);   // moves everything
hud.setScale(1.5);          // scales children
hud.setAlpha(0.8);          // fades all children
```

> [!IMPORTANT]
> A Container's origin is always `(0, 0)` and cannot be changed — position children relative to that. Containers have a per-frame matrix cost per child, so prefer a Group or Layer when you don't need a shared transform. For input, you must call `container.setSize(w, h)` before `setInteractive()`.

### Layers

A Layer is a lightweight render-order bucket with no transform. Use it to control which batches of objects draw on top of which.

```js
const bgLayer = this.add.layer();
const entityLayer = this.add.layer();
const uiLayer = this.add.layer();

bgLayer.add(this.add.image(400, 300, 'sky'));
entityLayer.add(player);
uiLayer.add(scoreText);

bgLayer.setDepth(0);
entityLayer.setDepth(1);
uiLayer.setDepth(2);
```

---

## Common Workflows (Recipes)

Practical patterns that combine the systems above.

### A boot → preload → menu → game flow

```js
const config = {
    type: Phaser.AUTO,
    width: 800,
    height: 600,
    scene: [BootScene, PreloadScene, MenuScene, GameScene]
};
```

Only the first scene in the array starts automatically. `BootScene` configures global settings, `PreloadScene` loads all assets with a progress bar, then `this.scene.start('MenuScene')`.

### A score that updates a separate UI scene

```js
// GameScene emits an event
this.events.emit('addScore', 10);

// UIScene (launched in parallel) listens
const game = this.scene.get('GameScene');
game.events.on('addScore', (points) => {
    this.score += points;
    this.scoreText.setText('Score: ' + this.score);
});
```

### Spawning enemies on a timer

```js
create() {
    this.time.addEvent({
        delay: 1000,
        loop: true,
        callback: () => {
            const x = Phaser.Math.Between(0, 800);
            this.enemies.get(x, 0);
        }
    });
}
```

### A pause overlay

```js
// In GameScene
this.input.keyboard.on('keydown-P', () => {
    this.scene.pause();
    this.scene.launch('PauseMenu');
});

// In PauseMenu
this.input.keyboard.on('keydown-P', () => {
    this.scene.stop();
    this.scene.resume('GameScene');
});
```

### Parallax scrolling background

```js
create() {
    this.bg = this.add.tileSprite(400, 300, 800, 600, 'clouds');
}

update() {
    this.bg.tilePositionX += 0.5;   // scroll the texture
}
```

### Flash and respawn on hit

```js
function onPlayerHit(player, enemy) {
    this.cameras.main.shake(200, 0.01);
    player.setTint(0xff0000);
    this.time.delayedCall(500, () => player.clearTint());
}
```

---

## Advanced Topics

### Filters (post-processing effects)

Phaser 4 unifies the old FX and Mask systems into a single **Filter** system. Filters can be applied to any game object or camera (WebGL only). Each filter has two lists: `internal` (object-local, cheaper) and `external` (screen-space, full resolution).

```js
sprite.enableFilters();
sprite.filters.internal.addGlow(0xff00ff, 4, 0, 1);
sprite.filters.external.addBlur(0, 2, 2, 1);

// Filters on a camera affect the whole view
this.cameras.main.filters.external.addBlur(1, 2);

// Masks are now a filter too
sprite.filters.internal.addMask(maskImage);
```

Built-in filters include Blur, Glow, Shadow, Pixelate, ColorMatrix, Bloom, Vignette, Wipe, Blend, ImageLight, Blocky, GradientMap, Quantize, and Key (chroma keying). See the [Admin Guide](phaserjs__phaser__AdminGuide.md) for the full list.

### Rendering massive numbers of objects

Phaser 4 adds two GPU-accelerated game objects for extreme counts:

- **`SpriteGPULayer`** renders **a million or more** sprites in a single draw call (up to 100x faster than individual sprites) by storing all member data in a static GPU buffer. Each member supports GPU-driven animations and per-member scroll factors for parallax.
- **`TilemapGPULayer`** renders an entire tilemap layer as a single quad — cost is per-pixel, not per-tile — so it can display up to 4096×4096 tiles with no penalty for tile count.

```js
const layer = this.add.spriteGPULayer('stars', 1000000);
const member = { x: 100, y: 200, frame: 'star', scaleX: 1, scaleY: 1, alpha: 1 };
layer.addMember(member);
```

### Tilemaps

Load a Tiled JSON map and build layers:

```js
preload() {
    this.load.image('tiles', 'assets/tileset.png');
    this.load.tilemapTiledJSON('map', 'assets/level.json');
}

create() {
    const map = this.make.tilemap({ key: 'map' });
    const tileset = map.addTilesetImage('tilesetName', 'tiles');
    const ground = map.createLayer('Ground', tileset, 0, 0);

    ground.setCollisionByProperty({ collides: true });
    this.physics.add.collider(this.player, ground);

    // GPU layer for huge maps (pass true as the last argument)
    const gpuGround = map.createLayer('Ground', tileset, 0, 0, true);
}
```

### Particles

```js
const emitter = this.add.particles(400, 300, 'spark', {
    speed: { min: -200, max: 200 },
    lifespan: 1000,
    scale: { start: 1, end: 0 },
    blendMode: 'ADD'
});
emitter.explode(50);   // burst of 50
```

### Custom plugins

Register global or per-scene plugins via the config `plugins` object to extend Phaser with reusable systems (UI controls, state machines, etc.). The community maintains a large catalogue of plugins (see RexRainbow's collection).

### Lighting

```js
sprite.setLighting(true);                  // v4 — no pipeline juggling
sprite.setSelfShadow(true, 0.5, 1/3);       // cast self-shadows from texture brightness
```

Lighting works across most game objects including BitmapText, Particles, TileSprite, and both tilemap layer types.

---

## How It Works

A conceptual look at what Phaser does for you, useful when debugging.

### The boot sequence

When you call `new Phaser.Game(config)`:

1. The config is parsed into a read-only `Config` instance (available later as `game.config`).
2. Global managers are created: AnimationManager, TextureManager, CacheManager, InputManager, SceneManager, ScaleManager, SoundManager, TimeStep (the game loop), and PluginManager.
3. Phaser waits for `DOMContentLoaded`, then boots: creates the renderer, adds the canvas to the DOM, prints the console banner, and emits `BOOT`.
4. Once textures are ready it emits `READY` and starts the main loop.

You can hook into boot with `callbacks.preBoot` and `callbacks.postBoot` in the config, and listen for lifecycle events on `game.events` (`'blur'`, `'focus'`, `'hidden'`, `'visible'`, `'pause'`, `'resume'`).

### The per-frame step

Each animation frame, the TimeStep calls the game step, which:

1. Emits `PRE_STEP` and `STEP` (global managers like input and sound update here).
2. Updates the Scene Manager — each active scene's `update(time, delta)` runs (scenes update in **reverse** array order, so the top scene gets input priority first).
3. Emits `POST_STEP`, then renders every visible scene in **forward** array order (so the top scene draws last, on top).

### Why scenes update reverse but render forward

The top-most scene (last in the array) updates first so it can consume input before lower scenes, but renders last so it appears on top. This is why a UI overlay scene placed last in the config array sits above and intercepts input ahead of the gameplay scene.

### The texture and cache model

Loaded images become GPU textures in the Texture Manager; everything else lands in typed sub-caches. Because these are owned by the Game (not a scene), assets persist across scene changes and are shared globally. This is why you load assets once in a preloader scene and use them everywhere.

---

## Migrating from Phaser 3

Phaser 4 keeps most of the public API you know, but there are important breaking changes. If you only use the standard API (Sprites, Text, Tilemaps, Arcade Physics, tweens), most code works unchanged. The big-ticket items:

| Area | What changed |
|---|---|
| **Renderer** | The v3 `Pipeline` system is replaced by a node-based `RenderNode` architecture. Custom pipelines must be rewritten as render nodes. |
| **FX & Masks** | Unified into one **Filter** system. `preFX`/`postFX` → `filters.internal`/`filters.external`. `BitmapMask` → the `Mask` filter. |
| **Tint** | `setTintFill()` removed → use `setTint(color).setTintMode(Phaser.TintModes.FILL)`. |
| **Geometry** | `Geom.Point` removed → use `Vector2`. All geometry helpers now return `Vector2`. |
| **Math** | `Math.TAU` now equals `PI * 2` (was `PI / 2`). Use the new `Math.PI_OVER_2` for the old value. `Math.PI2` removed. |
| **Data structures** | `Phaser.Struct.Set`/`Map` replaced by native JS `Set`/`Map`. |
| **Lighting** | `sprite.setPipeline('Light2D')` → `sprite.setLighting(true)`. |
| **Round pixels** | `roundPixels` now defaults to `false` (was `true`). |
| **Removed objects** | `Mesh` and `Plane` removed (proper 3D planned for the future). |
| **DynamicTexture / RenderTexture** | Must now call `render()` to execute buffered drawing commands. |

> [!IMPORTANT]
> Phaser 4 ships a detailed Migration Guide and an AI agent skill dedicated to v3→v4 migration. Point your AI coding assistant at the repository's `skills/v3-to-v4-migration` folder and it can perform much of the conversion automatically.

There's a full migration checklist in the v4.0 Migration Guide that ships with the framework.

---

## Troubleshooting

### Error: `Cannot create WebGL context, aborting.`

**Cause:** You forced `type: Phaser.WEBGL` but the browser or device cannot create a WebGL context.

**Solution:** Switch to `type: Phaser.AUTO` so Phaser falls back to Canvas automatically.

**Prevention:** Only force `Phaser.WEBGL` when you specifically require WebGL features and have a fallback message for unsupported browsers.

### Error: Image / texture shows as a green-and-black box

**Cause:** The texture key referenced in `this.add.image(x, y, 'key')` was never loaded, failed to load, or is misspelled. Phaser substitutes its built-in "missing texture" placeholder.

**Solution:** Verify the key matches exactly (keys are case-sensitive) and that the file path resolves. Check the Network tab for 404s and listen for `this.load.on('loaderror', file => console.warn(file.key))`.

**Prevention:** Run your game from a web server (not `file://`) and keep a single source of truth for asset keys.

### Error: Assets never load and `create()` never runs

**Cause:** You queued files outside `preload()` (e.g. in `create()`) and didn't start the loader, or you're loading from `file://` and CORS blocks the requests.

**Solution:** Call `this.load.start()` after queueing outside preload. Serve over HTTP: `npx serve` or `python3 -m http.server`.

**Prevention:** Do all initial loading in `preload()`, which auto-starts the loader.

### Error: `Cannot add Scene with duplicate key: X`

**Cause:** Two scenes were registered with the same key, or you added a scene whose constructor sets a key that collides.

**Solution:** Give every scene a unique key in its `super('Key')` call or scene config.

**Prevention:** Keep a constants file listing every scene key.

### Error: Sound doesn't play until I click

**Cause:** Browser autoplay policy blocks audio until the first user interaction. This is expected behavior, not a bug.

**Solution:** Trigger audio from a user action, or wait for the `unlocked` event: `this.sound.once('unlocked', () => this.sound.play('bgm'))`.

**Prevention:** Design your menu so the first sound plays on a button press.

### Error: A static platform doesn't collide where it's drawn

**Cause:** You scaled or moved a static-body game object without refreshing its body, so the physics body is still at the old size/position.

**Solution:** Call `platform.refreshBody()` (or `body.reset()`) after changing a static object's transform.

**Prevention:** Chain it at creation: `group.create(x, y, 'ground').setScale(2).refreshBody()`.

### Error: `update()` doesn't run during loading

**Cause:** A scene's `update()` is intentionally paused while its loader is running.

**Solution:** Animate your loading screen using the loader's `progress` event instead of `update()`.

**Prevention:** Keep preload-time visuals event-driven.

### Error: My HUD scrolls away with the world

**Cause:** HUD objects have the default scroll factor of `1`, so they move with the camera.

**Solution:** Set `hudObject.setScrollFactor(0)`, or render the HUD in a separate camera that doesn't scroll.

**Prevention:** Build HUDs in a dedicated UI scene or container pinned with `setScrollFactor(0)`.

### Error: Animation plays once and never repeats

**Cause:** The animation's `repeat` is `0` (the default), so it plays a single time.

**Solution:** Set `repeat: -1` for an infinite loop in the `this.anims.create()` config.

**Prevention:** Decide loop behavior when defining the animation.

### Error: Tint or filter has no effect

**Cause:** Tint, per-corner alpha, and filters are **WebGL-only** features. You're running under the Canvas renderer.

**Solution:** Use `type: Phaser.AUTO` or `Phaser.WEBGL` so WebGL is active when available.

**Prevention:** Don't force `Phaser.CANVAS` unless you specifically need it.

### Error: `this.physics` is undefined

**Cause:** Physics wasn't enabled in the game config, so the plugin wasn't installed into the scene.

**Solution:** Add `physics: { default: 'arcade', arcade: {} }` to your config.

**Prevention:** Enable physics in the config before using `this.physics`.

### Error: Restarting a scene keeps old state / throws on destroyed objects

**Cause:** State was initialized in the constructor (runs once) instead of `init()`, or stale references to destroyed objects survived the restart.

**Solution:** Move state resets into `init()` and clear object references on the `shutdown` event.

**Prevention:** Treat the constructor as one-time setup and `init()` as per-run setup.

### Error: Text using a custom web font shows the wrong font

**Cause:** The font wasn't loaded by the browser before the Text object was created — Phaser doesn't load web fonts.

**Solution:** Load the font via CSS `@font-face` or a web font loader and wait for it to be ready before creating the Text. Quote font names with spaces: `fontFamily: '"Press Start 2P"'`.

**Prevention:** Preload web fonts in your boot scene.

---

## Frequently Asked Questions

**Q: Do I need a build step to use Phaser?**
No. You can use the CDN script tag with plain HTML and JavaScript. A bundler (via npm) is recommended for larger projects and TypeScript, but it's optional.

**Q: JavaScript or TypeScript?**
Both are fully supported. Phaser ships first-class TypeScript definitions. The `create-phaser-game` scaffolder offers both for most templates.

**Q: How do I make my game full-screen / responsive?**
Use the `scale` config with `mode: Phaser.Scale.FIT` (keeps aspect ratio) or `RESIZE` (fills the parent). For true fullscreen mode, call `this.scale.startFullscreen()` from a user gesture.

**Q: What's the difference between `this.add` and `this.make`?**
`this.add.sprite(...)` creates the object **and** adds it to the scene's display list. `this.make.sprite(...)` creates it without adding it — useful for off-screen construction or configuring before placement.

**Q: Why is my image positioned by its center?**
Game objects default to a center origin `(0.5, 0.5)`. Call `setOrigin(0, 0)` for top-left positioning. (Text is the exception — it defaults to top-left.)

**Q: When should I use Arcade Physics vs Matter.js?**
Use **Arcade** for platformers, top-down, and arcade action — it's fast and simple (AABB + circles). Use **Matter.js** when you need realistic rigid-body physics: stacking, slopes, joints, complex shapes. Matter is covered in the Admin Guide.

**Q: How do I share data between scenes?**
Use the global `this.registry` for game-wide data, pass an object to `this.scene.start('X', data)`, or get a direct reference with `this.scene.get('X')`. See [Passing data between scenes](#passing-data-between-scenes).

**Q: How do I pool bullets/enemies for performance?**
Use a Group with `maxSize` and `get()` / `killAndHide()`. See [Groups (and object pooling)](#groups-and-object-pooling).

**Q: My game stutters on mobile. What can I help?**
Disable `arcade.debug`, batch objects by texture and blend mode to reduce draw-call breaks, prefer `Image` over `Sprite` for static objects, use `SpriteGPULayer`/`TilemapGPULayer` for very high counts, and consider the `pixelArt`/`autoMobileTextures` config options.

**Q: How do I pause the entire game (not just a scene)?**
Call `game.pause()` to halt all updates and rendering, and `game.resume()` to continue. Check `game.isPaused`. To pause a single scene, use `this.scene.pause()`.

**Q: How do I destroy and recreate the game?**
`game.destroy(true)` removes the canvas and cleans up. Pass `game.destroy(true, true)` only if you'll never create another Phaser instance on the page. It's asynchronous — listen for the `DESTROY` event.

**Q: Where are the official examples and tutorials?**
The Phaser website hosts 2,000+ code examples, 700+ tutorials, full API docs at docs.phaser.io, and a free 500-page "Phaser by Example" book.

**Q: Can AI assistants help me build Phaser games?**
Yes — Phaser is well understood by major LLMs, and this repository ships a comprehensive set of AI agent skills (in the `skills/` folder) covering every subsystem. Point your AI coding agent at them for idiomatic Phaser 4 code.

**Q: How do I add a filter/effect to a sprite?**
`sprite.enableFilters()` then `sprite.filters.internal.addGlow(...)` or `sprite.filters.external.addBlur(...)`. Filters are WebGL-only. See [Filters](#filters-post-processing-effects).

**Q: My keyboard input also scrolls the page. How can I help?**
Capture the keys: `this.input.keyboard.addCapture('SPACE,UP,DOWN,LEFT,RIGHT')`, which calls `preventDefault` on them.

---

## Quick Reference

### Renderer constants

| Constant | Value |
|---|---|
| `Phaser.AUTO` | 0 |
| `Phaser.CANVAS` | 1 |
| `Phaser.WEBGL` | 2 |
| `Phaser.HEADLESS` | 3 |

### Scene lifecycle methods

| Method | When | Receives |
|---|---|---|
| `init(data)` | First, every start | data object |
| `preload()` | After init | — |
| `create(data)` | After loading | data object |
| `update(time, delta)` | Every frame | timestamp, ms since last frame |

### Scene Plugin (`this.scene`) essentials

| Method | Effect |
|---|---|
| `start(key, data)` | Stop this scene, start target |
| `launch(key, data)` | Start target in parallel |
| `run(key, data)` | Start/resume/wake target |
| `restart(data)` | Restart this scene |
| `switch(key)` | Sleep this, start/wake target |
| `pause()` / `resume()` | Stop/start update (still renders) |
| `sleep()` / `wake()` | Stop/start update + render |
| `stop()` | Full shutdown |
| `get(key)` | Get a scene reference |

### Common factory methods (`this.add.*`)

`image`, `sprite`, `text`, `bitmapText`, `tileSprite`, `nineslice`, `graphics`, `rectangle`, `container`, `group`, `layer`, `particles`, `video`, `zone`, `gradient`, `noise`. Physics variants: `this.physics.add.sprite`, `image`, `staticImage`, `group`, `staticGroup`, `existing`, `collider`, `overlap`.

### Loader methods (`this.load.*`)

`image`, `spritesheet`, `atlas`, `multiatlas`, `aseprite`, `svg`, `audio`, `audioSprite`, `video`, `json`, `xml`, `text`, `bitmapFont`, `font`, `tilemapTiledJSON`, `tilemapCSV`, `pack`, `plugin`, `script`.

### Top-level namespaces

`Phaser.Game`, `Phaser.Scene`, `Phaser.GameObjects`, `Phaser.Physics` (Arcade, Matter), `Phaser.Input`, `Phaser.Loader`, `Phaser.Cameras`, `Phaser.Animations`, `Phaser.Tweens`, `Phaser.Sound`, `Phaser.Tilemaps`, `Phaser.Math`, `Phaser.Geom`, `Phaser.Display`, `Phaser.Curves`, `Phaser.Time`, `Phaser.Data`, `Phaser.Textures`, `Phaser.Scale`, `Phaser.Filters`, `Phaser.Utils`.

### Useful links

- API docs: https://docs.phaser.io
- Examples (2,000+): https://phaser.io/examples
- Tutorials (700+): https://phaser.io/learn
- Discord: https://discord.gg/phaser
- The architecture, build internals, security model, and full config reference live in the companion **[Admin Guide](phaserjs__phaser__AdminGuide.md)**.

---

_Generated June 2026 from phaserjs/phaser at 9e67ded. AI-generated documentation. The library is checked by sampling (three repositories so far); this guide was not individually reviewed. Verify against the project before relying on it. Unofficial; not affiliated with or reviewed by the project._
