Phaser 2.0 Tutorial: Flappy Bird (Part 5)

Scoring, Sound, Particles, and Persistant Storage

In the previous parts entries in this series ( [Part 1] [Part 2] [Part 3] [Part 4] ), we've been working on a full blown Flappy Bird clone called Flappy Bird Reborn. Today, we're going to implement the final touches of the game using sound, particle emitters, and persistant storage.

Here's what our game currently looks like:

flappy-bird-part-4-2

And this is where we're going: Fully Playable Demo

Want More?

My new video tutorial series HTML5 Mobile Game Development with Phaser over at ZenvaAcademy has just gone live.

While reading a blog might be great, watching someone actually create a game in front of you is a lot more engaging and you really get a feel for what goes into making a simple game.

Read the announcement post

Get new assets

I've added a bunch of assets to the assets zip archive. If you've been following this tutorial series since the beginning, you'll want to download and extract them to your assets folder. Some of them will be new, some of them will over-write previous assets. Nothing that we've used thus far will change. If you downloaded the assets on or after 4.14.2014, you'll be fine and can skip this step.

Creating the Game Instructions

We need to inform our players as to how play the game. We're going to use a simple group to show and hide the instructions, as well as cause our bird to flap without falling or rotating while the game has yet to "start".

The end result will look like this:
instructions-1

First, we need to load the assets

Open up preload.js and add the following two lines to the bottom of the preload() method:

this.load.image('instructions', 'assets/instructions.png');  
    this.load.image('getReady', 'assets/get-ready.png');

Now we need to create a group to hold our assets in, so, jump over to play.js and add the following to the bottom of the create() method:

create: function() {  
    ...
    this.instructionGroup = this.game.add.group();
    this.instructionGroup.add(this.game.add.sprite(this.game.width/2, 100,'getReady'));
    this.instructionGroup.add(this.game.add.sprite(this.game.width/2, 325,'instructions'));
    this.instructionGroup.setAll('anchor.x', 0.5);
    this.instructionGroup.setAll('anchor.y', 0.5);
}

We're using a simple Phaser.Group and adding our two sprites ('getReady' and 'instructions') to it. Then we're using the setAll() method of a group to set the anchor position of each element to (0.5, 0.5).

If you check your work, you should see something like this now:
instructions-2

We need to tell our bird not to start falling until the player has interacted with the game. We can do this by having our bird's physics body not be effected by gravity until we say so.

In bird.js, in the create() method, we need to set this.body.allowGravity to be false:

var Bird = function(game, x, y, frame) {  
  Phaser.Sprite.call(this, game, x, y, 'bird', frame);
  this.anchor.setTo(0.5, 0.5);
  this.animations.add('flap');
  this.animations.play('flap', 12, true); 

  // enable physics on the bird
  // and disable gravity on the bird
  // until the game is started
  this.game.physics.arcade.enableBody(this);
  this.body.allowGravity = false;  
};

We talked about this method earlier when creating our Pipe prefabs.

Now, the bird won't fall when the game starts:

instructions-3

But now we have another problem.

In bird.js in the update function we tell our bird to automatically rotate towards the ground on every frame:

Bird.prototype.update = function() {  
  // check to see if our angle is less than 90
  // if it is rotate the bird towards the ground by 2.5 degrees
  if(this.angle < 90) {
    this.angle += 2.5;
  } 
};

We need to make this rotation only happen when the bird is alive. Let's modify the conditional to check for this.alive:

Bird.prototype.update = function() {  
  // check to see if our angle is less than 90
  // if it is rotate the bird towards the ground by 2.5 degrees
  if(this.angle < 90 && this.alive) {
    this.angle += 2.5;
  } 
};

But now, we have to seed our bird with this.alive = false; and switch her over to being alive once the player has interacted with the game.

To do this, modify the create() method in bird.js to set the sprite's alive property to false:

var Bird = function(game, x, y, frame) {  
  Phaser.Sprite.call(this, game, x, y, 'bird', frame);
  this.anchor.setTo(0.5, 0.5);
  this.animations.add('flap');
  this.animations.play('flap', 12, true); 

  this.alive = false;

  // enable physics on the bird
  // and disable gravity on the bird
  // until the game is started
  this.game.physics.arcade.enableBody(this);
  this.body.allowGravity = false;  
};

Now, everything should look like it's supposed to:
instructions-1

However, it doesn't behave correctly.

There's two big issues here:
1. The pipes automatically begin generating.
2. The instructions don't disappear

We need to wait to begin generating pipes until the player interacts, and the moment the player does interact, we need remove the instructions.

To do this, we're going to need to intercept the first time a user clicks/taps/presses the spacebar, remove the instruction group, start our pipe generator, and set our bird's alive property to true.

The first thing we're going to do, is add a call back for the first time the player interacts. In the create() method of our PlayState, scroll down to where we created our interactions previously:

create: function() {  
    ...
  // add keyboard controls
      this.flapKey = this.game.input.keyboard.addKey(Phaser.Keyboard.SPACEBAR);
      this.flapKey.onDown.add(this.bird.flap, this.bird);

      // add mouse/touch controls
      this.game.input.onDown.add(this.bird.flap, this.bird);


      // keep the spacebar from propogating up to the browser
      this.game.input.keyboard.addKeyCapture([Phaser.Keyboard.SPACEBAR]);
      ...
}

Let's modify the code to look like the following:

create: function() {  
    // add keyboard controls
    this.flapKey = this.game.input.keyboard.addKey(Phaser.Keyboard.SPACEBAR);
    this.flapKey.onDown.addOnce(this.startGame, this);
    this.flapKey.onDown.add(this.bird.flap, this.bird);


    // add mouse/touch controls
    this.game.input.onDown.addOnce(this.startGame, this);
    this.game.input.onDown.add(this.bird.flap, this.bird);


    // keep the spacebar from propogating up to the browser     
    this.game.input.keyboard.addKeyCapture([Phaser.Keyboard.SPACEBAR]);
    ...
},

The two lines that are important here are the two that add an addOnce() method call to our inputs' onDown properties. We're adding a callback to the first time they interact with either one of those inputs. The callback will be the this.startGame() method that we have yet to create.

We also need to remove our pipeGenerator timer from the create() method, as we don't want to start generating pipes until we've started the game.

Scroll down in play.js to just below the shutdown() method and create the startGame() method:

startGame: function() {  
        this.bird.body.allowGravity = true;
        this.bird.alive = true;

        // add a timer
        this.pipeGenerator = this.game.time.events.loop(Phaser.Timer.SECOND * 1.25, this.generatePipes, this);
        this.pipeGenerator.timer.start();

        this.instructionGroup.destroy();
  },

With this method, when the user interacts with the game the first time, we're enabling gravity on our bird's physics body and setting it's alive property to true. We also moved the creation of the pipeGenerator from the create() method to the startGame() method.
Now, the moment the game is interacted with (whether via keypress, mouse click, or touch), the bird will immediately begin reacting to gravity, flap once, and the pipe generator will begin.

Scoring

What's a game without a reward system? In our case, we'll implement the simplest reward system available: A Score.

To implement scoring, we're going to need to do the following:
1. Determine when the player has scored
2. When the player scores, increase the score counter.
3. Display the score

In our PlayState add the following to the bottom of the create() method:

create: function() {  
    ...

    this.score = 0;

    ...
},

This is our score keeper and we'll be increasing this when the player has scored.

But when -does- the player score?

In the classic Flappy Bird game, the player scores when she has passed at least 50% of the way through a pair of pipes. As such, we'll need to mimic that behavior.

In play.js, in the update() method, we've currently got the following code:

update: function() {  
    // enable collisions between the bird and the ground
    this.game.physics.arcade.collide(this.bird, this.ground, this.deathHandler, null, this);

  // enable collisions between the bird and each group in the pipes group
  this.pipes.forEach(function(pipeGroup) {
      this.game.physics.arcade.collide(this.bird, pipeGroup, this.deathHandler, null, this);
  }, this);
},

We're already iterating through the PipeGroups that are stored in this.pipes, so, let's add another call inside of the loop:

update: function() {  
    // enable collisions between the bird and the ground
    this.game.physics.arcade.collide(this.bird, this.ground, this.deathHandler, null, this);

   // enable collisions between the bird and each group in the pipes group
   this.pipes.forEach(function(pipeGroup) {
       this.checkScore(pipeGroup);
       this.game.physics.arcade.collide(this.bird, pipeGroup, this.deathHandler, null, this);
   }, this);

We've add a call to this.checkScore() passing an argument that is a reference to a single PipeGroup. Since we're already iterating over all of the PipeGroups in this.pipes, we don't have to do anything other than create the method that contains the logic that checks to see whether the player has scored.

Scroll down below the startGame() method, and let's add the checkScore() method:

checkScore: function(pipeGroup) {  
    if(pipeGroup.exists && !pipeGroup.hasScored && pipeGroup.topPipe.world.x <= this.bird.world.x) {
        pipeGroup.hasScored = true;
        this.score++;
        this.scoreText.setText(this.score.toString());
    }
  },

The conditional here is a bit confusing, so let's take a look at each of the parts individually:

if(pipeGroup.exists)  

Here, we're simply checking to make sure that the PipeGroup that was passed in has it's exists property set to true. Remember, we set up our PipeGroup prefab to automatically set the exists property to false if it left the bounds of the world, and our iterator in PlayState's update() method is spinning through -all- of the children it contains. So, this first check is just to make sure that the passed in PipeGroup is one that we should even be concerned with.

The second part of the conditional:

if(!pipeGroup.hasScored)  

This check is to make sure that we haven't already scored against this PipeGroup. Remember, we added a hasScored property to our PipeGroup prefab exactly for this reason.

And finally:

if(pipeGroup.topPipe.world.x <= this.bird.world.x)  

This check is to see if our bird's position is greater than or equal to the world position of the top Pipe prefab in the passed in `PipeGroup'.

What is world position?

The world position is a game object's position relative to the entire game world, and not to its parent's or local position. In this case, the local position of topPipe's x property will be in the negative three hundred or four hundred range because it's moving left from it's parent's origin. The world x position of the topPipe however, should be about 100.

Remember that since we've set the anchor of both our bird and our pipeGroups to be (0.5, 0.5), the x position will always be the horizontal center of those objects.

So, to read out the conditional:

If we should be concerned with pipeGroup, we haven't scored against pipeGroup, and the x world position of our bird is greater than or equal to the x world position of the topPipe game object contained in the passed in PipeGroup, then set the hasScored property of the PipeGroup to true (which will keep us from scoring against it on the next update() call), increase our score, and call the setText method of the scoreText object to the string representation of our numerical score property.

But wait, we didn't create a scoreText object:

I know. That's because we're about to introduce a new concept...

Creating BitmapText Objects

Instead of using an old and busted system font that everyone has on their machine (Arial, Times, Verdana, Georgia), we can supply Phaser with a png and an XML map that correspondes to characters inside of the image. There's no need to build this by hand. I like the absolutely fantasatic littera bitmap font generator to translate any font file I find into a Phaser useable Bitmap Font. I've already created the bitmap font files for the typeface that we're going to use, you just have to load them.

In the Preload state, add the following lines to the preload() method:

Pipe We've now loaded our bitmap font so that we can use it elsewhere.

Jump over to the PlayState code, and add the following lines to the bottom of the create() method:

bird.js

Even though this is a new piece of code, it shouldn't look too foreign:

Bird.prototype.update = function() {  
  // check to see if our angle is less than 90
  // if it is rotate the bird towards the ground by 2.5 degrees
  if(this.angle < 90) {
    this.angle += 2.5;
  } 
};
The only thing different between this and previously created objects is the 'text' and fontSize arguments.

In our implementation, we're telling Phaser to add a new BitmapText object at half-the width of the screen, 10 pixels down in the y direction, using the flappyfont asset key, with a size of 24 and with text that is the string representation of our score property.

Simple.

This, in conjunction with the 'checkScore()` method, will display the current score and update whenever we've scored.

Let's check our work:
score-1

Fantastic.

Now, let's add

Sound

In games, sound shouldn't be an after thought (though I've kind of made it one here). Sounds help players get cues as to when something has happened without having to scan the screen for it. In this case, the most important sound is the one that plays when you've scored.

Let's go ahead and load the sounds we'll need for the rest of the game, see how to play a sound, and then add in other sounds as we continue:

In preload.js, at the bottom of the preload() method, add the following line:

preload: function() {  
    ...
    this.load.audio('score', 'assets/score.wav');
    this.load.audio('flap', 'assets/flap.wav');
    this.load.audio('pipeHit', 'assets/pipe-hit.wav');
    this.load.audio('groundHit', 'assets/ground-hit.wav');
    ...
},

We've now loaded the audio file to our game's cache. You'll notice we used the load.audio() method. This tells Phaser to not only load the sound from the url, but to decode it, if it's encoded. In our case, .wav files are treated as raw audio and don't need to be decoded.

Tab back over to play.js and add the following at the bottom of the create() method:

create: function() {  
    ...
    this.scoreSound = this.game.add.audio('score');
    ...
},

Again, this style of syntax should be almost second nature to you now.

Finally, scroll back down to checkScore(), and modify the code to look like the following:

checkScore: function(pipeGroup) {  
  if(pipeGroup.exists && !pipeGroup.hasScored && pipeGroup.topPipe.world.x <= this.bird.world.x) {
    pipeGroup.hasScored = true;
    this.score++;
    this.scoreText.setText(this.score.toString());
    this.scoreSound.play();
  }
},

All we've done is added the line that tells Phaser to call the play() method on our scoreSound audio object. Now, each time your player scores, there will be a small "coin" sound.

Let's add the flap sound to our Bird prefab and have it play when the bird flaps:

var Bird = function(game, x, y, frame) {  
    ...

    this.flapSound = this.game.add.audio('flap');

    ...
};

...

Bird.prototype.flap = function() {  
    this.flapSound.play();
    //cause our bird to "jump" upward
    this.body.velocity.y = -400;
    // rotate the bird to -40 degrees
    this.game.add.tween(this).to({angle: -40}, 100).start();
};

Now, everytime the bird's flap() method is called, the flap sound will play as well.

Groovy.

Game Over

In most games, the PlayState and GameOver state would be two completely different screens. You would transition to the GameOver state when a preset was met (in this case, when the bird dies). However, in Flappy Bird, the PlayState merely pops up a scoreboard that displays the score of the game you just finished, your best score, and any medals you might have earned. Because of this, we're not going to have a traditional GameOver state. You can go ahead and delete the gameover.js file that exists in your states/ folder, as we won't be using it.

This means that we're going to have to work on...

Creating the Scoreboard

Open up your terminal and create a new prefab called 'Scoreboard', and open up your editor to scoreboard.js.

Scoreboard is going to inherit from Phaser.Group instead of Phaser.Sprite (much like PipeGroup did) so make the following changes to the code:

var Scoreboard = function(game) {

  Phaser.Group.call(this, game)

};

Scoreboard.prototype = Object.create(Phaser.Group.prototype);  
Scoreboard.prototype.constructor = Scoreboard;  

Now that our Scoreboard is a sub-class of Phaser.Group, we need to add our display sprites.

Go ahead and add the scoreboard.png, gameover.png, medals.png, and particle.png assets to the loader in preload.js:

preload: function() {  
    ...
    this.load.image('scoreboard', 'assets/scoreboard.png');
    this.load.image('gameover', 'assets/gameover.png');
    this.load.spritesheet('medals', 'assets/medals.png', 44, 46, 2);
    this.load.image('particle', 'assets/particle.png');
    ...
},

Now, add the following code to scoreboard.js:

var Scoreboard = function(game) {

  var gameover;

  Phaser.Group.call(this, game);
  gameover = this.create(this.game.width / 2, 100, 'gameover');
  gameover.anchor.setTo(0.5, 0.5);

  this.scoreboard = this.create(this.game.width / 2, 200, 'scoreboard');
  this.scoreboard.anchor.setTo(0.5, 0.5);

  this.scoreText = this.game.add.bitmapText(this.scoreboard.width, 180, 'flappyfont', '', 18);
  this.add(this.scoreText);

  this.bestScoreText = this.game.add.bitmapText(this.scoreboard.width, 230, 'flappyfont', '', 18);
  this.add(this.bestScoreText);

  // add our start button with a callback
  this.startButton = this.game.add.button(this.game.width/2, 300, 'startButton', this.startClick, this);
  this.startButton.anchor.setTo(0.5,0.5);

  this.add(this.startButton);

  this.y = this.game.height;
  this.x = 0;

};

There's nothing here that you haven't seen before. The biggest thing of note is the fact that we are adding -all- of the sprites and text objects to the group by calling this.add(gameObject); for each created gameobject. Another interesting thing is we are setting the entire group's initial y position to be this.game.height. This means that every gameObject added to our Scoreboard will be blow the visible boundary of the game. This is so that we can tween our entire Scoreboard upwards as we see fit.

Does creation order matter?

Absolutely. In a Group, State, Sprite, or any game object that can have children, the order that objects are created in determines which display "layer" they are on. For instance, in the above code, we created the scoreboard object before the scoreText and bestScoreText objects. We set the text object's position to values that allow the text to appear "on top" of the scoreboard object. If we had created the scoreboard object after the text objects, the scoreboard sprite's layer would be ontop of the text objects layers, thus preventing us from seeing them. This is also why we created the background object first in our PlayState. If we had created it after we created the bird sprite or the pipes group, the background sprite's layer would be on top of the others, and we'd not be able to see the rest of the game.

Displaying the Scoreboard

We also need to create a show() method in our Scoreboard prefab that does the following:
1. Updates scoreText to display the passed in score
2. Checks local storage for a bestScore value
3. Logic to determine if bestScore is higher than score and write back to localStorage if it is.
4. Updates 'bestScoreText' to display the bestScore value
5. Determines whether or not to show a medal
6. Position the medal
7. If a medal should be displayed, create and start a particle emitter to display "shinies"
8. Tween the entire group into a visible position.

Scoreboard.prototype.show = function(score) {  
  var medal, bestScore;

  // Step 1
  this.scoreText.setText(score.toString());

  if(!!localStorage) {
    // Step 2
    bestScore = localStorage.getItem('bestScore');

    // Step 3
    if(!bestScore || bestScore < score) {
      bestScore = score;
      localStorage.setItem('bestScore', bestScore);
    }
  } else {
    // Fallback. LocalStorage isn't available
    bestScore = 'N/A';
  }

  // Step 4
  this.bestText.setText(bestScore.toString());

  // Step 5 & 6
  if(score >= 10 && score < 20)
  {
    medal = this.game.add.sprite(-65 , 7, 'medals', 1);
    medal.anchor.setTo(0.5, 0.5);
    this.scoreboard.addChild(medal);
  } else if(score >= 20) {
    medal = this.game.add.sprite(-65 , 7, 'medals', 0);
    medal.anchor.setTo(0.5, 0.5);
    this.scoreboard.addChild(medal);
  }

  // Step 7
  if (medal) {    

    var emitter = this.game.add.emitter(medal.x, medal.y, 400);
    this.scoreboard.addChild(emitter);
    emitter.width = medal.width;
    emitter.height = medal.height;

    emitter.makeParticles('particle');

    emitter.setRotation(-100, 100);
    emitter.setXSpeed(0,0);
    emitter.setYSpeed(0,0);
    emitter.minParticleScale = 0.25;
    emitter.maxParticleScale = 0.5;
    emitter.setAll('body.allowGravity', false);

    emitter.start(false, 1000, 1000);

  }
  this.game.add.tween(this).to({y: 0}, 1000, Phaser.Easing.Bounce.Out, true);
};

Step-by-Step

1: Updates scoreText to display the passed in score

this.scoreText.setText(score.toString());  

A call to a text object's setText() method, will update the text displayed by that object.

2: Checks local storage for a bestScore value

 bestScore = localStorage.getItem('bestScore');

If you aren't familiar with the localStorage capabilities of HTML5, might I suggest you quickly read this: Local Storage - Dive Into HTML5

3: Logic to determine if bestScore is higher than score and write back to localStorage if it is.

 if(!bestScore || bestScore < score) {
      bestScore = score;
      localStorage.setItem('bestScore', bestScore);
    }

4: Updates 'bestScoreText' to display the bestScore value

  this.bestText.setText(bestScore.toString());

This is exactly like Step 1 above.

5 & 6: Determines whether or not to show a medal, and postion them correctly

if(score >= 10 && score < 20)  
  {
    medal = this.game.add.sprite(-65 , 7, 'medals', 1);
    medal.anchor.setTo(0.5, 0.5);
    this.scoreboard.addChild(medal);
  } else if(score >= 20) {
    medal = this.game.add.sprite(-65 , 7, 'medals', 0);
    medal.anchor.setTo(0.5, 0.5);
    this.scoreboard.addChild(medal);
  }

In the original Flappy Bird game, if you hit a score of 10, you received a copper medal. A score of 25 or more earned you a silver medal, and a score of 50 or more earned you a gold medal. (I believe this is correct... I never scored high enough to find out!) For simplicity's sake, I decided that only two medals were necessary, a copper medal and a platinum medal. The player will receive a copper medal at 10+ points, and a platinum medal at 25+ points.

We position the medals relative to the scoreboard sprite's origin and set the anchor on the medal to (0.5, 0.5).

We finally add the medal as a child of the scoreboard sprite.

Step 7: If a medal should be displayed, create and start a particle emitter to display "shinies"

if (medal) {  
    var emitter = this.game.add.emitter(medal.x, medal.y, 400);
    this.scoreboard.addChild(emitter);
    emitter.width = medal.width;
    emitter.height = medal.height;

    emitter.makeParticles('particle');

    emitter.setRotation(-100, 100);

    emitter.minParticleScale = 0.25;
    emitter.maxParticleScale = 0.5;

    emitter.setXSpeed(0,0);
    emitter.setYSpeed(0,0);

    emitter.setAll('body.allowGravity', false);

    emitter.start(false, 1000, 1000);

  }

Hooray! New Stuff!

What is a particle emitter?

A Particle Emitter is a special object type that can spawn groups of other objects, called particles. They can be used to create countless special effects, including: rain, snow, blowing leaves, fireflies, fire, smoke, ground fog, waterfalls, fireworks, etc.

In Phaser, the Phaser.Emitter class inherits from Phaser.Group, which means that not only do we have access to all of the normal group methods, but also to special Phaser.Emitter methods that have been created specifically for this purpose.

Let's look at Step 7 line by line:

if (medal) {  

We don't want do any of the following if medal wasn't defined

var emitter = this.game.add.emitter(medal.x, medal.y, 400);  

This syntax should look familiar to you, the arguments passed in are x position, y position, and the maximum number of particles to create, respectfully.

this.scoreboard.addChild(emitter);  

We're adding the emitter as a child of our scoreboard sprite so that we can modify it's position relatively.

  emitter.width = medal.width;
  emitter.height = medal.height;

We're setting the emitter's width and height to that of the medal sprite. With an emitter, the width and height properties dictate an area that particles can be emitted from. That is, if the emitter's width is 30, and height is 10, a particle can be emitted with an x position range of 0 to 30 (relative to the emitter). The same concept applies for the y position (a range of 0 to 10). This allows for a more random spawn location. Without setting this, the emitter will always emit particles from the exact same location.

emitter.makeParticles('particle');  

This one line tells our emitter to create all of the particles using the 'particle' asset key. Earlier, we told the emitter to have a maximum capacity of 400 particles (which is over kill for this application). So, after this line, our emitter object will have 400 children.

emitter.setRotation(-100, 100);  

This line tells our emitter to pick a random rotation between -100 and 100 for each particle it emits.

    emitter.minParticleScale = 0.25;
    emitter.maxParticleScale = 0.5;

This sets a range of values for the particle's scale that the emitter can choose from when emitting a particle. The scale chosen will be applied to both the x and y scales.

emitter.setXSpeed(0,0);  
emitter.setYSpeed(0,0);  

A call to an emitter's setXSpeed() method:

emitter.setXSpeed(min, max);  

This tells our emitter to pick a random speed between the min and the max values provided for each particle. The same goes for the setYSpeed() method.

However, we don't want our particles to move at all, so we set the speed ranges to (0, 0). This means our particles should stay in place but...

emitter.setAll('body.allowGravity', false);  

Due to the fact that the emitter is built on the Arcade physics system, every particle will have game.physics.arcade.enableBody() applied to it. With out our line of code, all of the particles will begin to fall at the set gravity value.

emitter.start(false, 1000, 1000);  

This kicks everything off. The Phaser.Emitter class has a lot of properties and options when calling certain methods, and start() is no exception.

The full call accepts the following arguments:

emitter.start(explode, lifespan, frequency, quantity)  

explode is a boolean. If set to true, all of the emitter's particles will be emitted at once. lifespan is a number. It determines, in milliseconds, how long the particles last before they are kill()-ed frequency is a number. It determines, in milliseconds, how often a particle is emitted and is ignored if explode is true quantity is a number. It determines how many particles to emit.

Our call:

emitter.start(false, 1000, 1000);  

tells the emitter not to emit every particle at once but to emit one particle every second with a lifespan of one second.

Whew.

Ok..

Step 8: Tween the entire group into a visible position.

this.game.add.tween(this).to({y: 0}, 1000, Phaser.Easing.Bounce.Out, true);  

Again, this isn't anything you haven't seen before. We're merely tweening the entire group from it's current y position to a y position of 0 over the course of one second and using the Phaser.Easing.Bounce.Out easing method.

Restarting the Game

We've got to add one more method to our Scoreboard prefab:

Scoreboard.prototype.startClick = function() {  
  this.game.state.start('play');
};

When the start button is clicked, this will restart PlayState and the game will basically start over.

Instantiating our Scoreboard

Now that we've built our Scoreboard prefab, we need to add and display it when appropriate from our PlayState. Switch over to play.js and scroll down to the deathHandler() method and modify it thusly:

deathHandler: function() {  
    this.bird.alive = false;
    this.pipes.callAll('stop');
    this.pipeGenerator.timer.stop();
    this.ground.stopScroll();
    this.scoreboard = new Scoreboard(this.game);
    this.game.add.existing(this.scoreboard);
    this.scoreboard.show(this.score);
},

Now, when we've hit a 'gameover' condition, the scoreboard will slide into place above the game's display.

One last thing

We need to clean out our PlayState before it gets instantiated again. Last time, I showed you how to add a shutdown() method. We need to modify it so that it cleans everything out correctly:

shutdown: function() {  
    this.game.input.keyboard.removeKey(Phaser.Keyboard.SPACEBAR);
    this.bird.destroy();
    this.pipes.destroy();
    this.scoreboard.destroy();
},

Now, each time that the PlayState gets destroyed before a new one is created, our game objects are cleared for garbage collection and we won't be leaking much needed memory.

And... it's a game!

I'm going to stop here. The game you have now and the full demo I showed you at the beginning of this lesson are not exactly the same. I've spent some time tweaking displays and adding sound where it should be. Feel free to do the same, or grab the entire project's source code from github: Flappy Bird Reborn

Want More?

My new video tutorial series HTML5 Mobile Game Development with Phaser over at ZenvaAcademy has just gone live.

While reading a blog might be great, watching someone actually create a game in front of you is a lot more engaging and you really get a feel for what goes into making a simple game.

Read the announcement post

Back Talk

As always, if you've got questions or comments, drop them in the comment section below, hit me up on twitter (@codevinsky), or you can always find me as jdowell in the #phaserio freenode channel