Set up the foundation of a Phaser game: the Game config, the Scene
lifecycle, asset loading, cameras, and passing data between scenes. Targets
Phaser 4.2 for new projects; keep an existing Phaser 3.90 project on its
pinned major unless the user explicitly asks for a migration.
Phaser.Game config, structuring
Scenes, loading assets in preload, or fixing scene transitions and shared
state.phaser in package.json or import Phaser from 'phaser',
and code uses preload()/create()/update().When not to use: movement, velocity, colliders, gravity, or overlap → use
phaser-arcade-physics. Complex rigid-body simulation uses Matter physics (a
separate concern). For cross-engine save/load patterns use save-systems.
package.json and the lockfile. Use
Phaser 4.2 for new work; do not silently rewrite a Phaser 3 project as Phaser 4.new Phaser.Game(config) with type: Phaser.AUTO (WebGL with Canvas fallback), a width/height, and a scene
array. The first scene (and any with active: true) starts automatically.Scene. Subclass Phaser.Scene, pass a unique
key to super, and implement the lifecycle: init(data) → preload() →
create(data) → update(time, delta).preload, use them in create. Queued assets are not
available until create. The loader is per-scene; the cache it fills is global.init(), not the constructor. A scene instance is
reused across restarts, so constructor-set fields keep stale values.this.scene.start/launch/switch/sleep/wake.
Share data through this.registry (global) or a sibling scene's event emitter.// main.js — one Game owns the renderer, loop, cache, and Scene Manager.
import Phaser from 'phaser';
import BootScene from './scenes/BootScene.js';
import PlayScene from './scenes/PlayScene.js';
const config = {
type: Phaser.AUTO, // WebGL if available, else Canvas
width: 800,
height: 600,
backgroundColor: '#1d1d28',
scale: { mode: Phaser.Scale.FIT, autoCenter: Phaser.Scale.CENTER_BOTH },
scene: [BootScene, PlayScene] // BootScene starts first
};
new Phaser.Game(config);
// scenes/PlayScene.js
import Phaser from 'phaser';
export default class PlayScene extends Phaser.Scene {
constructor() {
super('play'); // unique scene key
}
init(data) {
// Reset run-specific state HERE so restarts start clean.
this.score = 0;
this.level = data.level ?? 1;
}
preload() {
// Queue downloads. Not usable until create().
this.load.image('player', 'assets/player.png');
this.load.spritesheet('coin', 'assets/coin.png', { frameWidth: 16, frameHeight: 16 });
}
create() {
this.player = this.add.sprite(400, 300, 'player');
this.scoreText = this.add.text(10, 10, 'Score: 0', { fontSize: '20px', color: '#fff' });
this.cursors = this.input.keyboard.createCursorKeys();
}
update(time, delta) {
// delta is milliseconds since last frame; divide by 1000 for seconds.
const speed = 200 * (delta / 1000);
if (this.cursors.left.isDown) this.player.x -= speed;
if (this.cursors.right.isDown) this.player.x += speed;
}
}
// The registry is a global DataManager shared by every scene.
this.registry.set('coins', 0); // in any scene
const coins = this.registry.get('coins'); // read anywhere
// React to registry changes (e.g. a HUD scene listening to gameplay):
this.registry.events.on('changedata-coins', (parent, value) => {
this.coinText.setText(`Coins: ${value}`);
});
// Talk directly to another running scene via its event emitter:
const ui = this.scene.get('hud');
ui.events.emit('show-message', 'Level cleared!');
this.scene.start('gameover', { score: this.score }); // stop this scene, start target
this.scene.launch('hud'); // run a second scene in parallel (overlay HUD)
this.scene.switch('menu'); // sleep this scene, start/wake target
this.scene.pause(); // freeze updates but keep rendering (modal)
this.scene.sleep(); // stop updating AND rendering, keep state for wake
this.cameras.main.setBounds(0, 0, 1600, 1200); // world size
this.cameras.main.startFollow(this.player, true, 0.1, 0.1); // smooth lerp follow
this.cameras.main.setZoom(1.5);
undefined in create/update → you forgot to queue them in
preload, or used the wrong key. The loader runs between preload and create.init() and clear arrays on shutdown.this.scene.start vs this.scene.launch → start stops the calling scene;
launch runs the target alongside it. Using start for a HUD hides the game.this is wrong in a callback → arrow functions keep the Scene's this; plain
function callbacks need a context argument or .bind(this).width/height
are set, and a scene actually started (check game.scene.dump() output).references/scene-flow.md.phaser-arcade-physics — velocity, gravity, colliders, overlap, and groups.input-systems — rebindable, multi-device input architecture (engine-agnostic).pixijs-rendering / threejs-scene-setup — other browser rendering stacks.platformer / puzzle — genre templates that compose Phaser skills.