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

This is the third, and last part of the Remake Dino in PhaserJS. If you missed out on the first part, you can reach it here, for the second part, you can follow this link. If you're looking for the full project in one piece, you can get the code from the GitHub repository.

We've left off the previous part with managing to spawn cactuses into the way of our dinosaur. In this tutorial, we will finish off the game by covering the remaining topics:

Table of Contents

  1. Increasing the Game Speed
  2. Game OverStoring the high score
  3. Restarting the Game
  4. Conclusion

Increasing the Game Speed

First of all, the game currently runs at a constant speed. There's no real challenge, the game doesn't get harder over time in any way. To fix that, let's introduce speed, just like the original game speeds up over time. For this, we are going to need two other state variables, called speed, and speedLoop:

this.state = {
    ...
    speed: 1,
    timer: {
        speedLoop: 0,
        cactusSpawnLoop: 0
    }
};
dino.js
Copied to clipboard!

While state.speed will hold the current speed of the game, state.timer.speedLoop will keep track of time to increase the speed by a fraction every 10 seconds. To increase it, we need to update the update method in the following way:

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

    if (this.inputs.space.isDown && !this.state.started && !this.state.gameOver) {
        this.state.started = true;
    }

    if (this.state.started) {
        this.player.update(this.inputs, delta);
            
        if (!this.state.UIUpdated) {
            this.updateUI();
        }
            
        if (this.state.timer.cactusSpawnLoop > this.state.cactusDistance) {
            this.state.cactusDistance = Phaser.Math.Between(5000 / this.state.speed, 1000 / this.state.speed);
            this.state.cactuses.push(new Cactus(this));
            this.state.timer.cactusSpawnLoop = 0;
        }
 
+       if (this.state.timer.speedLoop > 10000) {
+           this.state.timer.speedLoop = 0;
+           this.state.speed += .25;
+       }
    }
}
dino.diff
Copied to clipboard!

Just like we did for the cactusSpawnLoop, we need to add delta time to the speedLoop as well, to keep track of the time passed. Then when the game started, we can check if 10000 milliseconds have passed, and increase the speed by 0.25. On its own, this won't do much, we only have a speed inside the state that is increasing over time. However, we can use this number to increase difficulty. One way to do that is to reduce the distance between each cactus. To do that, modify cactusDistance to the following:

if (this.state.timer.cactusSpawnLoop > this.state.cactusDistance) {
-   this.state.cactusDistance = Phaser.Math.Between(5000, 1000);
+   this.state.cactusDistance = Phaser.Math.Between(5000 / this.state.speed, 1000 / this.state.speed);
    this.state.cactuses.push(new Cactus(this));
    this.state.timer.cactusSpawnLoop = 0;
}
dino.diff
Copied to clipboard!

We divide the time it takes for each cactus to spawn to reduce the distance between them. To further increase difficulty, you could also divide state.cactusDistance by the speed inside the if statement. Of course, with higher difficulty comes a higher reward, so let's go inside the Player class and update the scoring logic:

update(input, delta) {
    this.timer += delta;
     
-   if (this.timer > 100) {
+   if (this.timer > 100 / this.scene.state.speed) {
        this.timer = 0;
        updateScore(this.scene.state);
    }
}
Player.diff
Copied to clipboard!

As time passes, the score will be updated at an increasing pace. The longer we survive, the more cactuses we will face, and the faster we will collect the score.

Increasing the speed of the game over time
Speed increases every 2,5 seconds, as well as the score updates at an increasing speed.

Game Over

Still, there's no price we pay, as we cannot die. So as a next step, let's add the necessary collision. We only need one between the dino and the cactuses. To do that, let's open up our Cactus class, and add a new collider into the constructor:

class Cactus {
    constructor(scene) {
        ...
        this.collider = scene.physics.add.collider(scene.player.sprite, this.sprite, this.gameOver, null, this);
    }

    gameOver() {
        this.scene.player.die();
    }
}
Cactus.js
Copied to clipboard!

We can add colliders in Phaser by calling physics.add.collider with a couple of arguments:

As for the gameOver method, we have one single line: calling player.die. Since we haven't added this function yet, let's go into our Player class and do that now:

// Make sure you add `showHighScore` to your imports
import { updateScore, showHighScore } from '../ui/score'

die() {
    this.isDead = true;
    this.sprite.play('idle', true);

     this.scene.state.started = false;
     this.scene.state.gameOver = true;

     showHighScore();
     showGameOver();
}
Player.js
Copied to clipboard!

This will stop the dinosaur from playing the running animation and sets all necessary states to stop the game. On its own, this will not stop the incoming fleet of cactuses, however. To do that, let's add a new method to our Cactus class:

stop() {
    this.sprite.setVelocityX(0);
}
Cactus.js
Copied to clipboard!

This method is straightforward, it will set the velocity of the cactus back to zero. So where do we call this? Inside the update method of our scene. Remember that we've set state.gameOver to true in the die method of our player? We can use this flag to loop through each cactus we store in the state to stop them:

if (this.state.gameOver) {
    this.state.cactuses.forEach(cactus => cactus.stop());
}
dino.js
Copied to clipboard!

Storing the high score

If we run the game now, as soon as we hit a cactus, we should be greeted with a "Game Over". There's only one problem still. The high score remains at zero.

The highscore is not updated at the moment
Notice that the high score is still sitting at 0.

To fix this, let's add highScore to our state, so we can keep track of it:

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

We will also need a new method inside score.js, in order to update it. This function will set it to the current score:

export const setHighScore = state => {
    state.highScore = state.score;
    score.best.innerText = `HI ${state.score.toString().padStart(6, '0')}`;
};
ui/score.js
Copied to clipboard!

It needs to take the state as an argument in order to update that as well. Just like we did for the score, we need to use padStart here again. And to actually call it, we can add the following statement into the die method of our player:

die() {
    this.isDead = true;
    this.sprite.play('idle', true);

    this.scene.state.started = false;
    this.scene.state.gameOver = true;

    showHighScore();
    showGameOver();

+   if (this.scene.state.score > this.scene.state.highScore) {
+       setHighScore(this.scene.state);
+   }
}
Player.diffThe high score will only be set if it's greater than the current score. Don't forget to import `setHighScore`
Copied to clipboard!

Restarting the Game

There's only one thing missing from the game, and that is resetting it, so we can go another round. For this, let's add a last check to the update method of our Dino scene:

if (this.inputs.space.isDown && this.state.gameOver) {
    this.restartGame();
}
dino.js
Copied to clipboard!

We will be able to restart the game once the gameOver state is set to true, and we are pressing the space bar. Let's take a look at what we need to do in the restartGame method:

restartGame() {
    hideGameOver();
    resetScore(this.state);

    this.state.started = true;
    this.state.gameOver = false;
    this.state.speed = 1;
    this.state.cactuses.forEach(cactus => cactus.sprite.destroy());
    this.state.cactuses = [];

    this.player.isDead = false;
}
dino.js
Copied to clipboard!

We need to hide the "Game Over" text and reset the score โ€” which we yet to define โ€” as well as reset most of our state variables. Now we can finally make use of the cactuses array. To free up memory, we can call sprite.destroy to remove them, and also set the array to a new, empty one.

To further improve performance, we can destroy sprites as soon as they leave the scene. They will never come back, so there's no reason to keep them.

Also, make sure to reset the isDead state of the player. So to reset the score, we need one last function for score.js:

export const resetScore = state => {
    state.score = 0;
    score.current.innerText = '000000';
}
ui/score.js
Copied to clipboard!

It simply resets the state and sets the UI back to all zeroes. Now we have a working dinosaur game with cactuses, animations, scoring, and collisions.

Playing the finished dinosaur game
If we get a game over, we can restart by pressing space. The high score is also updated correctly.

Conclusion

And with that, you've just finished your very first game in Phaser! ๐ŸŽ‰ You've successfully created the base gameplay mechanics of Chrome's Dinosaur! If you've reached this far, congratulations! You've learned how to set up a new Phaser project with Parcel, how to set up animations, and add sprites to the game that are responding to user inputs. We've looked into how we can spawn enemies, and update the UI based on the game state.

If you would like to continue your journey in game development, make sure you check out how you can recreate the base gameplay mechanics of the famous Super Mario Bros. using Phaser:

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! ๐ŸŽฎ

Remove ads
Remove ads

๐Ÿ“š Get access to exclusive content

Want to get access to exclusive content? Support webtips with the price of a coffee to get access to tips, checklists, cheatsheets, and much more. โ˜•

Get access Support us
Remove ads Read more on
Remove ads
Remove ads
๐ŸŽ‰ Thank you for subscribing to our newsletter. x 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.