Phaser 2.0 Tutorial: Flappy Bird (Part 4)

Timers, Group Recycling, and Death

Welcome to the Phaser 2.0 Tutorial series on Flappy Bird. If you havent' yet, you should probably go through the First, Second, and Third Parts of this series.

Today, we're going to talk about timers, generating obstacles and recycling sprites (to cut down on memory usage).

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

Obstacle Generation

The first thing that we will want to do is add a timed loop to generate a new set of obstacles every so often.

Open up play.js and add the following to the bottom of the create() method:

create: function() {  
    /* all previous code here */

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

This will give us a state-level variable named this.pipeGenerator that contains a timer that will call this.generatePipes() every 1.25 seconds.

When you create an event loop, the syntax looks like this:

game.time.events.loop(delay, callback, callbackContext, arguments)  

In our case, the delay uses a predefined enum Phaser.Timer.SECOND that is set to equal 1000 milliseconds. We multiply is by 1.25, because we want a new pipe pair generated every 1.25 seconds.

If we try running the code right now, we'll get a console error when the timer fires for the first time because this.generatePipes() is not defined.

So, let's go ahead and define it:

In the same file, scroll down below your update() method and add the following:

generatePipes: function() {  
    console.log('generating pipes!');
},

Now, when you run your code, every 1.25 seconds, you should get a new console message telling you that your timer ran.

Excellent.

But that doesn't really do anything interesting, does it?

Let's make it do something fun.

The first thing we'll need to do is to tell our loader to use our pipe.png sprite sheet. Open up preload.js, and just beneath the line that adds our bird sprite sheet, add the following:

this.load.spritesheet('pipe', 'assets/pipes.png', 54,320,2);  

With this line, we're telling our loader to load the pipes.png image file as a sprite sheet with 2 frames, each 54 pixels wide by 320 tall.

Creating the Pipe and PipeGroup prefabs

Open up your terminal to your project directory and create two new prefabs: pipe and pipeGroup. If you need a refresher on how to do that, jump back to Part 2: Generating a Prefab.

You should now have pipe.js and pipeGroup.js in your prefabs directory.

First, let's make a customize the prefab to control an individual pipe.

Open up pipe.js, set the sprite's anchor to the center, enable a physics body on it, disable gravity for this sprite's body, and make the body immovable. If you'll recall, we covered this in Part 2 when we were creating our ground tileSprite.

var Pipe = function(game, x, y, frame) {  
  Phaser.Sprite.call(this, game, x, y, 'pipe', frame);
  this.anchor.setTo(0.5, 0.5);
  this.game.physics.arcade.enableBody(this);

  this.body.allowGravity = false;
  this.body.immovable = true;

};

Now, let's create an instantiatible group that contains a pair of pipes: the top pipe, and the bottom pipe.

Open up pipeGroup.js and begin by changing the base class from Phaser.Sprite to Phaser.Group instead:

var Pipe = require('./pipe');

var PipeGroup = function(game, parent) {  
  Phaser.Group.call(this, game, parent);
};

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

module.exports = PipeGroup;  

Now, our PipeGroup prefab class inherits from Phaser.Group which means we have access to group functions. Take notice that the arguments that are passed into this class are very different from those passed into an extended Sprite class.

Also note that we've added a require() statement to import our Pipe prefab class.

Next, let's have our group add something to itself...

Adding the top pipe

var PipeGroup = function(game, parent) {  
    Phaser.Group.call(this, game, parent);
    this.topPipe = new Pipe(this.game, 0, 0, 0);
    this.add(this.topPipe);
};

These two lines first generate a new Pipe that we're referencing in our PipeGroup as topPipe using the first frame of the pipe spritesheet, and then we add it to our PipeGroup as a child.

Why all of those zeroes in the new Pipe instantiation?

Take a look at our Pipe prefab constructor:

var Pipe = function(game, x, y, frame) {  
  Phaser.Sprite.call(this, game, x, y, 'pipe', frame);
  ...
};

Remember from an earlier discussion, setting the location of a group transposes the location of its members. So, here, the first two 0's are setting the Pipe's xand y values. The last zero is the frame we want to use. Remember I said that our pipe image asset had two frames?

pipes

The first frame (frame[0]) is our top down pipe while the second frame(frame[1]) is our right-side-up pipe.

We now have a single Pipe in our group.

Let's add the second Pipe:

var PipeGroup = function(game, parent) {  
    Phaser.Group.call(this, game, parent);

    this.topPipe = new Pipe(this.game, 0, 0, 0);
    this.add(this.topPipe);

    this.bottomPipe = new Pipe(this.game, 0, 440, 1);
    this.add(this.bottomPipe);
};

Almost a complete copy and paste of the first two lines. The only differences here are the y and frame values passed to the Pipe constructor. The y value here is a magic number. It was calculated via the following method:

y = pipe.height + (bird.height * 5)  

This means that the space between topPipe and bottomPipe should be about 5x the height of bird. This can be tweaked, but we'll get to that later.

We need to add one last thing to our PipeGroup prefab's constructor, and that's a switch that will tell us whether the instantiated pipeGroup has been scored against or not:

var PipeGroup = function(game, parent) {  
    Phaser.Group.call(this, game, parent);

    this.topPipe = new Pipe(this.game, 0, 0, 0);
    this.add(this.topPipe);

    this.bottomPipe = new Pipe(this.game, 0, 440, 1);
    this.add(this.bottomPipe);

    this.hasScored = false;
};

We'll use this later to determine if the bird has passed between the pipes and whether or not to add it to the score.

Adding Pipes to the Game

Back in play.js, we'll need to require() our PipeGroup class.

At the top of the file, just below our require() statements for Bird and Ground add a require statement for PipeGroup

var PipeGroup = require('../prefabs/pipeGroup');  

Now scroll down and let's make our generatePipes() method actually do something.

generatePipes: function() {  
    var pipeY = this.game.rnd.integerInRange(-100, 100);
    var pipeGroup = new PipeGroup(this.game);
    pipeGroup.x = this.game.width;
    pipeGroup.y = pipeY;
},

The first line is something we've not seen before, but it's a very good thing to know. Phaser comes with it's very own versatile random number generator. In this case, we're going to generate a random y position for our pipeGroup so that the pipes aren't always in the same place.
The integerInRange() syntax looks like the following:

this.game.rnd.integerInRange(min, max);  

In our case, (-100, 100) are two more magic numbers. They represent the range that I've found works best for the y positions of PipeGroups. Feel free to play around with this as much as you'd like.

We then generate a new PipeGroup, set it's x position to the width of the game, and the y position to the randomly generated number.

Let's check our work:

pipe-group-2

Well, that's close to what we want, but something is -very- wrong.

You know what it is, don't you? It's the fact that our pipes aren't moving. They are all stacking up on the right side of the screen.

Let's fix that in out PipeGroup prefab.

At the bottom of the constructor, underneath our hasScored switch, we need to make all of the children in the group set their velocities in the x direction. We could call each child manually:

this.topPipe.body.velocity.x = -200;  
this.bottomPipe.body.velocity.x = -200;  

OR We could use the built in Phaser.Group functions to make our life easier.

var PipeGroup = function(game, parent) {

  Phaser.Group.call(this, game, parent);

  this.topPipe = new Pipe(this.game, 0, 0, 0);
  this.bottomPipe = new Pipe(this.game, 0, 440, 1);
  this.add(this.topPipe);
  this.add(this.bottomPipe);
  this.hasScored = false;

  this.setAll('body.velocity.x', -200);
};

This line calls a special method on Phaser.Group instances that sets the given property to the given value for -all- members of a group. It even does deep property binding. We're setting the x property of the velocity object on each child's physics body to a value of -200. Again, -200 is a magic number that matches with the autoScroll from our Ground object that we created in Part 2.

Let's refresh and see what we've got.
pipe-group-3

You'll notice that we aren't colliding with anything yet. But that's ok, because we have to talk about something else first:

Recycling

When you instantiate a new sprite, the browser loads the image into a canvas context, creates the sprite's methods, display, and physics bodies (if appropriate). When you generate a lot of sprites this way, it can become taxing on the system's memory and rendering capabilities.

A Small Experiment

I'm not going to dictate the code I wrote to do this, but let's take a look at what happens when you generate a lot of sprites, first without recycling, and then later with recycling.

I wrote a small demonstration that generates a new bird sprite every 1/100th of a second, applies physics bodies, and drops it from a constant y position and an iterative x position. I've attached names to the birds so that you can see that they are indeed unique or recycled. I also added a display to show you how many times the bird generator has been called and total number of birds generated.

Initial:
bird-generator-1

About 1200 generator calls
bird-generator-2

About 2000 generator calls
bird-generator-3

At this point, I took a snapshot of my memory heap, and it had escalated to about 22 megs of memory used. Notice that there are as many birds created as there are calls to the generator. That means that at 2000 generator calls, the canvas now has 2000 bird images loaded. You can see clearly how badly performance suffers.

Now, let's see what it looks like with recycling.

Initial:
bird-generator-4

About 2000 generator calls:
bird-generator-5

About 5000 generator calls:
bird-generator-6

Notice this time that there are between 52 and 53 total birds generated. The memory usage for this is sitting at about 10 megs. You can clearly see that recycling is absolutely necessary for just about every game type that procedurally generates sprites.

Implementing Recycling

The very first thing we'll need for recycling is a group to put our pipeGroup prefabs in once we create them.

In the create() method of play.js, let's add a pipes group just below the instantiation of this.bird and just above where we create a Ground object

create: function() {  
    ...
    // create and add a new Bird object
    this.bird = new Bird(this.game, 100, this.game.height/2);
    this.game.add.existing(this.bird);

    // create and add a group to hold our pipeGroup prefabs
    this.pipes = this.game.add.group();

    // create and add a new Ground object
    this.ground = new Ground(this.game, 0, 400, 335, 112);
    this.game.add.existing(this.ground);
    ...
},

Now that we have a group that we can recycle from, let's add the actual recycling logic.

Let's take a quick look at our code in generatePipes():

generatePipes: function() {  
    var pipeY = this.game.rnd.integerInRange(-100, 100);
    var pipeGroup = new PipeGroup(this.game);
    pipeGroup.x = this.game.width;
    pipeGroup.y = pipeY;
}

We're going to slightly modify our generator code to make recycling work.

generatePipes: function() {  
    var pipeY = this.game.rnd.integerInRange(-100, 100);
    var pipeGroup = this.pipes.getFirstExists(false);
    if(!pipeGroup) {
        pipeGroup = new PipeGroup(this.game, this.pipes);  
    }
    pipeGroup.reset(this.game.width + pipeGroup.width/2, pipeY);
}

The first line hasn't changed, but the second line is really really important. It attempts to get the first element from a group that has it's exists property set to false.

What does exists do?

The exists property on game objects tells Phaser whether or not it should run that object's update method during the game's global update() method call. It's automatically set to true when the game object is instantiated. If the game object is a sprite, it is automatically set to false when the sprite's kill() method is called. It's also a handy way to find out if an object has been killed or some how "decommissioned".

Basically, the second line tells our pipes group to begin iterating through its children and returns the first one that doesn't exist in the game world.

However, me must do another check, and that's what the blocked if statement does. If the pipes group doesn't have any non-existant children, we have to create a new PipeGroup.

Check out this line:

pipeGroup = new PipeGroup(this.game, this.pipes);  

This is slightly different than before. Because PipeGroup inherits from Phaser.Group, we can pass in a second parameter that tells Phaser to automatically add the created object to a group. In this case, we want to automatically add the newly created PipeGroup directly to our pipes group, this.pipes.

Finally, let's take a look at the last line we added:

However, we're going to have to make a few more adjustments to our PipeGroup prefab class.

pipeGroup.reset(this.game.width + pipeGroup.width/2, pipeY);  

This calls the reset method on the created or recycled pipeGroup object with a new x and y position. Our x position here will be the far right edge of the screen, and our y position will be the random position we acquired at the beginning of our generator code.

The All-Important Reset

All game objects that inherit from Phaser.Sprite already have a reset() method. If you were to call the reset method on our bird, the syntax might look something like this:

this.bird.reset(200, 100);  

The arguments for a reset call are, respectively, the x and y coordinates of where they sprite should be reset to. But the call does a lot more than that.

reset(x,y)

This places the Sprite at the given x/y world coordinates and then sets alive, exists, visible and renderable all to true. Also resets the outOfBounds state and health values. If the Sprite has a physics body that too is reset. From the Phaser.Sprite documentation:

Side Note:

The way we are going about this isn't the simplest demonstration of recycling. However, when you're building a real game, you're going to find yourself needing to understand these core concepts so that you can bend gameObjects to your will.

The Unbearable Lightness of Existing: An Experiment

Take a look at this image again:
bird-generator-4

You might be wondering how the recycling works with a group that contains only a basic sprite and how we knew when to "kill" a bird so that it was ready to be used again.

Let me show you the constructor for the Bird sprite prefab I used for that:

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.name = 'bird';

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

  this.checkWorldBounds = true;
  this.outOfBoundsKill = true;
};

The two lines that should look different to you are the last two:

  this.checkWorldBounds = true;
  this.outOfBoundsKill = true;

The first line tells the sprite to check, on every frame, whether or not any part of the sprite is inside of the world bounds.

The second line tells the sprite to automatically call its kill() method when it goes outside of the world bounds.

To tell it like a story:

Ok, it's a new update cycle. The position for bird is { x: -32, y: 100 } and its width is 32 pixels. That means that bird is entirely out of bounds, so I'm going to call bird.kill() which will set the alive, exists and visible properties of bird to false.

The generator code for the bird looks like this:

generateBird: function() {  
    var bird = this.birdGroup.getFirstExists(false);
    if(!bird) {
        bird = new Bird(this.game, x, y);
        this.birdGroup.add(bird);
    }
    bird.reset(x, y);
}

Note: The reset call is not necessary when you've generated a new Bird object. I do it because it seems neater to me than to have an extra else in there.

Recycling Groups

Unfortunately, Phaser.Group doesn't have a built in reset, and for good reason. There's no way for Phaser to intuit exactly what we want done by resetting a group. As such, we need to implement one ourselves.

Our reset is going to follow these steps:
1. Reset the topPipe object to (0,0) (relative to the group)
2. Reset the bottomPipe object to (0, 440) (also relative to the gorup)
3. Set the group's x and y coordinates from the passed in values (relative to the world)
4. Set the x velocity of all the group's children to -200
5. Clear the group's hasScored switch to false
6. Set the group's exists property to true.

Jump over to pipeGroup.js and add the following reset() method:

PipeGroup.prototype.reset = function(x, y) {

  // Step 1    
  this.topPipe.reset(0,0); 

  // Step 2
  this.bottomPipe.reset(0,440); // Step 2

  // Step 3
  this.x = x; 
  this.y = y;

  // Step 4
  this.setAll('body.velocity.x', -200);

  // Step 5
  this.hasScored = false;

  // Step 6
  this.exists = true;
};

In Steps 1 & 2, you'll see that we're using the reset() method of our topSprite and bottomSprite to reposition them to the group's relative origin.

We have one last thing to add to our PipeGroup to get recycling working correctly.

Implementing checkWorldBounds for a group of sprites

Remember the checkWorldBounds and outOfBoundsKill properties on the Bird prefab we talked about in the experiment above? We need to implement something like that for our PipeGroup group so that we know when to set our PipeGroup instance's exists property to false.

Let's build a simple checkWorldBounds function in pipeGroup.js:

PipeGroup.prototype.checkWorldBounds = function() {  
  if(!this.topPipe.inWorld) {
    this.exists = false;
  }
};

What we're doing here is checking that the inWorld property of our topPipe sprite is false. inWorld is a property that Phaser sets on all sprites and it gets updated every frame. We could check both topPipe && bottomPipe, but we know they are both at the same x location on the screen, and that they are both traveling at the same speed. Because of that, there's no reason for the extra check.

Now, in the update() method of PipeGroup, let's tell Phaser to run our custom checkWorldBounds() method on every update:

PipeGroup.prototype.update = function() {  
  this.checkWorldBounds(); 
};

You might be wondering why we're not checking whether or not the current instance of PipeGroup exists or not in the checkWorldBounds() method. Remember that if an object's exists property is set to false, its update() method doesn't run.

Checking Our Work

At this point, we're should now be either generating or recycling a new PipeGroup every time our generator runs. We should be adding it to the screen, and seeing pipes come flying towards the player as soon as they are reset.

Let's take a look:
flappy-bird-part-4

Notice that it doesn't look too different than where we were before we started implementing recycling.

Death

Now that we have our pipes recycling and our bird flapping, there's one last thing to do for this lesson.

Making our bird die.

In the original game, there were two ways to die:
1. Hitting the ground
2. Hitting a pipe

We've already got our ground colliding with our bird, but now, let's make it call a method when it happens.

In the update() method of our Play state, modify the current line of code so that it looks like the following:

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

A full call to the collide() method looks like this:

game.physics.arcade.collide(gameObject1, gameObject2, collisionCallback, processCallback, context);  

All of those items you should be familiar with, except the processCallback argument. We're not going to be using a process callback here, but if you needed to do special checks between the two colliding bodies to determine if you really want the objects to collide, you'd do it in a process callback and return true for collision or false for no collision.

Now, let's make our bird collide with the pipes.

With a group of sprites, you'd merely do the following:

game.physics.arcade.collide(sprite, group, callback, null, this);  

Phaser's collision detection will automatically try to collide a sprite with all of the sprites in a group.

However, due to the fact we've got a group of groups, we have to help Phaser a long.

Add the following below the ground collision detection we added just a moment ago:

    // 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);

Here, we're iterating through each PipeGroup that exists in our pipes group and telling Phaser to collide our bird with each one. We're going to set the callback to the same one that we did for the ground.

Let's now create our deathHandler() method...

What should we do on death?

There's a lot we could do on death, but for right now, we're just going to jump to our GameOver state.

Create the deathHandler method in our Play state thusly:

deathHandler: function() {  
    this.game.state.start('gameover');
  },

However, there's one other thing we need to do. When we leave a game state, Phaser calls the game state's shutdown() method. In this method, it's a good idea to destroy memory intensive gameObjects as well as clear any input methods.

Let's create the shutdown() method at the bottom of our Play state:

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

The first line will remove the spacebar from being linked to flapKey. With out this line, when you returned to the Play state, the spacebar would still be linked to a previous implementation, and wouldn't work.

The next two lines completely destroy and remove our bird and pipes object from memory and the canvas. Even more helpful is the fact that calling a group's destroy() method will also destroy all of its children.

The Wrap Up

Alright. We should now, by the end of this lesson, have a semi-recognizable version of flappy bird up and running. Let's take a quick look:
flappy-bird-part-4-2

(Hint: I changed the game.state.start() call in preload.js to load 'menu' instead of 'play' for the recording)

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


Source Code:

You can view the source for the files we modified in this lesson here: Phaser 2.0 Tutorial: Flappy Bird (Part 4) gist

Next Time:

In Part 5, we'll take a look at scoring, HUD Management, Sound, Particles and Game Over.

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