💡 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 Chrome's Dino Game in PhaserJS: Part II.

How to Remake Chrome's Dino Game in PhaserJS: Part II.

Ferenc AlmasiLast updated 2021 June 07 • Read time 13 min read
  • twitter
  • facebook
JavaScript

This is the second part of the Remake Dino in PhaserJS. If you missed out on the first part, you can reach it here. We've left off the previous part with managing to draw the dinosaur onto the scene and making it run, using the generated animations. If you're looking for the full project in one piece, you can get the code from the GitHub repository. In this tutorial, we are going to cover the following topics:


Table of Contents

  1. Making Dino Move
  2. Setting a State for the Game
    1. Updating the UI
  3. Spawning Stars
  4. Spawning Cactuses
  5. Updating the Score
  6. Summary

Making Dino Move

Let's start by giving dino the ability to jump when we press the spacebar. Create a new method in our Player class called update and add the following:

Copied to clipboard! Playground
constructor(scene, x, y) {
    ...
    this.isDead = false;
    return this;
}

update(input) {
    if (!this.isDead && this.sprite.body.onFloor()) {
        this.sprite.play('run', true);
    }

    if ((input.space.isDown && this.sprite.body.onFloor())) {
        this.sprite.setVelocityY(-500);
        this.sprite.play('idle', true);
    }
}
objects/Player.js

Note that I've defined another property for the class in the constructor called isDead. This will check if the player has died. We only want to allow movements if the player is still alive, so we need to include it in our if checks. This update method does two things:

  • Checks if the player is alive, and is on the floor, using body.onFloor, and if so, it plays the run animation.
  • It also checks if we are pressing the space bar, and if the player is on the floor, it sets its y velocity to -500, essentially making the sprite jump up, and also plays the idle animation.

Make sure you check if the body is on the floor for the jump as well, otherwise you can press jump again mid-air.

So where is our input coming from? We can set it up in the create method of our dino scene:

Copied to clipboard!
create() {
    this.player = new Player(this, 25, 460);
    this.inputs = this.input.keyboard.createCursorKeys();
}
dino.js

This will return a key state object, that we can use in the update method of our Player class. At the end of the previous part, we've made the dinosaur run using the update method of our scene. Replace that code with the following now:

Copied to clipboard!
update() {
-   this.player.sprite.play('run', true);
+   this.player.update(this.inputs);
}
dino.diff

Now you should be able to press space to make the dinosaur jump. There's only one problem. The "Press space to play" text is still showing, so let's fix that by setting up the game state.

The dino jumping in the canvas.
Looking to improve your skills? Check out our interactive course to master JavaScript from start to finish.
Master JavaScriptinfo Remove ads

Setting a State for the Game

To keep track of the different states in the game, let's create a new state object in the constructor of our Dino scene:

Copied to clipboard! Playground
constructor() {
    super('Dino');

    this.state = {
        started: false
        gameOver: false
    };
}
dino.js

For now, all we need are two flags to check if the game has been started, or if the game is over. Both of them will be false initially. We can use these flags in the update method to decide if we want to start updating the player or not:

Copied to clipboard! Playground
update() {
    if (this.inputs.space.isDown && !this.state.started && !this.state.gameOver) {
        this.state.started = true;
    }

    if (this.state.started) {
        this.player.update(this.inputs);
    }
}
dino.js

This will ensure that the dinosaur will start in a standing position and will only start running once the game has been started, which we can do by pressing the space bar. But the "Press space to play" is still there, so let's introduce another flag to our state and get rid of it.

Copied to clipboard! Playground
this.state = {
    started: false,
    gameOver: false,
    UIUpdated: false
};
dino.js

Inside our update method, we can check if the UI has already been updated or not. The reason we need this flag is because the update method runs constantly, and we want to avoid keep updating the UI once it has already been updated.

Copied to clipboard! Playground
if (this.state.started) {
    this.player.update(this.inputs, delta);

    if (!this.state.UIUpdated) {
        this.updateUI();
    }
}
dino.js

#Upda

Updating the UI

As you can see, I've outsourced the functionality to a method called updateUI, so let's see what is inside of it:

Copied to clipboard! Playground
updateUI() {
    hidePressToPlay();
    hideGameOver();

    showScore();

    this.state.UIUpdated = true;
}
dino.js

We want a couple of elements to be updated. Namely, we want to hide the "Press space to play" text, and the "Game Over" as well in case the player has died. Then we want to show the score and set the UIUpdated flag to true to avoid keep updating the UI. To make updating the UI elements easier, we can outsource these functionalities as well to separate files. Inside the top of your imports, add the following two lines:

Copied to clipboard!
import { showScore } from './ui/score'
import { hidePressToPlay, hideGameOver } from './ui/gameState'
dino.js

And let's create the appropriate files for them. We will need a ui folder with a score.js and gameState.js. For the scoring mechanism, we can create methods for showing/hiding the score and the highscore:

Copied to clipboard! Playground
const score = {
    container: document.querySelector('.score'),
    current: document.querySelector('.current-score'),
    best: document.querySelector('.highscore')
}

export const showScore = () => score.container.classList.remove('hidden');
export const hideScore = () => score.container.classList.add('hidden');
export const showHighScore = () => score.best.classList.remove('hidden');
ui/score.js

Note that I've created a score object at the top of the file to hold references to DOM elements. This way if anything changes, we only need to update the selector in one place.

As for the gameState, we can take a similar approach; outsource the selectors into one place, and create separate functions for showing/hiding the elements:

Copied to clipboard! Playground
const state = {
    pressToPlay: document.querySelector('.press-to-play'),
    gameOver: document.querySelector('.game-over')
}

export const showPressToPlay = () => state.pressToPlay.classList.remove('hidden');
export const hidePressToPlay = () => state.pressToPlay.classList.add('hidden');

export const showGameOver = () => state.gameOver.classList.remove('hidden');
export const hideGameOver = () => state.gameOver.classList.add('hidden');
ui/gameState.js

Spawning Stars

To make the scene more alive let's also add a couple of stars. For this, we already have them loaded inside of a spritesheet. To keep the logic of spawning stars separated from the rest of the game, let's create a new class for this, just as we did for the Player class:

Copied to clipboard! Playground
class Star {
    constructor(scene) {
        const randomSprite = Math.floor(Math.random() * 2);
        const randomXPosition = Math.floor(Math.random() * 620) + 20;
        const randomYPosition = Math.floor(Math.random() * 400) + 20;

        this.sprite = scene.physics.add.staticSprite(randomXPosition, randomYPosition, 'tiles', randomSprite)
            .setScale(2);
    }
}

export default Star;
objects/Star.js

All we need this time is a constructor, that again, receives the scene and adds a staticSprite at a random x and y position. The reason we want a staticSprite is to make the stars immovable, and unaffected by gravity.

Note that using Math.random, we can also select a random sprite to alternate between the two types of stars we have. Also, to keep the stars in the scene, we want to use smaller numbers for Math.random, than the width and height of the canvas. Let's go back to our dino Scene and add a couple of them inside our create method:

Copied to clipboard! Playground
import Star from './objects/Star'

class Dino extends Phaser.Scene {
    constructor() {
        super('Dino');

        this.state = {
            ...
            numberOfStars: 3
        };
    }

    create() {
        this.player = new Player(this, 25, 460);

        for (let index = 0; index < this.state.numberOfStars; index++) {
            new Star(this);
        }

        this.inputs = this.input.keyboard.createCursorKeys();
    }
}
dino.js

For this, we can introduce another state to define the number of stars as a config. If we take a look at our scene now, every time the game is refreshed, we should have 3 different stars drawn to the canvas at random locations:

Spawning stars into different positions into the canvas

Spawning Cactuses

The main elements are still missing from the scene: cactuses. Just as for the Player or the Star class, it's best to create one for the cactus as well:

Copied to clipboard! Playground
class Cactus {
    constructor(scene) {
        this.scene = scene;
        this.sprite = scene.physics.add.sprite(700, 470, 'tiles', 2)
            .setScale(2)
            .setSize(5, 15)
            .setCollideWorldBounds(true);

        this.sprite.body.customBoundsRectangle.left = -100;
        this.sprite.body.customBoundsRectangle.right = 740;
    }
}

export default Cactus;
objects/Cactus.js
Note that the game width is 640, and the cactus is placed at 700

Unlike the stars, this should not be a staticSprite. We want it to be affected by gravity and collide with both the world bounds and the player. We also want to set a custom size using setSize to make the bounding box better fit the shape of the sprite.

The collision box of a cactus
If you set debug to true the collision box is unnecessarily large by default.

Note that we also want it to be placed outside of the world, outside of the player's view. Because of this, we need to extend the left and right side of the world using a customBoundsRectangle, otherwise, the cactus would collide with the world's bounds. Now to actually spawn the cactuses, we want to go back to our scene and add a couple of new elements to our state, and also extend the update method:

Copied to clipboard! Playground
// Don't forget to import the class at the top of your file
import Cactus from './objects/Cactus'

this.state = {
    ...
    cactuses: [],          // An array to hold the cactuses
    cactusDistance: 2000,  // The distance in seconds between two cactuses
    timer: {
        cactusSpawnLoop: 0 // A timer to keep track of the time of last spawn
    }
};

update(time, delta) {
    this.state.timer.cactusSpawnLoop += delta;

    ...

    if (this.state.started) {
        ...

        if (this.state.timer.cactusSpawnLoop > this.state.cactusDistance) {
            this.state.cactusDistance = Phaser.Math.Between(5000, 1000);
            this.state.cactuses.push(new Cactus(this));
            this.state.timer.cactusSpawnLoop = 0;
        }
    }
}
dino.js

Note that the update method accepts two arguments: the current time, and delta time in milliseconds, which calculates the time since the last frame.

To create frame-rate independent movement, we want to increase our timer loop with the value of delta. If this timer is greater than the distance we've set in the state, we can push a new cactus to state.cactuses. The reason we want to hold cactuses in an array is that we also want to reference all of them at once when we need to stop them from moving when the game is over.

We also need to reset the timer and notice that we can randomly increase/decrease the distance from the next cactus using Phaser.Math.Between. This will give back a random number between 5000 and 1000.

The only thing missing is animating them. We want to move them from the right, all the way to the left. We can achieve this with this one line:

Copied to clipboard! Playground
constructor(scene) {
    ...
    this.update();
}

update() {
    this.sprite.setVelocityX(-150);
}
Cactus.js
Animating cactuses from right to left
We are still missing collision detection between the dinosaur and the cactuses

Updating the Score

To finish off this tutorial, let's also update the score so at least we know how far we've got. To do this, let's add a new property to our state to keep track of the current score:

Copied to clipboard!
this.state = {
    ...
    score: 0
};
dino.js

Then open our Player class, and add a new property there as well, called timer:

Copied to clipboard! Playground
class Player {
    constructor(scene, x, y) {
        this.scene = scene;
        this.timer = 0;
        ...
    }
}
Player.js

Inside the update method of the class, we can increase this timer with delta, just like we did for the cactuses, and update the score based on that:

Copied to clipboard! Playground
// Don't forget to import the helper function at the top of your class
import { updateScore } from '../ui/score'

// Don't forget to pass `delta` to the `update` method here, and inside the scene
update(input, delta) {
    this.timer += delta;

    ...

    if (this.timer > 100) {
        this.timer = 0;
        updateScore(this.scene.state);
    }
}
Player.js

This will call the updateScore method every 100 milliseconds. But we haven't defined this function yet, so let's open score.js and add this new function:

Copied to clipboard! Playground
export const updateScore = state => {
    const currentScore = Number(score.current.innerText);

    state.score = currentScore + 1;
    score.current.innerText = (currentScore + 1).toString().padStart(6, '0');
};
ui/score.js

This function will take the current state as a parameter, and updates both the DOM and the state with +1 as well. Note that we want to always have six digits for the score. This is why we need to use padStart, to prefix the current score with the necessary number of zeroes.

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

Summary

And that is all for this tutorial. If you've reached this far, congratulations, you've learned a lot! 🎉 We've looked into how to take user input, how to set state and update the UI of the game, and how to spawn different game objects.

This tutorial is broken down into three sections. Make sure you continue your journey with part III:

If you missed the first part, you can reach it here.

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
Mentoring

Rocket Launch Your Career

Speed up your learning progress with our mentorship program. Join as a mentee to unlock the full potential of Webtips and get a personalized learning experience by experts to master the following frontend technologies:

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.