Platform Games in Phaser 3 – Setting Up

Platform games have been a staple in video games since Mario Brothers came along in the early 80’s. Because the framework gives us a lot of code to work with, setting Up Phaser 3 platform games isn’t too difficult. There are a lot of 3rd party tools to help you do this, but I’m going to show you how you can set it up just through code.

Loading the images

The first thing we need to do is preload the images we will be using. I’ve picked two bricks to use to make our platforms.

 this.load.image("brown", "images/tiles/brickBrown.png");
 this.load.image("grey", "images/tiles/brickGrey.png");

Setting Up the Grid

I’m going to use an AlignGrid class to position things by a percentage of the screen’s width. You can read more about AlignGrid here.

this.aGrid = new AlignGrid({
            scene: this,
            rows: 11,
            cols: 11
        });
this.aGrid.showNumbers();

When we use the showNumbers() function of the grid, we get a layout grid to see where to place our game objects.

placing a block

Let’s start of with placing a single block. Let’s make a function that will do 3 things.

  • Add the image to the scene
  • Position the block
  • Scale the block to a percentage of the game’s width
placeBlock(pos, key) {
        //add the block to the scene. Position is not important yet
        let block = this.physics.add.sprite(0, 0, key);
        
        //place the block
        this.aGrid.placeAtIndex(pos, block);
        
        //scale the block
        Align.scaleToGameW(block, .1);
        
    }

You can test out the function by adding this line at the end of the create function

this.placeBlock(89,"brown");

Making a floor

If we had to a single line of code for each block it would take up a lot up a lot of room. Fortunately, we can simply loop through a start and an end value and call the placeBlock() function from that loop.

makeFloor(fromPos, toPos, key) {
        for (var i = fromPos; i < toPos + 1; i++) {
            this.placeBlock(i, key);
        }
 }

Now we can make our bottom floor by calling the makeFloor() function from the end of the create function.

 this.makeFloor(88, 98, "brown");

Adding Gravity

Now that we have our floor we can make the ninja fall towards it. First, let’s scale the ninja.

Align.scaleToGameW(this.ninja, .2);

The Align class comes as part of the Utility Template. Basically what this function does is:

this.ninja.displayWidth = this.sys.game.config.width *.2;
this.ninja.scaleY = this.ninja.scaleX;

Before we can use physics in the game, we must add physics to the game’s configuration object.

This is the physics object.

physics: {
            default: 'arcade',
            arcade: {
                debug: true
            }
        }

Here is what it looks like as part of the entire configuration object.

 var config = {
        type: Phaser.AUTO,
        width: w,
        height: h,
        parent: 'phaser-game',
        scene: [SceneMain],
        physics: {
            default: 'arcade',
            arcade: {
                debug: true
            }
        }
    };

Now we can set the gravity on the ninja. You can find out more about gravity in Phaser 3 here.

 this.ninja.setGravityY(200);

Add Colliders

We can set up colliders in physics to check for collisions in three different ways

  • Sprite against Sprite
  • Sprite against a Group
  • One group against another.

For this example instead of checking the ninja against every block placed we can place every block in a single block.

adding a group

We can add a group a scene in Phaser to keep a collection of game objects.

let group=this.add.group();

But when using physics we need to add the group to the physics object of the scene rather than the group itself.

this.brickGroup=this.physics.add.group();

adding the block to the group

In the placeBlock function let’s add the block to the group just after creating it.

placeBlock(pos, key) {
        //add the block to the scene. Position is not important yet
        let block = this.physics.add.sprite(0, 0, key);
        this.brickGroup.add(block);

Adding the collider

Now that we have the blocks in the group, we can set up a collider between the ninja and the brickGroup.

this.physics.add.collider(this.ninja,this.brickGroup);

Making the floor stable

If you run the code now or watch the video above you’ll see that the ninja falling will push the blocks down. We can fix this by setting the blocks to immovable.

block.setImmovable();

It is important that any physics that you want to apply to a sprite take place after adding that sprite to a group. If you add a sprite to a group, all physics properties are reset to default.

 placeBlock(pos, key) {
        //add the block to the scene. Position is not important yet
        let block = this.physics.add.sprite(0, 0, key);
        this.brickGroup.add(block);
        //place the group
        this.aGrid.placeAtIndex(pos, block);
        block.setImmovable();
        Align.scaleToGameW(block, .1);        
    }
Platform Games Phaser 3

Complete Code

Here is the complete SceneMain code for reference. Later we will go more into building platform games with Phaser 3

class SceneMain extends Phaser.Scene {
    constructor() {
        super('SceneMain');
    }
    preload() {
        //
        //preload all imagaes
        //
        this.load.atlas("ninja", "images/ninja.png", "images/ninja.json");
        this.load.image("brown", "images/tiles/brickBrown.png");
        this.load.image("grey", "images/tiles/brickGrey.png");
    }
    create() {
        //
        //add a physics group
        //
        this.brickGroup=this.physics.add.group();
        //
        //add the ninja
        //      
        this.ninja = this.physics.add.sprite(200, -100, "ninja");
        //
        //scale the ninja
        Align.scaleToGameW(this.ninja, .2);
        //
        //get the frame names       
        //
        var frameNames = this.textures.get('ninja').getFrameNames();
        // console.log(frameNames);
        // 
        // make the animations
        // 
        this.makeAnims();
        //play idle animation
        this.ninja.play("idle");
        window.ninja = this.ninja;
        //
        //
        //make an align grid
        this.aGrid = new AlignGrid({
            scene: this,
            rows: 11,
            cols: 11
        });        
        //
        //show the numbers
        //
        this.aGrid.showNumbers();        
        //
        //make the floor
        //
        this.makeFloor(88, 98, "brown");        
        //
        //add gravity to the ninja to make it fall
        //
        this.ninja.setGravityY(200);
        //
        //set a collider between the ninja and the floor
        //
        this.physics.add.collider(this.ninja,this.brickGroup);
    }
    
    placeBlock(pos, key) {
        //
        //add the block to the scene. Position is not important yet
        //
        let block = this.physics.add.sprite(0, 0, key);
        this.brickGroup.add(block);
        //
        //place the group
        //
        this.aGrid.placeAtIndex(pos, block);
        //
        //make the block immovable
        //
        block.setImmovable();
        //
        //add the block to the group
        //
        Align.scaleToGameW(block, .1);
        
    }
    makeFloor(fromPos, toPos, key) {
        for (var i = fromPos; i < toPos + 1; i++) {
            this.placeBlock(i, key);
        }
    }
    makeAnims() {
        this.anims.create({
            key: 'attack',
            frames: this.makeAnim('ninja', 'Attack__00'),
            frameRate: 8,
            repeat: -1
        });
        this.anims.create({
            key: 'jump',
            frames: this.makeAnim('ninja', 'Jump__00'),
            frameRate: 8,
            repeat: -1
        });
        this.anims.create({
            key: 'slide',
            frames: this.makeAnim('ninja', 'Slide__00'),
            frameRate: 8,
            repeat: -1
        });
        this.anims.create({
            key: 'jumpAttack',
            frames: this.makeAnim('ninja', "Jump_Attack__00"),
            frameRate: 8,
            repeat: -1
        });
        this.anims.create({
            key: 'jumpThrow',
            frames: this.makeAnim('ninja', "Jump_Throw__00"),
            frameRate: 8,
            repeat: -1
        });
        this.anims.create({
            key: 'idle',
            frames: this.makeAnim('ninja', "Idle__00"),
            frameRate: 8,
            repeat: -1
        });
        this.anims.create({
            key: 'dead',
            frames: this.makeAnim('ninja', "Dead__00"),
            frameRate: 8,
            repeat: -1
        });
        this.anims.create({
            key: 'run',
            frames: this.makeAnim('ninja', "Run__00"),
            frameRate: 8,
            repeat: -1
        });
    }
    makeAnim(key, frameName) {
        let myArray = [];
        for (var i = 0; i < 8; i++) {
            let fn = frameName + i + ".png";
            myArray.push({
                key: key,
                frame: fn
            })
        }
        return myArray;
    }
    update() {}
}

Leave a Comment