~2 min read

Scene transitions

The `scene.transition()` API — animated handoff between scenes with both scenes running during the overlap.

scene.start(key) swaps scenes hard — the outgoing scene’s shutdown fires before the incoming scene’s create. scene.transition({...}) gives you an overlap window where both scenes run together, perfect for crossfades, slide-ins, and “wipe one scene off the screen while the next slides on” effects.

The shape

this.scene.transition({
	target: 'Game',     // key of the scene to bring in
	duration: 600,      // ms — both scenes run for this long
	sleep: false,       // if true, this scene sleeps (resumable); else shutdown
	remove: false,      // if true, this scene is removed entirely
	moveAbove: true,    // render the target above this scene
	allowInput: false,  // disable input on this scene during transition
	onUpdate: (progress) => { /* called each frame, 0..1 */ },
});

Reads as: “start Game running alongside me; for the next 600ms call onUpdate with progress; when it ends, do X to me (sleep / shutdown / remove).”

The target scene’s init and create fire immediately. Its update runs every frame from frame zero of the transition. The outgoing scene keeps updating too (unless you stop it from the onUpdate).

Crossfade

The most common pattern. Tween the outgoing scene’s alpha down while the incoming scene’s alpha rises.

// In the outgoing scene:
this.scene.transition({
	target: 'Game',
	duration: 500,
	moveAbove: true,
	allowInput: false,
	onUpdate: (progress) => {
		this.cameras.main.setAlpha(1 - progress);
	},
});

// The incoming scene's create():
create() {
	this.cameras.main.setAlpha(0);
	this.scene.get('Title').events.once('transitionout', () => {
		// Outgoing scene is done — clean up if needed.
	});
	// The transition's progress is on `this.scene.systems.settings.transitionProgress`
	// during the overlap window; this scene's camera alpha can mirror it.
}

If both scenes mirror the progress, you get a true crossfade — the incoming scene rises from 0 alpha while the outgoing scene falls.

Slide-in

this.scene.transition({
	target: 'Pause',
	duration: 400,
	sleep: true,    // pause this scene; we'll resume after Pause closes
	moveAbove: true,
	onUpdate: (progress) => {
		// Slide the pause scene in from the right.
		const pause = this.scene.get('Pause');
		pause.cameras.main.scrollX = (1 - progress) * this.scale.width;
	},
});

sleep: true is the trick — when the pause scene closes (this.scene.stop() from inside it), Phaser wakes this scene rather than recreating it. Saves the whole “preserve state across scene swap” problem.

Events fired during transition

EventWhereWhen
transitionoutoutgoing scene’s eventsWhen the transition starts.
transitioninittarget scene’s eventsAfter target’s init, before create.
transitionstarttarget scene’s eventsAfter target’s create.
transitioncompletetarget scene’s eventsWhen the transition window ends.
this.events.once('transitioncomplete', () => {
	this.cameras.main.setAlpha(1);   // ensure we're fully visible
});

When to use transition vs. start vs. launch

WantUse
Hard cut, replace current scenescene.start(key)
Run another scene alongside, indefinitely (HUD, ambient music)scene.launch(key)
Animate a swap with overlapscene.transition({...})
Animate a swap with no overlap (cut + animate in destination)scene.start + camera.fadeIn in the new scene

The fade-based “swap” demonstrated in examples/scene-transition is the non-overlapping version — fade out, swap, fade in. That’s simpler and good for most cases. Use scene.transition() when you need both scenes drawing simultaneously.

Common pitfalls

Forgetting allowInput: false. During the overlap, both scenes’ input handlers are live. A click on a button in the outgoing scene fires its handler — usually not what you want. Setting allowInput: false on the outgoing scene during transition prevents this.

Stale state on resume. If you use sleep: true, the scene’s create does not re-run on resume. Reset transient state in a 'wake' event handler instead:

this.events.on('wake', () => {
	this.timer = 0;
	this.player.setVelocity(0);
});

Cleanup on remove: true. Setting remove: true destroys the outgoing scene’s display list immediately. If you had a tween writing to that display list during the transition, it’ll error when the targets vanish. Use sleep or shutdown semantics for animated handoffs.