Learn to create a HTML5 Game in 5 Minutes

February 9, 2019

Building Games with HTML5 and JavaScript has gained a lot of popularity in recent years and many Game Engines / Frameworks (List) were created.

In this article, I will show you how to build a flappy bird clone with the free and powerful Game Framework Phaser 3 in TypeScript (will compile to plain JavaScript).

Flappy Bird clone

Step 1: Prerequisites

To access the source code on github, download and install git from here. You also need node.js with npm, which you get here.

Step 2: Get the full source code

Start your terminal (Windows, Mac), navigate to the desired directory and clone the flappy bird repository:

git clone https://github.com/digitsensitive/phaser3-flappy-bird.git

After cloning:

Now everything is setup and we can dive into the source code.

Step 3: Understand the source code

The core of every phaser game is your Game class, that extends the Phaser.Game class and is given a basic configuration during creation.

1
2
3
4
5
export class Game extends Phaser.Game {
  constructor(config: GameConfig) {
    super(config);
  }
}

We can define many important game parameters in the GameConfig (f.e. game scenes, allowed input). For the sake of simplicity we will keep it very simple and clean for now.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
const config: GameConfig = {
  width: 390,
  height: 600,
  parent: 'game',
  scene: [GameScene],
  input: {
    keyboard: true
  },
  physics: {
    default: 'arcade',
    arcade: {
      gravity: { y: 300 }
    }
  },
  render: { pixelArt: true }
};

For our Flappy Bird game we define a width (390 px) and a height (600 px). We will only have one scene (the Game Scene), allow the user to steer the bird with the keyboard and enable Arcade Physics. Of course we have to define a positive gravity in the y-direction, so that the bird will be pulled down from the sky if no flap is done. Finally we will set pixelArt to true, so that our pixel art will be sharp without any antialiasing.

Phaser 2D Coordinate System

After having setup our game instance and the basic game configuration we proceed to our game scene. Usually you will have multiple scenes in your games and make transitions between them. We will keep it simple with one single scene.

Basic Phaser Workflow

Each scene calls core functions according to a predefined pattern. Independent of Phaser the constructor() of each class will be called at first. Since our GameScene extends Phaser.Scene we must call super() which will execute the constructor of the base class. For now, we can simply take notice of this.

1
2
3
4
5
6
7
export class GameScene extends Phaser.Scene {
  constructor() {
    super({
      key: 'GameScene'
    });
  }
}

Next, we call the init() function which is the right place to initiate variables. In our example we will only set a variable for the score.

1
2
3
init(): void {
    this.registry.set("score", -1);
}

After that we call the preload() function, where we load all our assets. We will make use of the pack function and load all the assets via a JSON File.

1
2
3
4
5
6
7
preload(): void {
    this.load.pack(
        "flappyBirdPack",
        "./src/assets/pack.json",
        "flappyBirdPack"
    );
}

In the create() function we construct all our actual Game Objects:

Last but not least we call the update() function. This function will be called until the scene restarts. If the bird is alive, we will move the background, update the bird actions and check if there is a bird-pipe overlap. If the bird is dead, we will stop all the pipe movements and restart the scene as soon as the bird is out of the screen.

The addRowOfPipes() function is quite interesting and worth a look.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
 private addNewRowOfPipes(): void {
    // update the score
    this.registry.values.score += 1;
    this.scoreText.setText(this.registry.values.score);

    // randomly pick a number between 1 and 5
    let hole = Math.floor(Math.random() * 5) + 1;

    // add 6 pipes with one big hole at position hole and hole + 1
    for (let i = 0; i < 10; i++) {
        if (i !== hole && i !== hole + 1 && i !== hole + 2) {
        if (i === hole - 1) {
            this.addPipe(400, i * 60, 0);
        } else if (i === hole + 3) {
            this.addPipe(400, i * 60, 1);
        } else {
            this.addPipe(400, i * 60, 2);
        }
        }
    }
}

Have a look at the lower illustration. We will loop through 10 boxes (each with a height of 60 x 60 pixels, since we scaled the 20 x 20 pixels pipe by a factor of 3) and create a hole with a height of 3 boxes. I decided to only accept a random number between 1 and 5, so that the hole will neither be at the top nor at the bottom. Depending where the hole is, we will use other pipe graphics (= since it is a spritesheet simply other frames) above and below.

Scaling and Positioning of the Pipes with holes

The pipe class should be self-explanatory. Finally, I would like to discuss the update function of the Bird class.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
update(): void {
    // handle angle change
    if (this.angle < 30) {
        this.angle += 2;
    }

    // handle input
    if (this.jumpKey.isDown && !this.isFlapping) {
        // flap
        this.isFlapping = true;
        this.body.setVelocityY(-350);
        this.scene.tweens.add({
        targets: this,
        props: { angle: -20 },
        duration: 150,
        ease: "Power0"
        });
    } else if (this.jumpKey.isUp && this.isFlapping) {
        this.isFlapping = false;
    }

    // check if off the screen
    if (this.y + this.height > this.scene.sys.canvas.height) {
        this.isDead = true;
    }
}

If the bird angle is less than 30 degree we add 2 degrees each time. If you look at the illustration, the bird will look down more and more.

Bird angle (degrees): 0° is to the right, 90° is down and 180° is left

When the the player clicks the jump button (= the bird is flapping) we will add some velocity upwards and use a tween to change to reset the angle again to -20 degrees within 150 milliseconds. As soon as the player releases the jump button, we will set the boolean isFlapping back to false.

Congratulations! You have reached the end of this tutorial. Thank you for reading this. If you have any questions, feel free to contact me.

For more examples have a look at my Phaser3-Typescript Repository on Github.

Game Development HTML5 Phaser Phaser 3 Typescript