Build your own Flappy Bird game with Cocos Creator (Part 2)
2020.04.14 by COCOS
Tutorials Beginners Cocos Creator 2D

If you are confused as to why we're halfway through a tutorial, you might have figured out you need to go back to part 1.

Go to part 1

Now that we are back on track, let's start talking about how to add the pipes to the game.

Obstacles as Prefabs

We need to add obstacles to the game world. This section helps to understand how to make obstacles into prefabricated resources, also know as Prefabs, and then instantiate and control the movement of these obstacles from JavaScript code.

Note: It is crucial to understand the importance of Prefabs. Please make sure to read the Prefabs

Implementing

First, create an empty Pipe node, and then create two Sprite child nodes to the Pipe, as shown in the following figures:

40
41

Next, create a Prefab directory under the assets directory (used to put all prefab resources for this game).

42

Third, drag the Pipe node into the Prefab directory, and then a Pipe prefab resource will be generated in the Prefab folder.

43
44

Fourth, delete the Pipe node under the Canvas node and use code control to generate and move directly in the script. Add the following code to MainControl.ts:

    @property(cc.Prefab)
    pipePrefab: cc.Prefab = null;

    pipe: cc.Node[] = [null, null, null]

    start () {
        for (let i = 0; i < this.pipe.length; i++) {
            this.pipe[i] = cc.instantiate(this.pipePrefab);
            this.node.addChild(this.pipe[i]);

            this.pipe[i].x = 170 + 200 * i;
            var minY = -120;
            var maxY = 120;
            this.pipe[i].y = minY + Math.random() * (maxY - minY);
        }
    }

The first pieces of code: declare a prefab in the MainControl script, and modify it with @property (cc.Prefab) to display it in the IDE's MainControl script.

The second piece of code: declares an array of type cc.Node with a length of 3, which is 3 pipe obstacles.

The third piece of code: the start() function is one of the Cocos Creator life cycle functions, and it is triggered after the onLoad() function is triggered. start() is usually used to initialize some intermediate state data, which may change during update() and is frequently enabled and disabled. Instantiate the pipe array with the pipe prefab in the start( ) function, and add these 3 instantiated nodes to the Canvas node. Lines 42 to 45 of the code assigned values to the three pipes. The x coordinate is placed at intervals of 200 pixels, and the y coordinate is random within the range of (-120, 120).

45

Add the following code to MainControl.ts in the update() function:

        // move pipes
        for (let i = 0; i < this.pipe.length; i++) {
            this.pipe[i].x -= 1.0;
            if (this.pipe[i].x <= -170) {
                this.pipe[i].x = 430;

                var minY = -120;
                var maxY = 120;
                this.pipe[i].y = minY + Math.random() * (maxY - minY);
            }
        }

This code mainly moves 3 pipes to the left by 1 pixel per frame. When a pipe moves out of the screen from the left (the abscissa is less than or equal to -170), the abscissa of this pipe is assigned a value of 430, and then height is re-acquired.

Save your progress

Save your changes in the text editor that you are using. Find the Save button in Cocos Creator and click Save once.

Read about our physics system

Cocos Creator has a built-in physics system. This game simply uses the Collider component, part of the collision component in the physics system.

For further learning, please review the: Collision documentation.

Adding Collider Components

First, we need to add a Collider component to our bird:

47

Second, we should use the BoxCollider component directly. The editing option is unchecked by default. If it is checked, we can see that there is a green box around the bird. You can use the mouse to change the size of the collision body. In this game, we can just use the default size, there is no need to change this adjustment.

48

Next, double-click the prefab resource to enter the prefab editing interface. We need to add a collision body to the obstacle:

Fourth, add collision components to both the upper and lower pipes, then save and return to the scene for editing.

Fifth, double-click the MainControl.ts TypeScript file and edit the code:

    onLoad () {
        //open Collision System
        var collisionManager = cc.director.getCollisionManager();
        collisionManager.enabled = true;
        //open debug draw when you debug the game
        //do not forget to close when you ship the game
        collisionManager.enabledDebugDraw = true;
    }

Adding the above code to the onLoad() function does two main things:

1: It enables collision detection (the detection is disabled by default). Only nodes with collision components attached will be checked for collision in the game.

2: It enables Debug mode, so we can see the shape and size of the collision body of the node.

Save your progress

Save your changes in the text editor that you are using. Find the Save button in Cocos Creator and click Save once.

Birds and obstacles are surrounded by white lines, which is the shape of the collision body. By looking at the above .gif notice the collision body will move and rotate with the node.

Continuing with collisions

Next, open the BirdControl.ts TypeScript file and add the following code:

    onCollisionEnter (other: cc.Collider, self: cc.Collider) {
        //game over
        cc.log("game over");
    }

The onCollisionEnter() function is the callback function of the engine’s default collision start event. Each node that implements a collision body will trigger this function when a collision starts.

The above code indicates that after a bird collision, a game over message is output to the console. You can open a browsers console by using Command + Option + I or Ctrl + Shift + I.

53

When you see the console output game over, it means that the onCollisionEnter() function in BirdControl.ts has been triggered when the bird and the pillar come into contact.

However, the player does not know that the game is over. We will continue to make our game more playable in the next sections.

Save your progress

Save your changes in the text editor that you are using. Find the Save button in Cocos Creator and click Save once.

Better logic

In the previous tutorial, we added code to output on the console when the game is over. This tutorial builds on this to improve the logic for ending the game.

First, add a Sprite node to the Scene. This Node will be used to display the gamemOver sprite.

54

Modify the MainControl.ts script file to add the following code:

onLoad() {
    // open Collision System
    var collisionManager = cc.director.getCollisionManager();
    collisionManager.enabled = true;
    // open debug draw when you debug the game
    // do not forget to close when you ship the game
    collisionManager.enabledDebugDraw = true;
    // find the GameOver node, and set active property to false
    this.spGameOver = this.node.getChildByName("GameOver").getComponent(cc.Sprite);
    this.spGameOver.node.active = false;
}   

The above code accomplished the following:

  • Declare a spGameOver sprite. Note that @property (cc.Sprite) is not used at this time. This is mainly to access the component in other ways.
  • Initializes spGameOver sprite. This happens by first getting the node named GameOver, and then getting the cc.Sprite component through the getComponent API interface. You only need to get the node here, because the active attribute is the node. Assigning the active attribute to false would hide the node.

When the game ends, it is OK to display the spGameOver sprite.

Modularity and referencing modules

However, the collision event is triggered in the BirdControl.ts script. How can the MainControl.ts script know about this event being triggered? The concept of modularity and how to reference modules.

First, add a gameOver function to the MainControl.ts script. Add the following code to the MainControl.ts:

gameOver () {
    this.spGameOver.node.active = true;
}

Second, modify BirdControl.ts as follows:

import MainControl from "./MainControl";

const {ccclass, property} = cc._decorator;

@ccclass
export default class BirdControl extends cc.Component {

    // Speed of bird
    speed: number = 0;

    // assign of main Control component
    mainControl: MainControl = null;

    onLoad () {
        cc.Canvas.instance.node.on(cc.Node.EventType.TOUCH_START, this.onTouchStart, this);
        this.mainControl = cc.Canvas.instance.node.getComponent("MainControl");
    }
    
    onCollisionEnter (other: cc.Collider, self: cc.Collider) {
        // game over
        cc.log("game over");
        this.mainControl.gameOver();
    }

The above code accomplished the following:

  • Import the MainControl module into the current module.
  • Declare a variable of type MainControl (only the first block of code is written here, the type of MainControl can be recognized).
  • Get a reference to the MainControl script component in the onLoad() method.
  • When a collision occurs, use the MainControl script to reference the object to call in the gameOver() function. The MainControl module receives the message that the game is over.

At this step, running our tutorial should look similar to this:

56

When the bird collides with the pillar, the GameOver sprite is displayed, but it was blocked by the pillar. This is because the pillar is added to the Canvas node after the game is started, therefore it is rendered after the GameOver sprite.

Final touches

First, to fix the rendering order, create an empty Pipe node and drag this Pipe node to the top of GameOver.

Next, modify the following code in the MainControl.ts script. Get the node named Pipe, and add all the instantiated pipes to the Pipe node. This will cover the pipes by the GameOver sprite.57590×518 19.7 KB
581248×488 49.9 KB

start () {
    for (let i = 0; i < this.pipe.length; i++) {
        this.pipe[i] = cc.instantiate(this.pipePrefab);
        this.node.getChildByName("Pipe").addChild(this.pipe[i]);

        this.pipe[i].x = 170 + 200 * i;
        var minY = -120;
        var maxY = 120;
        this.pipe[i].y = minY + Math.random() * (maxY - minY);
    }
}

So where are we?

At this step, our tutorial should look similar to this:

59

Save your progress

Save your changes in the text editor that you are using. Find the Save button in Cocos Creator and click Save once.

Adding game state

First, add a start button to the scene:

60

When the button is clicked, the effect is changed to a zoom effect.

61

Second, add a background image to the button.

63

Third, delete the Label node above the button.

62

These steps create a button named BtnStart and added it to the Canvas node.

Fourth, modify the MainControl.ts script to add the above code:

64
65
66
67
68
69

Save your progress

Save your changes in the text editor that you are using. Find the Save button in Cocos Creator and click Save once.

Tallying a score

This part of the our Flappy Bird tutorial series will add scoring. Why keep a score? Bragging rights, of course!

First, add a scoring Label node:

71
72

Second, create a Label text node called LableScore, set the Y coordinate, and bind it to the MainControl.ts script.

Third double-click the Pipe prefab to add a Collider component to the root node of the prefab.

Fourth, adjust the size of the Collider component so that the size of the collider is full of gaps. Set the tag property to 1 to distinguish it from the collider of the obstacle.

77

The width of the Collider component is recommended to be smaller than the width of the pipe, which will perform better.

78

Fifth, add the following code to the MainControl.ts script:

73
    @property(cc.Label)
    labelScore: cc.Label = null;

    // record score
    gameScore: number = 0;
75
    // reset score when restart game
    this.gameScore = 0;
    this.labelScore.string = this.gameScore.toString();

Sixth, add the following code to the BirdControl.ts script:

onCollisionEnter (other: cc.Collider, self: cc.Collider) {
    // collider tag is 0, that means the bird have a collision with pipe, then game over
    if (other.tag === 0) {
        cc.log("game over");
        this.mainControl.gameOver();
        this.speed = 0;
    }
    // collider tag is 1, that means the bird cross a pipe, then add score
    else if (other.tag === 1) {
        this.mainControl.gameScore++;
        this.mainControl.labelScore.string = this.mainControl.gameScore.toString();
    }
}

Seventh, back in the Cocos Creator editor, set the labelScore property:

76

Save your progress

Save your changes in the text editor that you are using. Find the Save button in Cocos Creator and click Save once.

Adding background music

Note: It is crucial to understand the importance of Audio. Please make sure to read the Audio documentation

First, create an empty node named AudioSource.

Second, create a TypeScript named AudioSourceControl.ts, and bind the script on the newly created AudioSource node.

82

Third, add the following code to the AudioSourceControl.ts script:

const {ccclass, property} = cc._decorator;

// sound type enum
export enum SoundType {
    E_Sound_Fly = 0,
    E_Sound_Score,
    E_Sound_Die
}

@ccclass
export default class AudioSourceControl extends cc.Component {

    @property({type:cc.AudioClip})
    backgroundMusic: cc.AudioClip = null;

    // sound effect when bird flying
    @property({type:cc.AudioClip})
    flySound: cc.AudioClip = null;

    @property({type:cc.AudioClip})
    scoreSound: cc.AudioClip = null;

    @property({type:cc.AudioClip})
    dieSound: cc.AudioClip = null;

    // LIFE-CYCLE CALLBACKS:

    // onLoad () {}

    start () {
        // play background music
        cc.audioEngine.playMusic(this.backgroundMusic, true);
    }

    playSound (type: SoundType) {
        if (type == SoundType.E_Sound_Fly) {
            cc.audioEngine.playEffect(this.flySound, false);
        }
        else if (type == SoundType.E_Sound_Score) {
            cc.audioEngine.playEffect(this.scoreSound, false);
        }
        else if (type == SoundType.E_Sound_Die) {
            cc.audioEngine.playEffect(this.dieSound, false);
        }
    }

    // update (dt) {}
}

Fourth, return to the Cocos Creator editor, create a new folder named Sound, and import all music sound effects resources into this folder

83
84

Fifth, bind the music sound effects resources to the script:

85

Save your progress

Save your changes in the text editor that you are using. Find the Save button in Cocos Creator and click Save once.

So where are we?

When you run the game you can hear background music!

Adding sound effects

We need to still trigger the corresponding sound effects at the corresponding time points.

First, add the audioSourceControl property in the MainControl.ts script.

86
87

Second, add the following code to the MainControl.ts script:

88

Third, add the following code to the BirdControl.ts script:

89

A few last steps!

Don’t forget to turn off the drawing of the debug information of the collision bodies:

90

Save your progress

Save your changes in the text editor that you are using. Find the Save button in Cocos Creator and click Save once.

Conclusion

WOW! You made it! I hope you enjoyed this tutorial on creating a Flappy Bird clone from scratch with Cocos Creator. Thanks again to game developer HuJun from our Chinese forum for the creation of the tutorial and Jason Slack-Moehrle for the translation of the tutorial.