-
Notifications
You must be signed in to change notification settings - Fork 4
Scenes
Scenes are the "worlds" of your Phaser game and where you will do most of your work.
The scene callback methods are init()
, preload()
, create()
, and update()
. The archetypal callback pattern is
// Configure this scene cycle
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(/*…*/);
}
Scenes have their own systems and plugins (bolded in the table below) as well as references to some global systems.
These 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 . |
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 |
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, and lasts until it's removed (destroyed).
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 with the key 'yang' and then the Scene Manager changes the key to 'yin'.
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.
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.
const levelSceneConfig = { preload, create, update };
const level1SceneConfig = { ...levelSceneConfig, key: 'level1' };
const level2SceneConfig = { ...levelSceneConfig, key: 'level2' };
new Phaser.Game({
scene: [ level1SceneConfig, level2SceneConfig ]
};
class LevelScene {/*…*/};
new Phaser.Game({
scene: [ new LevelScene('level1'), new LevelScene('level2') ]
};
class Boot extends Phaser.Scene {
// Load assets
preload() {}
// Start Play scene
create() {}
}
class Play extends Phaser.Scene {
// Create game objects with loaded assets
create() {}
// Update the scene
update() {}
}
new Phaser.Game({
scene: [
// Only 'boot' will start.
new Boot('boot'),
new Play('play')
]
});
new Phaser.Game({
scene: [/* etc. */],
callbacks: {
postBoot: function (game) {
game.scene.dump();
// Look for output in console.
}
}
});
The config includes scene settings and other properties to be copied onto the scene.
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.
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).
Scenes render from first to last (bottom to top). You can rearrange them with Scene Manager methods like bringToTop()
, or sendToBack()
, but this is unusual, except for some visual effects. Don't use these methods to "fix" visibility problems when changing scenes — you should probably be using sleep/wake or start/stop instead. Remember you can add all your scene definitions to the game config and so establish the rendering order there, even for scenes that aren't started immediately.
You can toggle a scene's visibility with scene.setVisible()
. This doesn't change the scene's status.
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
.
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.
-
launch()
andstop()
do start and shutdown. -
sleep()
andwake()
do sleep and wake. -
pause()
andresume()
do pause and resume.
-
run()
may start, wake, or resume a scene -
switch()
may start or wake a scene
These affect both the target scene and the calling scene. They're convenient for moving through scenes one at a time.
-
start()
stops the calling scene. -
switch()
sleeps the calling scene.
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('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()
starts or restarts the target scene. It never resumes or wakes — cf. run()
.
run()
resumes the target scene if paused; wakes it if sleeping; restarts it if running; and starts it otherwise.
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
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;
}
}
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;
});
}
If you never use a scene again, you can remove it:
this.scene.destroy();
You can't reuse the key until the original scene is removed.
// After destruction, `this.scene` and `this.sys.scenePlugin` are unusable.
// So we need to use the manager directly.
const { manager } = this.scene;
this.events.once('destroy', () => {
manager.add('key', newScene);
}
this.scene.remove();
sceneToRemove.events.once('destroy', () => {
this.scene.add('key', newScene);
}
this.scene.remove(sceneToRemove);
Instead of juggling scene keys, you can use dynamic keys.
this.registry.set('levelSceneKey', 'level1');
this.scene.remove(this.registry.get('levelSceneKey'));
this.registry.set('levelSceneKey', 'level2');
this.scene.add(this.registry.get('levelSceneKey'), Level, true);
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.
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.
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 |
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.