Adding Coins and Lives to the Mario-Style HTML5 Platformer

NOTE: This tutorial is not updated to the latest Quintus version and doesn’t run with it.

This tutorial is the third part in the Mario HTML5 platformer series, so get ready to dig in deeper into the awesome Quintus game framework. In this tutorial we’ll learn how to add coins, lives to our player and how to show all of this in a “player stats” area.

Are you a French speaker? If so, thanks to Alexandre Bianchi you can enjoy an adapted French version of this tutorial.

Tutorial goals

  1. Implementing “lives” for our player.
  2. How to collect coins.
  3. Loading the coins location from a JSON object.
  4. Displaying a “player stats” area and keeping it updated.
  5. Searching for an object in a scene.

Get Another HTML5 Gamedev Shot

If you are enjoying this tutorial and want a lot more of this stuff feel free to check out our HTML5 game development online courses that cover all the basics to get started:

HTML5 Mobile Game Development for Beginners

Requirements

The requirements for the dodgy Mario clone tutorial series are:

  • Familiarity with HTML, CSS, JavaScript and basic object oriented concepts.
  • Clone or download Quintus from it’s Github page.
  • Setup a local webserver. We need to run the code in this tutorial in a web server and not by just double clicking on the files. WAMP for Windows, MAMP for Mac. On linux just type sudo apt-get install apache2.
  • Download and install the Tiled game editor, available for Linux, Mac and Windows
  • Have your favorite IDE ready (Netbeans, Eclipse, Sublime2, Notepad++, VIM, or any tool you use for coding).
  • Have your knuckles cracked and ready for a new dive into HTML5 gamedev.

Tutorial Assets

Get the full tutorial source code and images here. All images used in this tutorial have a Public Domain license.

Heads up!
I’ve provided the Quintus files among the tutorial source code but keep in mind that the framework is under heavy development. It’s be best if you just grab them straight from the Github page. Also, keep an eye on the repository as you work on your games since new features are being added.

Player Stats

In these series we’ve been working all along with just one scene: the level that contains the actual game. Quintus allows us to keep on many scenes on the screen so that for instance we can be scrolling through the level in one, but the other ones stay on their same place and don’t get scrolled.

Being more clear on this point, imagine we just use one scene, the level one, and we add player stats to it such as the number of lives and the number of coins. It’s all gonna look good until you start working and the viewport camera moves away from your initial location. The player stats will stay behind as the rest of the level. This concept is explored in more depth in our HTML5 Educational Game tutorial.

Anyway, let’s create a second scene called “gameStats”:

Q.scene("gameStats", function(stage) {
    var statsContainer = stage.insert(new Q.UI.Container({
        fill: "gray",
        x: 960/2,
        y: 620,
        border: 1,
        shadow: 3,
        shadowColor: "rgba(0,0,0,0.5)",
        w: 960,
        h: 40
        })
    );
        
    var lives = stage.insert(new Q.UI.Text({ 
            label: "Lives x 3",
            color: "white",
            x: -300,
            y: 0
        }),statsContainer);
    
    var coins = stage.insert(new Q.UI.Text({ 
            label: "Coins x 0",
            color: "white",
            x: 300,
            y: 0
        }),statsContainer);
});

What we are doing here is create a new scene that has a gray container which goes on the bottom of the page. This container has two Q.UI.Text objects, one for the lives and one for the coins. They both start with default values. In order to learn more about the Q.UI.Text and the containers check the file quintus_ui.js from the library and the HTML5 Educational Game tutorial.

Staging the game stats scene would be the last step. The asset loading should look like this (see that we are also adding a coin.png asset file, to be used next):

//load assets
Q.load("tiles_map.png, player.png, slime.png, fly.png, level1.tmx, coin.png", function() {            
    Q.sheet("tiles","tiles_map.png", { tilew: 70, tileh: 70});          
    Q.stageScene("level1");
    Q.stageScene("gameStats",1);
});

The second parameter when staging the scene is the index (0 by default). Scenes with higher index show on top of scenes with lower index.

Coins

Righto. All platformers need coins or something like them (diamonds, gold, fruits?). Their implementation using Quintus and the stuff we’ve learned so far is quite straightforward:

Q.Sprite.extend("Coin", {
    init: function(p) {
        this._super(p, {asset: "coin.png"});
    }            
});

We’ll load the coins in our level initialisation using a JSON object in a similar manner to how we did it with the enemies:

//level assets. format must be as shown: [[ClassName, params], .. ] 
            var levelAssets = [
                ["GroundEnemy", {x: 18*70, y: 6*70, asset: "slime.png"}],
                ["VerticalEnemy", {x: 800, y: 120, rangeY: 70, asset: "fly.png"}],
                ["VerticalEnemy", {x: 1080, y: 120, rangeY: 80, asset: "fly.png"}],
                ["GroundEnemy", {x: 6*70, y: 3*70, asset: "slime.png"}],
                ["GroundEnemy", {x: 8*70, y: 70, asset: "slime.png"}],
                ["GroundEnemy", {x: 18*70, y: 120, asset: "slime.png"}],
                ["GroundEnemy", {x: 12*70, y: 120, asset: "slime.png"}],
                ["Coin", {x: 300, y: 100}],
                ["Coin", {x: 360, y: 100}],
                ["Coin", {x: 420, y: 100}],
                ["Coin", {x: 480, y: 100}],
                ["Coin", {x: 800, y: 300}],
                ["Coin", {x: 860, y: 300}],
                ["Coin", {x: 920, y: 300}],
                ["Coin", {x: 980, y: 300}],
                ["Coin", {x: 1040, y: 300}],
                ["Coin", {x: 1100, y: 300}],
                ["Coin", {x: 1160, y: 300}],
                ["Coin", {x: 1250, y: 400}],
                ["Coin", {x: 1310, y: 400}],
                ["Coin", {x: 1370, y: 400}]
            ];
            
            //load level assets
            stage.loadAssets(levelAssets);              
Heads up!
It’s recommended you try to load all the entities in the game such as enemies, items, etc, using JSON objects. JSON is a data exchange protocol which is commonly used to receive and send information to and from the web. If your plan is to eventually load level data from the web or from the local storage it’s good that you keep JSON in mind and get used to working with this format.

Our level can be filled up with coins now but we still need to be able to grab them and keep a total of coins. Let’s update a bit our Player definition:

Q.Sprite.extend("Player",{
    init: function(p) {
        this._super(p, { asset: "player.png", x: 110, y: 50, jumpSpeed: -400, coins: 0});
        this.add("2d, platformerControls"); 

        this.on("hit.sprite",function(collision) {
            if(collision.obj.isA("Coin")) {
                collision.obj.destroy();
                this.p.coins++;
                //TODO: update the GUI
                
            }
        });
    },
    ...

This is firstly, setting an initial value of 0 coins. We are checking the collision with coin objects. If we find a coin then destroy the coin and increase the number of coins in 1. We still need to update the GUI, which we’ll be doing next.

Searching for the GUI

We grabbed a coin, destroyed it and increased our coin count. The GUI needs to be updated, but how do we find it? we need to search for it. Quintus has it’s own way of searching for objects:

this.on("hit.sprite",function(collision) {
    if(collision.obj.isA("Coin")) {
        collision.obj.destroy();
        this.p.coins++;
        var coinsLabel = Q("UI.Text",1).items[1];
        coinsLabel.p.label = 'Coins x '+this.p.coins;
        
    }
});

By doing

Q("UI.Text",1).items[1];

we are searching for all Q.UI.Text objects in the scene located at index 1. This returns an object, where the property “items” contains an Array with all the results. In this case the index 1 (second item in the Array) contains the text we are looking for. Index 0 of the array contains the lives text.

Once we find the text object we are looking for, we just update the label property (object.p.label, don’t forget the p which we always use to access an entity’s parameters in Quintus).

Player Lives

Our game so far is extremely tough. One strike and you are out! we’ll implement now player lives or energy. If the lives run out then we are getting the game over kick.

There are many ways in which lives / energy can be implemented. The way it’ll be in this example is:

  1. The player is hit by an enemy (same collision rules we have in place)
  2. The lives go minus one
  3. The player is “invincible” for 1 second, so that if you are hit again right away it won’t take a second life
  4. After the 1 second you can be hit again if you are not careful
  5. If the lives run out.. game over!

When the player is hit, ideally, the player sprite should be blinking for that second or have some sort of visual cue. Since this is just a tut not a real game we won’t show any visual cue.

Setup an initial number of 3 lives:

Q.Sprite.extend("Player",{
    init: function(p) {
        this._super(p, { asset: "player.png", x: 110, y: 50, jumpSpeed: -400, lives: 3, coins: 0});
        ...

Modify the enemies so that now they don’t kill you, but damage() you instead.

//component for common enemy behaviors
Q.component("commonEnemy", {
    added: function() {
        var entity = this.entity;
        entity.on("bump.left,bump.right,bump.bottom",function(collision) {
            if(collision.obj.isA("Player")) {                        
              collision.obj.damage();
            }
        });
        entity.on("bump.top",function(collision) {
            if(collision.obj.isA("Player")) { 
                //make the player jump
                collision.obj.p.vy = -100;
                
                //kill enemy
                this.destroy();
            }
        });
    },
    
});

The damage() method in the player will follow the steps mentioned before. It will make you invincible for 1 second and then you’ll be damageable again. We also need to update step() to keep track of this 1 second. The following goes inside Player:

step: function(dt) {
    if(Q.inputs["left"] && this.p.direction == "right") {
        this.p.flip = "x";
    } 
    if(Q.inputs["right"]  && this.p.direction == "left") {
        this.p.flip = false;                    
    }
    
    if(this.p.timeInvincible > 0) {
        this.p.timeInvincible = Math.max(this.p.timeInvincible - dt, 0);
    }
},
damage: function() {
    //only damage if not in "invincible" mode, otherwise beign next to an enemy takes all the lives inmediatly
    if(!this.p.timeInvincible) {
        this.p.lives--;
        
        //will be invincible for 1 second
        this.p.timeInvincible = 1;
        
        if(this.p.lives<0) {
            this.destroy();
            Q.stageScene("endGame",1, { label: "Game Over" }); 
        }
        else {
            var livesLabel = Q("UI.Text",1).first();
            livesLabel.p.label = "Lives x "+this.p.lives;
        }
    }
}

This part:

var livesLabel = Q("UI.Text",1).first();

is updating the GUI in a similar way to the coin counter, but using a first() method that the returned object comes with (remember that index 0 in the Array was the lives text).

This gives us a playable demo where you can collect coins, jump around, kill bad guys and if you are not careful they can take your lives away and kill you!

Congratulations

Congrats if you’ve got all the way here! you have now the basics of what can become a great game! Some ideas on what to build next:

  • Visual cue when the player is damaged
  • Multiple levels
  • Level boss
  • Grab extra lives
  • Enemies that take coins away instead of lives

What Else to Check Out

If you like my teaching style, at Zenva we have two high quality video courses on HTML5 game development where we build several examples of different types of games, such as a top-view shooting bad guys Zelda-style game, spaceship games, farming games, virtual pet games and many other examples!

HTML5 Mobile Game Development for Beginners