๐Ÿ’ก This page contain affiliate links. By making a purchase through them, we may earn a commission at no extra cost to you.
How to Remake Mario in PhaserJS: Part II.

How to Remake Mario in PhaserJS: Part II.

Ferenc Almasi โ€ข Last updated 2021 June 07 โ€ข Read time 12 min read
  • twitter
  • facebook
JavaScript

This is the second part of the Remake Mario in PhaserJS tutorial. If you missed out the first part, you can reach it here.

How to Remake Mario in PhaserJS

In the previous part, we generated a world in Tiled and imported it into Phaser, and also added Mario into the scene and made him able to freely move around. You can get the code from the GitHub repository. The tilemap I'm using throughout this tutorial is also included. In this part, we will continue populating the world with game objects that the user can interact with. This part will cover the following topics:


Table of Contents

  1. Collecting Coins
  2. Spawning Goombas
  3. Summary

Collecting Coins

Let's start by adding some coins to the world the player can collect. Create an empty Coin class in your gameObjects folder and call it in your create method inside your Game scene:

Copied to clipboard! Playground
class Coin {
    constructor(scene) {
        this.scene = scene;
    }
}

export default Coin;

// In your Game.js create method:
this.coins = new Coin(this);
Coin.js

Just like the Player class, this should take the scene as its argument. To generate the coins, we can use a group this time. Groups in Phaser are useful for grouping similar game objects together. Now when you create the group, make sure you disable gravity for them and set them to immovable:

Copied to clipboard! Playground
constructor(scene) {
    this.scene = scene;
    this.coins = this.scene.physics.add.group({
        immovable: true,
        allowGravity: false
    });
}
Coin.js

Otherwise, you may get the following behavior and Mario will be cast into depression:

Using a non-static group makes coins fly around

Alternatively, you can use a staticGroup which is created for this purpose. Static groups are immovable by default and they are not affected by gravity. To get the coins from the map, you want to access the coin layer in Tiled. This can be done by using the getObjectLayer method of the map we've created in the previous tutorial.

Copied to clipboard! Playground
const coinObjects = this.scene.map.getObjectLayer('coin').objects;
        
for (const coin of coinObjects) {
    this.coins.create(coin.x, coin.y, 'atlas')
        .setOrigin(0)
        .setDepth(-1);
}
Coin.js

This will get every object from the "coin" layer in Tiled, and create a coin inside the group, based on their x and y position. Make sure you set their depth to keep Mario on top of the coins. Again, this can be achieved in other ways as well. For example, there's a handy createFromObjects method that you can use to create the sprites right away:

Copied to clipboard! Playground
const coinSprites = this.scene.map.createFromObjects('coin');

for (const coin of coinSprites) {
    coin.setTexture('atlas')
        .setScale(1) // setTexture resets the scale to .5 so this is needed
        .setOrigin(0)
        .setDepth(-1);
    
    this.coins.add(coin);
}
Coin.js

The end result is the same. Now we have the coins but they are rather looking like little Marios than coins.

Wrong texture is used for the coins

This happens because we have specified that we want to use the atlas for the texture of the coins, but we haven't specified which frame to use. Therefore, it uses the very first frame, which happens to be Mario standing still, so let's change that. Just like we did for the Player class, add an update method for the Coin class as well and call it in your scene's update method:

Copied to clipboard! Playground
update() {
    for (const coin of this.coins.children.entries) {
        coin.play('rotate', true);
    }
}

// Inside your Game.js update method:
this.coins.update();
Coin.js

This would make each coin rotate, but we haven't created the animation for it yet, so open up your animations.js file, and after the animations of Mario, add the following:

Copied to clipboard! Playground
scene.anims.create({
    key: 'rotate',
    frames: scene.anims.generateFrameNames('atlas', {
        prefix: 'mario-atlas_',
        start: 6,
        end: 9,
    }),
    frameRate: 10,
    repeat: -1
});
animations.js

This should make things work. Now we have rotating coins in the world, but we can't interact with them so far, so let's make them collectible.

Coin animation

We need to add collision detection to each coin. This can be done with a simple for loop along with a new collider:

Copied to clipboard!
for (const coin of this.coins.children.entries) {
    coin.collider = this.scene.physics.add.overlap(coin, this.scene.player.sprite, this.collect, null, this);
}
Coin.js

Make sure you assign the collider to a new property on each coin, as we will need to destroy it later, when Mario collides with one of them. Also, note that we are using overlap here instead of collider. They work similarly, both check for collisions between two objects, however, you would use collider where you want objects to be blocked by each other, rather than making them overlap. Also, this references this.collect as a callback function, which we haven't defined yet, so let's do that right now:

Copied to clipboard! Playground
collect(coin) {
    this.scene.tweens.add({
        targets: coin,
        ease: 'Power1',
        scaleX: 0,
        scaleY: 0,
        duration: 200,
        onComplete: () => coin.destroy()
    });
    
    increaseScore(1);

    coin.collider.destroy();
}
Coin.js

Luckily, overlap provides both objects as parameters. We only want to use the coin. The above code will create a tween animation. It will scale down the coin โ€” which is the target โ€” to 0, over 200 ms. Once the animation completes, we can call destroy to get rid of the game object. You can see an increaseScore function call after the animation. I've outsourced this function into a separate file, as we will reuse it later inside other files. This is what the file exports:

Copied to clipboard! Playground
export default score => {
    const scoreElement = document.getElementsByClassName('score-amount')[0];
    const currentScore = Number(scoreElement.innerText);

    scoreElement.innerText = currentScore + score;
};
increaseScore.js

This is imported into Coin.js, and we want to increase the score by 1 for every coin collected. Lastly, make sure you call destroy on coin.collider as well, otherwise, the callback function keeps running for every frame and you get more scores than you should.

Collider executing multiple times.
This is what happens if you don't remove the collider right after the first collision.

As noted out by a helpful Discord user on the phaser channel, using a single collider for the whole group is also achievable and more efficient, than using one for each coin. See 53d6065 for the necessary changes.

Looking to improve your skills? Check out our interactive course to master JavaScript from start to finish.
Master JavaScriptinfo Remove ads

Spawning Goombas

Now that we have all the functionality that's needed for the coins, let's turn our attention to the goombas. Goombas, also known as Kuribล in Japan means "close friend". They are little, mushroom-like species, one of the most common forms of enemies in Super Mario. Fun fact: In Hungarian "gomba" means mushroom, although the two words have nothing to do with each other, their resemblance is pure coincidence. By now, you know how it goes. Create a new class called Goomba and call it in your Game scene:

Copied to clipboard! Playground
class Goomba {
    constructor(scene) {
        this.scene = scene;
    }
}

export default Goomba;

// In your Game.js create method, instantiate the class:
this.goombas = new Goomba(this);

// In your Game.js update method, call update on it
this.goombas.update();
Goomba.js

No surprise, we're going to have an update here as well, so you can call it right away in your scene's update method. We're going to use a group for this too, however, they won't be immovable:

Copied to clipboard! Playground
constructor(scene) {
    this.scene = scene;
    this.goombas = this.scene.physics.add.group();
    this.collider = this.scene.physics.add.collider(this.scene.player.sprite, this.goombas, this.gameOver, null, this);

    const goombaObjects = this.scene.map.getObjectLayer('goomba').objects;

    for (const goomba of goombaObjects) {
        this.goombas.create(goomba.x, goomba.y - goomba.height, 'atlas')
            .setScale(1.5)
            .setOrigin(0)
            .setDepth(-1);
    }

    for (const goomba of this.goombas.children.entries) {
        goomba.direction = 'RIGHT';
        goomba.isDed = false;
    }

    this.scene.physics.add.collider(this.goombas, this.scene.platform);
}
Goomba.js

After their creation, we want to add some custom properties for each goomba. Their current heading direction, either RIGHT or LEFT, and a boolean flag to check if they are dead. I've also set their scale to 150% to make them a little bit bigger, and don't forget to set the depth to -1 if you want Mario to stay on top of the layers. At the end of your constructor add a collider between the goombas and the platform to make them stay on top. We have another collider for Mario and the Goombas, this will call this.gameOver. For now, let's only see what we want to happen when Mario steps on a goomba from above.

Copied to clipboard! Playground
gameOver() {
    // PHEW
    if (this.scene.player.sprite.body.touching.down) {
        this.die();

        return;
    }

    // Otherwise, it's game over
}
Goomba.js

Using the sprite's body.touching object, we can determine, which side of the player is touching other game objects. If it's the bottom, called down, we've stepped on a goomba, which should be exterminated.

Copied to clipboard! Playground
die() {
    for (const goomba of this.goombas.children.entries) {
        if (goomba.body.touching.up) {
            goomba.isDed = true;
            goomba.play('goombaDie', true);
            goomba.on('animationcomplete', () => goomba.destroy());

            increaseScore(.5);

            this.scene.player.sprite.setVelocity(0, -350);
            this.scene.player.sprite.play('jump');
        };
    }
}
Goomba.js

Since we are storing goombas in a group, we need to loop through the group to check which goomba has been hit by Mario. We can check again if the sprite's body is touching anything else. Here we set the goomba to be dead, and make Mario jump โ€” essentially bouncing up from the goomba. We also want to give some score to the player and play the dying animation on the goomba, which we haven't defined yet, so make sure to do that in our animations.js:

Copied to clipboard! Playground
scene.anims.create({
    key: 'goombaRun',
    frames: scene.anims.generateFrameNames('atlas', {
        prefix: 'mario-atlas_',
        start: 11,
        end: 12,
    }),
    frameRate: 15,
    repeat: -1
});

scene.anims.create({
    key: 'goombaDie',
    frames: [{ key: 'atlas', frame: 'mario-atlas_10' }],
    frameRate: 10,
    hideOnComplete: true
});
animations.js

Conveniently, Phaser provides a hideOnComplete flag we can use to hide the animation once it's completed. Make sure to also add the running animation. We can use this in our update method, along with some logic to keep the goomba move back and forth:

Copied to clipboard! Playground
update() {
    for (const goomba of this.goombas.children.entries) {
        if (goomba.body.blocked.right) {
            goomba.direction = 'LEFT';
        }

        if (goomba.body.blocked.left) {
            goomba.direction = 'RIGHT';
        }

        if (goomba.direction === 'RIGHT') {
            goomba.setVelocityX(100);
        } else {
            goomba.setVelocityX(-100);
        }

        !goomba.isDed && goomba.play('goombaRun', true);
    }
}
Goomba.js

We can use body.blocked to check if the sprite is blocked by something else. In case it is, we simply switch directions and set the velocity to the opposite.

Killing goombas
Stepping on a goomba, will kill it, but nothing happns so far if we collide with it from the left or right side

Summary

And that is all for this tutorial. If you've reached this far, congratulations, you've learned a lot! ๐ŸŽ‰ You now know how to create different game objects with different functionalities based on objects defined in Tiled. This tutorial is broken down into three sections. Make sure you continue your journey with part III, where we implement the game over and winning. We will also have a look into Phaser's particle system.

How to Remake Mario in PhaserJS

Do you have experience building platform games in PhaserJS? Let us know your thoughts and opinions about it in the comments below! Thank you for reading through, happy coding! ๐ŸŽฎ

  • twitter
  • facebook
JavaScript
Did you find this page helpful?
๐Ÿ“š More Webtips
Frontend Course Dashboard
Master the Art of Frontend
  • check Access 100+ interactive lessons
  • check Unlimited access to hundreds of tutorials
  • check Prepare for technical interviews
Become a Pro

Courses

Recommended

This site uses cookies We use cookies to understand visitors and create a better experience for you. By clicking on "Accept", you accept its use. To find out more, please see our privacy policy.