Scenes - samme/phaser3-faq GitHub Wiki

Scenes are the "worlds" of your Phaser game and where you will do most of your work.

Callbacks

The scene callback methods are init(), preload(), create(), and update(). The archetypal callback pattern is

// Configure the scene
function init(data) {}

// Queue assets for downloading
function preload() {}

// Create game objects with loaded assets
function create(data) {}

// Work on game objects at each game step
function update(time, delta) {}

init() is less often used.

You can create game objects in any method, but if you've queued assets in preload() then you won't be able to use those assets before create().

If you don't use preload() then the pattern is simply:

// Create game objects
function create(data) {}

// Work on game objects at each game step
function update(time, delta) {}

Either init() or create() can be used.

Within the scene callbacks, this is the scene. If you don't much like using this, you can assign it to a variable:

let scene;

function init() {
  if (scene) throw new Error("Only one scene can use the `scene` variable");

  scene = this;

  scene.events.once('destroy', () => { scene = null; });
  scene.events.once('shutdown', () => { scene = null; });
}

Or you can destructure it within a callback:

function create() {
  const { add, cameras, events } = this;

  add.sprite(/*…*/);
}

Scene stuff

Scenes have their own systems (bolded in the table below) as well as references to some global systems.

The scene systems can be mapped onto different property names if you like.

Scene Systems Notes
add sys.add
anims sys.anims
cache sys.cache
cameras sys.cameras
children sys.displayList
data sys.data
events sys.events Different from game.events.
facebook sys.facebook
game sys.game
input sys.input
lights sys.lights
load sys.load
make sys.make
matter sys.matterPhysics
physics sys.arcadePhysics
plugins sys.plugins
registry sys.registry
renderer sys.renderer
scale sys.scale
scene sys.scenePlugin
sound sys.sound
textures sys.textures
time sys.time
tweens sys.tweens

Creating scenes

Scenes can be created from a class or a config object. They're added to the game through the Scene Manager. This can be done through the game config, the add() methods, or (rarely) load.scene().

  • If a config object is given, a new Phaser.Scene is instantiated and some of the config values are copied onto it.
  • If a class is given, it's instantiated.
  • The scene is booted.
  • The scene is started, if you indicated so.

Every scene in the Scene Manager's list is an object (instance) that's been booted. They last until they're removed (destroyed).

Examples: single-scene game

It's simplest to use the game config. When only one scene is given, it is started automatically.

const sceneConfig = { create: function () {/*…*/} };

new Phaser.Game({
  scene: sceneConfig
});
class Scene extends Phaser.Scene {
  create () {/*…*/}
}

new Phaser.Game({
  scene: Scene
});
class Scene extends Phaser.Scene {
  create () {/*…*/}
}

new Phaser.Game({
  scene: new Scene()
});

You may also use scene.add(), although there's no great advantage in this case. autoStart (the third argument) means start the scene at the time that's added.

const sceneConfig = { create: function () {/*…*/} };

new Phaser.Game({
  callbacks: {
    preBoot: (game) => {
      game.scene.add('default', sceneConfig, true);
    }
  }
});
class Scene extends Phaser.Scene {
  create () {/*…*/}
}

new Phaser.Game({
  callbacks: {
    preBoot: (game) => {
      game.scene.add('default', Scene, true);
    }
  }
});
class Scene extends Phaser.Scene {
  create () {/*…*/}
}

new Phaser.Game({
  callbacks: {
    preBoot: (game) => {
      game.scene.add('default', new Scene(), true);
    }
  }
});

Beware that with scene.add(), it's possible provide conflicting scene keys:

// OOPS
game.scene.add('yin', new Scene('yang'), true);

In this case the scene is instantiated as 'yang' and then the Scene Manager changes it to 'yin'.

Examples: multi-scene game

In a multi-scene game, each scene needs a unique key.

Again, it's easiest to add scenes in the game config. The first scene plus any additional scenes with { active: true } are started automatically.

// Scene config objects:
const bootSceneConfig = { key: 'boot', /*…*/ };
const playSceneConfig = { key: 'play', /*…*/ };
const uiSceneConfig = { key: 'ui', active: true };

new Phaser.Game({
  // 'boot' and 'ui' will be started
  scene: [ bootSceneConfig, playSceneConfig, uiSceneConfig ]
}; 
// Scene classes:
class BootScene {/*…*/};
class PlayScene {/*…*/};
class UIScene {/*…*/};

new Phaser.Game({
  // 'boot' and 'ui' will be started
  scene: [ new BootScene('boot'), new PlayScene('play'), new UIScene({ key: 'ui', active: true }) ]
});

You can configure scenes in their constructors instead if you like.

Example: multiple scenes from a base config

const levelSceneConfig = { preload, create, update };
const level1SceneConfig = { ...levelSceneConfig, key: 'level1' };
const level2SceneConfig = { ...levelSceneConfig, key: 'level2' };

new Phaser.Game({
  scene: [ level1SceneConfig, level2SceneConfig ]
};

Example: multiple scenes from one class

class LevelScene {/*…*/};

new Phaser.Game({
  scene: [ new LevelScene('level1'), new LevelScene('level2') ]
};

Listing scenes after boot

new Phaser.Game({
  scene: [/* etc. */],
  callbacks: {
    postBoot: function (game) {
      game.scene.dump();
      // Look for output in console.
    }
  }
});

Configuration

You can create a scene from an object config or a class.

From an object config

The config includes scene settings and other properties to be copied onto the scene.

From a class

Pass scene settings.

You can do this directly during instantiation:

new Scene({ key: 'example', /* etc. */});

Or you can do it within the constructor:

class Scene extends Phaser.Scene {
  constructor() {
    super({ key: 'example', /* etc. */});
  }
}

Or you can do some of both:

class Scene extends Phaser.Scene {
  constructor(config) {
    super({ ...defaultConfig, ...config });
  }
}

If you create more than one scene from the class, you'll need some way to pass unique keys.

You can create a scene directly, but there's nothing much for it to do by itself.

const scene = new Phaser.Scene('example');

console.log(scene.sys.settings.key); // -> 'example'

All of the essential systems are added by the Scene Manager when booting the scene.

Plugins

Scenes have core plugins that are always installed and default plugins that are installed unless disabled. Any extra scene plugins you've added in the game config with { start: true } or any { mapping: … } are also default plugins.

If you've added a scene plugin to the game config without those, the plugin isn't installed by default, and you can install it in a specific scene by adding its key to the plugins array:

new Phaser.Scene({
  plugins: [ ...Phaser.Plugins.DefaultPlugins.DefaultScene, 'SomeOtherPluginKey' ]
});

You can also remove default plugins this way:

new Phaser.Scene({
  // "DataManagerPlugin", "Loader", and "LightsPlugin" are omitted.
  plugins: [ 'Clock', 'InputPlugin', 'TweenManager' ]
});

Physics plugins are different. They are installed in a scene if the game config's physics.default is 'arcade' or 'matter' or if the scene settings config includes physics.arcade or physics.matter config objects (even if empty).

Rendering

Scenes render from first to last (bottom to top). You can rearrange them with Scene Manager methods like bringToTop(), or sendToBack(), but this isn't typical. Don't use these methods to "fix" visibility problems when changing scenes — you should probably be using sleep/wake or start/stop instead.

You can toggle a scene's visibility with scene.setVisible(). This doesn't change the scene's status.

The scene life cycle

A scene is booted once and can be started any number of times (or never). It lasts until it's removed (destroyed).

A scene that hasn't been added to the manager has status PENDING.

A scene that has been added to the manager but never started has status INIT. (This means "booted" and is unrelated to the scene init() method.)

When a scene starts it goes through statuses START, LOADING (if assets were queued during preload()), CREATING, and RUNNING. Its methods init(), preload(), and create() are called also, if they exist. update() is called at each game step while the scene is in the RUNNING state.

A RUNNING scene can change to PAUSED and back by pause/resume or to SLEEPING and back by sleep/wake.

A scene that has been stopped has status SHUTDOWN. It can be started again.

A scene that has been removed/destroyed has status DESTROYED. It can't be used again.

A scene is active only during START, LOADING, CREATING, and RUNNING; and visible only during START, LOADING, CREATING, RUNNING, and PAUSED.

Changing scenes

There are a lot of ways, because it depends on what you want to do, and Phaser lets you run multiple scenes at once.

The basic operation pairs are pause–resume, sleep–wake, and shutdown–start.

Usually, with a typical gameplay scene, you will enter the scene with start and exit with shutdown. Gameplay starts over each time.

But to move to an intermission scene and back, you might exit the gameplay scene with sleep and then reenter with wake. It's hidden in the meantime, but then the player returns to the same state they left.

And to move to a modal scene and back, you might exit the gameplay scene with pause and then reenter with resume. In this case it's still visible but suspended.

With scenes like menu or title screens that you expect the player to revisit, you have a choice of sleep–wake or shutdown–start. The sleep–wake pattern may be more manageable. It's easier to reason about scenes that start only once.

The "double" scene control methods are start() and switch(); they affect both the target scene and the calling scene. They're convenient for moving through scenes one at a time.

start()

start('target') starts the target scene and stops the calling scene. It's equivalent to stop().launch('target').

If you want to start a second scene without stopping anything, use launch('target') instead.

// In scene A: stop A, start B
this.scene.start('B');

// In scene B: stop B, start C
this.scene.start('C');

// In scene C: stop C, start A again
this.scene.start('A');

switch()

switch('target') starts or wakes the target scene and sleeps the calling scene.

// In scene A: sleep A, start B
this.scene.switch('B');

// In scene B: sleep B, start C
this.scene.switch('C');

// In scene C: sleep C, *wake* A
this.scene.switch('A');

Here each scene is started only once, and never shut down.

A switch() equivalent is

this.scene.sleep();

if (this.scene.isSleeping('target') {
  this.scene.wake('target');
} else {
  this.scene.launch('target');
}

switch() restarts a paused scene, never resumes it — cf. run().

launch()

launch() starts or restarts the target scene. It never resumes or wakes — cf. run().

run()

run() resumes the target scene if paused; wakes it if sleeping; restarts it if running; and starts it otherwise.

Problems restarting a scene

Some common causes:

  • Your own state variables haven't been reset
  • You're working on an invalid object from the previous scene cycle, e.g., a game object or camera
    • Often, a destroyed game object's method is still registered to an event emitter

Example: bad state after restart

let gameOver = false;

function update() {
  if (gameOver) {
    // GAME OVER :(
  }
}

After restarting the scene it's GAME OVER immediately, because the gameOver variable hasn't been reset. Rewrite as

let gameOver;

function init() {
  gameOver = false;
}

function update() {
  if (gameOver) {
    // GAME OVER :(
  }
}

In a scene class, you might introduce the same problem in the scene constructor:

class Scene extends Phaser.Scene {
  constructor () {
    super();

    this.gameOver = false;
  }
}

Rewrite it similarly:

class Scene extends Phaser.Scene {
  constructor() {
    super();

    this.gameOver;
  }

  init() {
    this.gameOver = false;
  }
}

Example: invalid game objects after restart

const sprites = [];

function create () {
  sprites.push(this.physics.add.sprite(/*…*/));
}

function update (time) {
  for (const sprite of sprites) {
    sprite.setVelocity(1, 1);
    // > TypeError: undefined is not an object (evaluating 'this.body.setVelocityX')
  }
}

Here you get an error after restarting the scene. The destroyed sprites from the last cycle are still in the sprites array. Rewrite as

function create () {
  // …

  this.events.once('shutdown', () => {
    sprites.length = 0;
  });
}

Removing a scene

If you never use a scene again, you can remove it:

this.scene.destroy();

Replacing a scene with the same key

You can't reuse the key until the original scene is removed.

A scene removing itself

// After destruction `this.scene` will be gone.
const { manager } = this.scene;

this.events.once('destroy', () => {
  manager.add('key', newScene);
}

this.scene.remove();

A second scene removing the first

oldScene.events.once('destroy', () => {
  this.scene.add('key', newScene);
}

this.scene.remove('key');

Appendix

Scene control

Calls without a key argument

These affect the calling scene only.

this.scene.start()
method calling scene notes
pause pause
remove destroy Queued during scene processing
restart shutdown?, start Shutdown first only if RUNNING or PAUSED.
resume resume
sleep sleep
start shutdown?, start Shutdown first only if RUNNING or PAUSED.
stop shutdown
wake wake

remove() is queued while the Scene Manager is updating or rendering, or run immediately otherwise.

All other operations are queued always.

start() and restart() are identical when called without a key argument.

Calls with a key argument

These affect the target scene and may affect the calling scene.

this.scene.start('target')
method calling scene target scene notes
launch shutdown?, start Target scene is shutdown first only if RUNNING or PAUSED. If the calling scene is also the target, nothing happens
pause pause
remove destroy Queued during scene processing
resume resume
run shutdown?, start / wake / resume Target scene is shutdown first only if RUNNING
sleep sleep
start shutdown shutdown?, start Target scene is shutdown first only if RUNNING or PAUSED
stop shutdown
switch sleep shutdown?, start / wake Target scene is shutdown first only if RUNNING or PAUSED
wake wake

remove() is queued while the Scene Manager is updating or rendering, or run immediately otherwise.

All other operations are queued always.

restart() never takes a key argument. Use launch('target') instead.

Systems

Within a scene, you can listen to these events from this.events.

Note that isActive() is true only for status RUNNING.

method status identity active visible events
init INIT boot
start START yes yes start, ready
pause PAUSED isPaused() no pause
resume RUNNING isActive() yes resume
sleep SLEEPING isSleeping() no no sleep
wake RUNNING isActive() yes yes wake
shutdown SHUTDOWN no no shutdown
destroy DESTROYED no no destroy

Life cycle

Table shows: SCENE STATUS, scene events, scene methods

INIT START LOADING CREATING RUNNING PAUSED SLEEPING SHUTDOWN DESTROYED
boot
start (resume/wake) pause sleep shutdown destroy
ready
init
create
create
preupdate preupdate
update update
update
postupdate postupdate
prerender prerender prerender
render render render
  • resume is emitted only for PAUSED → RUNNING.
  • wake is emitted only for SLEEPING → RUNNING.