« Back to Blog

Phaser Platformer Series: 21 Scenes

posted on 10 August 2021

In the last tutorial we dipped our toe into scenes. In this tutorial we’ll be adding a title screen and end screen to our game. To do this we’ll need to expand our code to have multiple scenes.

Here is what we’ll be making:

See the Pen
Phaser Platformer Series: 21 Scenes
by Digitherium (@digitherium)
on CodePen.

If you are viewing on a mobile, you can open a version of it here without all the codepen chrome taking up screen space so you play it in landscape mode.

Our old config code defined the scene like this:

scene: {
    preload: preload,
    create: create,
    update: update
}

That tells phaser that there is only one scene, and actually points to the standard methods needed for a phaser scene. preload is where we load assets, create is where we define and create things and update is our game loop where the game logic plays out. We want to have multiple scenes, a title screen, the game itself and an end screen. There are many ways to do this, but I think this is one of the easiest – take a look at our new config code:

var config = {
    type: Phaser.AUTO,
    width: 800,
    height: 400,
    transparent: true,
    physics: {
        default: 'arcade',
        arcade: {
            fps: 120,
            gravity: { y: 300 }
        }
    },
    scene: [titleScreen, gameScene, endScreen]
};
var game = new Phaser.Game(config);

We are passing in three scenes. No mention of preload, create, or update here – that’s because those methods are included in each of those scenes (if they are needed). We’d need to create those three scenes first before instantiating the game. You can create a scene like this:

var titleScreen = new Phaser.Scene("titleScreen");

We pass in the name that we want to call our scene and we can use that as a reference later to grab our scene (it’s case sensitive). Once our scene is created we need to write code for it and add the usual functions. Let’s start with a super simple scene:

var titleScreen = new Phaser.Scene("titleScreen");
titleScreen.preload = function() {
    this.load.setBaseURL("assets/");
    this.load.image("logo", "digitherium-logo.jpg");
}

titleScreen.create = function() {
    var logo = this.add.sprite(config.width / 2, config.height / 2 - 40, 'logo');

    //add some text
    this.add.text(config.width / 2, config.height / 2 + 70, 'CLICK TO START', { fontSize: 32, color: '#ffffff' }).setShadow(4, 4, "#000000", 2, false, true).setOrigin(0.5);

    //add click / tap to start game
    this.input.on('pointerdown', function(pointer) {
        //this is called from the scene not the game object!
        titleScreen.scene.start('game');
    });
}

This scene is so simple it doesn’t even have an update method. It just puts a sprite on the screen (40px up of the center of the screen), some basic text and then listens for a touch or click.

On touch or click it then switches from the ‘titleScreen’ to the ‘game’ screen using titleScreen.scene.start. This takes in the name of the scene you want to switch to. It shuts down the scene that starts it, and loads up the one you specify. It’s important to note that it’s called from the scene you are switching from NOT the game itself. So this would *not* work:

game.scene.start('game');

Because game references our whole game, not just this one scene. You’ll often see the scene.start method called like this:

this.scene.start('game');

If this refers to the scene, then you’re all good. In our code, we’re inside a pointerdown listener and the scope has changed, so this no longer refers to the scene. Instead we call startScene.scene.start and we know for sure that it’s the startScene itself that calls the function.

We need to port our game code over into it’s own scene. We’d create a scene like this:

var gameScene = new Phaser.Scene("game");

Notice the “game” key matches the one called in the start scene call. We’ll need to make some changes to our code to wrap our game code in the game scene but before we dive into that, that’s look at the end game screen:

var endScreen = new Phaser.Scene("endScreen");
endScreen.create = function() {
    var logo = this.add.sprite(config.width / 2, config.height / 2 - 40, 'logo');

    //add some text
    this.add.text(config.width / 2, config.height / 2 + 70, 'GAME OVER\nCLICK TO RESTART', { fontSize: 32, color: '#ffffff', align: 'center' }).setShadow(4, 4, "#000000", 2, false, true).setOrigin(0.5);

    this.input.on('pointerdown', function(pointer) {
        //shut down this scene and start up the game scene
        endScreen.scene.start('game');
        //restart the scene to reset all the variables!
        gameScene.scene.restart();
    });
}

This doesn’t even have a preload function! That’s because all these scenes are included in the same game, so the assets have already been preloaded in another scene and we can already reference them. The key ‘logo’ already exists and Phaser knows which image file that is linked to. That’s the thing with the built in Phaser functions, you only have to use them as needed. If you don’t need to preload anything, you don’t need a preload function, Phaser will still be happy.

When this scene is clicked, it calls the scene.start function again to load the game scene and it also calls gameScene.scene.restart(). We are directly referencing the gameScene object here instead of using this. Once again, it’s a scoping issue as this within our pointerdown function refers to the pointer down event NOT the scene. So as well as loading in the game screen, we also reset all the variables with a restart. This ensures the score is reset and all the coins are put back in place, the baddies are respawned etc etc.

Before we dig into how to refactor our game code to fit in our new scene setup, a small disclaimer on the code structure.

There are much, much cleaner and better ways to structure a Phaser game then how we are doing it here! Usually each scene would extend a class and be in a seperate javascript (or typescript) file. We’re chucking everything together in one file because it’s following our natural progression from the first tutorial. I chose to avoid any build tools to keep it a low barrier to entry – here we are bumping up against the cost of that.

Back to it. We’ve created a title screen, we’ve created an end screen and we’ve rewritten our config to load in these scenes and start in the right place. We just need to write the game scene. We create it just like the others:

var gameScene = new Phaser.Scene("game");

There are a few extra built in functions for a Phaser scene that we can use. init is called when the scene is first created, and it’s here that we can move the global variables.

//the majority of the global variables are now initialised here
gameScene.init = function() {
    this.score = 0;
    this.maxHearts = 3;
    this.vulnerableTime = 1000;
    this.acceleration = 600;
    this.maxPlayerSpeed = 200;
    this.superPlayerSpeed = 400;
    this.jumpVelocity = -165;
    this.jumpVelocitySmall = -165;
    this.jumpVelocityBig = -330;
    this.standing = true;
    this.wasStanding = false;
    this.onLadder = false;
    this.edgeTimer = 0;
    this.prevPos = 0;
    this.yPos = 0;
    this.touchJump = false;
    this.touchJumpThreshold = 5;
    this.touchMoving = false;
    this.isTouch = false;
    this.touchMoveThreshold = 3;
    this.largeThumbMoveAcross = 25;
    this.thumbSizeOffset = 60;
    this.shellSpeed = 500;
    this.heroColor = new Phaser.Display.Color(255, 255, 255);
    this.invincibleColor = new Phaser.Display.Color(56, 194, 239);
    this.coinTweens = [];
}

Instead of declaring them as variables using var, they are defined as properties of the scene object by prefixing them with this. We do this as they are now contained within the scene, instead ofbeing global. The scene owns them now.

Throughout the code we’ll then refer to any of these variables with it’s this prefix as we are accessing a property of the scene. If this is no longer referring to our scene because it’s in some sort of callback function, then we can access functions and variables of gameScene directly using gameScene as a prefix instead of this. i.e. instead of saying this.player we can say gameScene.player. This works because when we create our scene we store it in the variable gameScene and that variable is accessible anywhere in the code.

You may have noticed that they are some global variables missing from that list – those are ones that didn’t initialise right away such as player, baddies etc, because they have no default value. We could declare them and assign them a null value, but that seems like an odd thing to do. Instead, we initialise them in the create function instead.

The rest of the code was then updated so in the update function, the old global variables have the this prefix. The collider and overlap callbacks are passing game objects like the player as an argument to those functions, so we don’t need this to reference them as we have them passed in directly to a function to use.

For callback functions that call another function where scope is lost, we just use gameScene as our prefix. For instance shellHitBaddie is an overlap function called when a shell and baddie intersect. That function goes on to call destroyShell and within there we have a tween with a complete callback. The scope is lost within the tween callback and this isn’t usable so we just use gameScene instead.

And that’s our new basic setup. Next up we’ll attempt to add a bit of polish to the menus. Coming soon.

chromeless mobile version
view all the code on codepen
download the source on github.