How to Make a Mario-style Platformer with Phaser 3

Phaser is a fast, free, open source HTML5 game framework for desktop and mobile. Phaser 3 is the next generation of Phaser framework with many new and improved features, fully modular structure and new custom WebGL renderer. In this tutorial we will use Phaser 3 to create simple Mario-style platformer game.

Learning goals

The goal of this tutorial is to teach you how to build a platformer games with Phaser 3. You will learn how to:

  • Use Tiled maps in Phaser 3 game
  • Create sprites and animations
  • Use Phaser 3 arcade physics system
  • Create the basic logic of a Mario-style platformer game

Tutorial requirements

  • Intermediate level of JavaScript Skills
  • Some sort of code editor. I prefer to use Netbeans, but you can use whatever you like
  • Web server – you can work locally with Apache, use a remote web server or even try some online JavaScript Code Editors
  • Tutorial assets – you can use the ones coming with the source code download or make your own

Assets copyright

The assets used in this tutorial are created by Kenney Vleugels and can be found at www.kenney.nl. All assets used are public domain CC0 licensed.

Source code

You can download the tutorial source code and asset files here.

Learn Phaser 3 with our newest Mini-Degree

The HTML5 Game Development Mini-Degree is now available for Pre-Order on Zenva Academy. Learn to code and make impressive games with JavaScript and Phaser 3!

Pre-order now

Create the project

In Phaser 3 a config object is used to configure the basic functionality of a game. The config object can have a lot of options, but for now we will add only the most basic ones. Width and height define the size of the canvas element that will be created. The type property is for setting the type of the renderer for our game, I will set it to Phaser.AUTO. In this case Phaser 3 will try to use WebGL, but if it can’t for some reason, it will use Canvas. You can change this option to Phaser.WEBGL or Phaser.CANVAS for corresponding renderer.
Now we will create simple Phaser 3 project with only the default scene and we will implement three functions:

  • preload – loads the game assets
  • create – create the map, the player and the other game objects
  • update – update the game

For our Mario-style platformer I will use Phaser 3 arcade physics. To enable it from the start, you need to add the physics property to the config object:

    physics: {
        default: 'arcade',
        arcade: {
            gravity: { y: 500 }, // will affect our player sprite
            debug: false // change if you need
        }
    },

Very fast and really straightforward way to add physics system to a game. Also, I’ve added a gravity property to it that will work on our player sprite. So here is our empty, but fully functional Phaser 3 project with arcade physics enabled:

var config = {
    type: Phaser.AUTO,
    width: 800,
    height: 600,
    physics: {
        default: 'arcade',
        arcade: {
            gravity: {y: 500},
            debug: false
        }
    },
    scene: {
        key: 'main',
        preload: preload,
        create: create,
        update: update
    }
};

var game = new Phaser.Game(config);

var map;
var player;
var cursors;
var groundLayer, coinLayer;
var text;

function preload() {

}

function create() {

}

function update() {

}

 

Load the assets

Now we need to load the assets into the game. Phaser 3 comes with nice loading functionality so you just need to add your assets in the preload function and the engine will automatically load anything defined in it when started.
Here is how our preload function should look:

function preload() {
    // map made with Tiled in JSON format
    this.load.tilemapTiledJSON('map', 'assets/map.json');
    // tiles in spritesheet 
    this.load.spritesheet('tiles', 'assets/tiles.png', {frameWidth: 70, frameHeight: 70});
    // simple coin image
    this.load.image('coin', 'assets/coinGold.png');
    // player animations
    this.load.atlas('player', 'assets/player.png', 'assets/player.json');
}

This will load four assets – our map made with Tiled Editor, a spritesheet containing our tiles, a coin image and the player animations in an image atlas.

Running the game now will show only black screen, as we haven’t added anything to our scene.

Create the world

The map for this tutorial is created with Tiled Map Editor. You can create your own map with it or use the map that comes with the source files for this tutorial. Our map has two layers – one for the ground tiles, named ‘World’ and one that contains the coins with the name ‘Coins’. The map should be exported in JSON format.
Now we need to add the map to the scene. Add this code to the create function:

    // load the map 
    map = this.make.tilemap({key: 'map'});
    
    // tiles for the ground layer
    var groundTiles = map.addTilesetImage('tiles');
    // create the ground layer
    groundLayer = map.createDynamicLayer('World', groundTiles, 0, 0);
    // the player will collide with this layer
    groundLayer.setCollisionByExclusion([-1]);

    // set the boundaries of our game world
    this.physics.world.bounds.width = groundLayer.width;
    this.physics.world.bounds.height = groundLayer.height;

We will need the last two rows to limit the movement of our player and the camera later on. We don’t want to lose the player sprite outside the game screen so we make the world limited to the layer size.

Great, now we have our game world ready. Here is a screenshot of how it should look:

Add the player

Its time to add the player sprite. Add the code bellow at the bottom of the create function:

    // create the player sprite    
    player = this.physics.add.sprite(200, 200, 'player'); 
    player.setBounce(0.2); // our player will bounce from items
    player.setCollideWorldBounds(true); // don't go out of the map

You will see that we are not just creating a sprite, but we use the Physics Game Object Factory to make our player so it has dynamic physics body added to it from the start. Then we set a bounce property to it and make the player collide with the physics world bounds.

When you check the game in your browser you will find that we have a problem. Our player don’t stay on the ground, but falls through it to the bottom of the game. The solution of this problem is to add collision with the ground layer. Here is how you do it in Phaser 3 style – add this code at the end of the create function:

this.physics.add.collider(groundLayer, player);

In Phaser 2 you add your check for collisions during update, but Phaser 3 makes it automatically with the Collider object.
Now we have the player collide with the platforms and we need to make it move when a key is pressed. For this game we will use the arrow keys of the keyboard. Phaser has a build-in keyboard manager and it is a good choice for our platformer.
Add the code bellow at the end of the create function:

cursors = this.input.keyboard.createCursorKeys();

Now the cursors object is populated with four Key objects: left, right, up and down. Here is how we can use them to check if a key is pressed in our update function:

    if (cursors.left.isDown) // if the left arrow key is down
    {
        player.body.setVelocityX(-200); // move left
    }
    else if (cursors.right.isDown) // if the right arrow key is down
    {
        player.body.setVelocityX(200); // move right
    }
    if ((cursors.space.isDown || cursors.up.isDown) && player.body.onFloor())
    {
        player.body.setVelocityY(-500); // jump up
    }

If the left key is down we set player velocity to negative value and the player moves left. If the right arrow key is down we set the player velocity to positive value and move right. If no left or right button is hold the player will not move horizontally.
If the up arrow key is hold and the player body is touching the floor, we make it jump. This is achieved by setting the y velocity to a negative number. You can experiment with this and the gravity to get a better understanding on the player movements.

As you can test now the player sprite can move and jump, but the camera stays still. Making the camera follow the player is very simple, you just need to call the camera startFollow method. Also we will want to stop the camera from exiting the game world so we will use its setBounds to limit it to the map size. Add the following code to the create function:

    // set bounds so the camera won't go outside the game world
    this.cameras.main.setBounds(0, 0, map.widthInPixels, map.heightInPixels);
    // make the camera follow the player
    this.cameras.main.startFollow(player);
    
    // set background color, so the sky is not black    
    this.cameras.main.setBackgroundColor('#ccccff'); 

Now we have another problem – our player moves left and right and jumps, but there is no walk animation.
Its time to mention that the ‘player’ is loaded as texture atlas, that is a combination of images in one big image. In our case the player atlas contains walking and standing frames of our player character. Its is not in the scope of this tutorial to explain texture atlases in details, but if you are not familiar with the term, I recommend you to research more on it now.
For the walk animation the atlas contains the frames ‘p1_walk01.png’, ‘p1_walk02.png’ … to ‘p1_walk11.png’. To make simple walk animation from these frames we need to define it this way:

    this.anims.create({
        key: 'walk',
        frames: this.anims.generateFrameNames('player', { prefix: 'p1_walk', start: 1, end: 11, zeroPad: 2 }),
        frameRate: 10,
        repeat: -1
    });

First the key property is the name of this animation. Next we need to add the frames for it, in our case we use the function generateFrameNames to add all walk frames easily. The repeat property set to -1 tells Phaser to loop this animation. The frame rate property is pretty self explanatory.

I won’t use two separate animations for left and right movement, instead I will use only one animation ‘walk’ and I will flip the player. Flipping is done with the player.flipX property. Here is how the update function should look now:

function update(time, delta) {    
    if (cursors.left.isDown)
    {
        player.body.setVelocityX(-200); // move left
        player.anims.play('walk', true); // play walk animation
        player.flipX= true; // flip the sprite to the left
    }
    else if (cursors.right.isDown)
    {
        player.body.setVelocityX(200); // move right
        player.anims.play('walk', true); // play walk animatio
        player.flipX = false; // use the original sprite looking to the right
    } else {
        player.body.setVelocityX(0);
        player.anims.play('idle', true);
    }  
}

Its time to add something to collect – the coins. We will add another dynamic Tiles layer to our game world:

    // coin image used as tileset
    var coinTiles = map.addTilesetImage('coin');
    // add coins as tiles
    coinLayer = map.createDynamicLayer('Coins', coinTiles, 0, 0);

Now the coins are visible, but we need to be able to collect them. Add this code to the create function:

    coinLayer.setTileIndexCallback(17, collectCoin, this); // the coin id is 17
    // when the player overlaps with a tile with index 17, collectCoin will be called    
    this.physics.add.overlap(player, coinLayer);

The function setTileIndexCallback can be used to invoke certain actions when the player touches some tiles.

Here is what your create function should look:

function create() {
    // load the map 
    map = this.make.tilemap({key: 'map'});

    // tiles for the ground layer
    var groundTiles = map.addTilesetImage('tiles');
    // create the ground layer
    groundLayer = map.createDynamicLayer('World', groundTiles, 0, 0);
    // the player will collide with this layer
    groundLayer.setCollisionByExclusion([-1]);

    // coin image used as tileset
    var coinTiles = map.addTilesetImage('coin');
    // add coins as tiles
    coinLayer = map.createDynamicLayer('Coins', coinTiles, 0, 0);

    // set the boundaries of our game world
    this.physics.world.bounds.width = groundLayer.width;
    this.physics.world.bounds.height = groundLayer.height;

    // create the player sprite    
    player = this.physics.add.sprite(200, 200, 'player');
    player.setBounce(0.2); // our player will bounce from items
    player.setCollideWorldBounds(true); // don't go out of the map    
    
    // small fix to our player images, we resize the physics body object slightly
    player.body.setSize(player.width, player.height-8);
    
    // player will collide with the level tiles 
    this.physics.add.collider(groundLayer, player);

    coinLayer.setTileIndexCallback(17, collectCoin, this);
    // when the player overlaps with a tile with index 17, collectCoin 
    // will be called    
    this.physics.add.overlap(player, coinLayer);

    // player walk animation
    this.anims.create({
        key: 'walk',
        frames: this.anims.generateFrameNames('player', {prefix: 'p1_walk', start: 1, end: 11, zeroPad: 2}),
        frameRate: 10,
        repeat: -1
    });
    // idle with only one frame, so repeat is not neaded
    this.anims.create({
        key: 'idle',
        frames: [{key: 'player', frame: 'p1_stand'}],
        frameRate: 10,
    });


    cursors = this.input.keyboard.createCursorKeys();

    // set bounds so the camera won't go outside the game world
    this.cameras.main.setBounds(0, 0, map.widthInPixels, map.heightInPixels);
    // make the camera follow the player
    this.cameras.main.startFollow(player);

    // set background color, so the sky is not black    
    this.cameras.main.setBackgroundColor('#ccccff');
}

Now we need to implement the collectCoin function. In it we will remove the tile that represents the touched coin. Here is the code for it:

function collectCoin(sprite, tile) {
    coinLayer.removeTileAt(tile.x, tile.y); // remove the tile/coin
    return false;
}

Lets add score variable and a text to show how much coins we’ve collected.
Add this at the top of the game near the other variables:

var score = 0;
var text;

And this at the end of the create function:

    text = this.add.text(20, 570, '0', {
        fontSize: '20px',
        fill: '#ffffff'
    });
    text.setScrollFactor(0);

Here we create a Text Game Object with font size of 20 and white color. We set its scroll factor to 0; this tells Phaser to fix it to the screen. Now when a coin is collected we can increment the score and set the text to show it:

function collectCoin(sprite, tile) {
    coinLayer.removeTileAt(tile.x, tile.y); // remove the tile/coin
    score ++; // increment the score
    text.setText(score); // set the text to show the current score
    return false;
}

With this our very simple platformer is ready. From here on its up to you to develop it more and explore the possibilities.

Conclusions

In this tutorial you learned the basics of creating a Mario-style platformer game like creating the world from a map, adding sprites with physics, controlling the interaction between physics objects. There is a lot more that can be done, for example adding a paralax background, creating a bigger level, adding more collectable types and some enemies. With the experience from this article you can now try to develop your own platform game and expand it a bit with some of the above functionality.