When you’re building a game, you’re probably going to have more than one screen. For example, you might have an initial menu screen, a main gameplay screen, and a game over screen. These screens can be thought of as scenes in a game, the same way you can think of scenes in film.
Every scene that you have in your game represents a clean slate, or a fresh starting point for that particular subsection of content.
In this tutorial, we’re going to see how to create multiple scenes, switch between them, and pass data between them, using Phaser 3.x and simple JavaScript.
To get an idea of what we plan to accomplish, take a look at the following animated image:
We’re not doing much in the above image, but that doesn’t make it any less important. We have two scenes in the above example, one with a plane, and one with some game over text. Upon some kind of trigger, the scenes are switched, and in this example the first scene with the plane, passes a message to the second scene. That message is rendered on the screen.
Let’s work towards reproducing the above example.
Depending on how you’ve been creating Phaser games in the past, designing your game for multiple scenes could look very different in terms of code formatting. Rather than dumping everything into an index.html file or similar, we need to have a class for every scene that we plan to use.
To see what I’m talking about, create a new directory on your computer with an index.html, scene-one.js, and scene-two.js file inside. Within the index.html file, add the following markup:
<!DOCTYPE html>
<html>
<head>
<script src="//cdn.jsdelivr.net/npm/phaser@3.24.1/dist/phaser.min.js"></script>
<script src="scene-one.js"></script>
<script src="scene-two.js"></script>
</head>
<body>
<div id="game"></div>
<script>
const phaserConfig = {
type: Phaser.AUTO,
parent: "game",
width: 1280,
height: 720,
backgroundColor: "#5DACD8",
scene: [ ]
};
const game = new Phaser.Game(phaserConfig);
</script>
</body>
</html>
Remember, we’re not going to dump all of our code in the <script>
tag for this example. Notice that we’re importing both the scene-one.js and scene-two.js files for use. As of right now, if we ran this project, it likely wouldn’t run because we don’t have any scene information defined.
We’re going to add the foundation for our scenes in the next step.
Both of our scenes are going to be simplistic in comparison to what can be accomplished. I don’t want to add too much complexity to take away from the point I’m trying to show, which is in scenes.
Open the scene-one.js file and add the following code:
var SceneOne = new Phaser.Class({
Extends: Phaser.Scene,
initialize: function() {
Phaser.Scene.call(this, { "key": "SceneOne" });
},
init: function() {},
preload: function() {
this.load.image("plane", "plane.png");
},
create: function() {
this.plane = this.add.image(640, 360, "plane");
},
update: function() {}
});
In the above Phaser class, we give it a name which will be important when switch scenes. The class has access to the same init
, preload
, create
, and update
events that you’d expect. For this particular example, we’re loading an image and adding it to the screen. The image is not particularly important, we’re just using it as something to recognize which scene we are in.
Now open the scene-two.js file:
var SceneTwo = new Phaser.Class({
Extends: Phaser.Scene,
initialize: function() {
Phaser.Scene.call(this, { "key": "SceneTwo" });
},
init: function() {},
preload: function() {},
create: function() {
var text = this.add.text(
640,
360,
"Hello World",
{
fontSize: 50,
color: "#000000",
fontStyle: "bold"
}
).setOrigin(0.5);
},
update: function() {}
});
Pay attention to the key
again, because that will be important when it comes to switching between scenes.
In the above scene example, we are rendering text to the screen, in this case the center of our Phaser screen. Again, nothing particularly exciting is happening in this scene, just like with the previous.
With the scene logic in place, we can add them to the overall Phaser configuration. Within the index.html file, change the phaserConfig
to look like the following:
const phaserConfig = {
type: Phaser.AUTO,
parent: "game",
width: 1280,
height: 720,
backgroundColor: "#5DACD8",
scene: [ SceneOne, SceneTwo ]
};
Notice that we’ve added both scenes to our scene
array. The order is important because the first scene in the array will be the default scene. You can always navigate between them, but it is important to know which one will display first.
If you run the game, you should see the scene with the image.
Now that we have multiple scenes each with their own lifecycle events, we probably want to switch between them and even pass data between them. Before we worry about the data side of things, let’s just focus on switching.
The idea is as follows:
SceneOne
scene.SceneTwo
scene.Ideally you’d probably want to switch scenes based on some game object interaction, but for demonstration purposes, a timer will be fine.
Within the create
function of the scene-one.js file, change it to look like the following:
create: function() {
this.plane = this.add.image(640, 360, "plane");
this.time.addEvent({
delay: 3000,
loop: false,
callback: () => {
this.scene.start("SceneTwo");
}
})
},
Notice the use of the timer. After three seconds, the next named scene will start. When the next scene starts, that scene goes through each of the lifecycle events necessary for configuring and rendering the scene.
If we did nothing else, our code would work. However, it might make sense to pass data from one scene to another. An example being with score information. Maybe the first scene is the gameplay scene and you’re accumulating a score. Then maybe the second scene is a game over scene that should display the score. Well, you’re going to need to transfer the score to the new scene, so this example could be useful.
Within the create
function of the scene-one.js file, change the scene.start
slightly to look like the following:
this.scene.start("SceneTwo", {
"message": "Game Over"
});
Notice that this time we’re passing an object. The key and values in this object can be whatever you want.
Since we’re passing values, we need a way to receive them in the next scene. Within the init
function of the scene-two.js file, change it to look like the following:
init: function(data) {
this.message = data.message;
},
Notice that this function is accepting a parameter. That parameter is the data from the previous scene. We need to set it to a locally scoped scene variable so that it can be accessed throughout the lifecycle events of our scene.
Now let’s modify the create
function in the same file:
create: function() {
var text = this.add.text(
640,
360,
this.message,
{
fontSize: 50,
color: "#000000",
fontStyle: "bold"
}
).setOrigin(0.5);
},
Notice that instead of rendering “Hello World”, we’re now rendering the message that was passed from the previous scene.
You just saw how to work with numerous scenes and pass data between them in a Phaser 3.x game. This is useful if you’re creating a larger scale game, or just need to be able to transition between different screens such as a menu screen, gameplay screen, and game over screen.
In this example, we just used a static image. If you’re interested in learning how to animate that image, check out my previous tutorial on the subject titled, Animate Spritesheets in a Phaser Game.