Adobe Creative Cloud

September 21, 2016 /Gaming /HTML5 /

Part 5: Building a HTML5 Flappy Bird Game Clone with Adobe Animate CC

Welcome to part five of this HTML5 Flappy Bird tutorial series. Parts three and four had us implement endless scrolling. Today we’re going to get our little hero up and flapping. Along the way we’ll implement some basic physics in order to perfectly capture that familiar Flappy Bird feeling. We’ll also spend some time adding collision detection.

What you will learn…

  • We’ll continue to explore some of the fundamentals of CreateJS
  • You’ll see how easy it is to simulate very simple game world physics
  • How to handle collision detection within your game

What you will need…

  • A basic understanding of at least one programming language, such as JavaScript or ActionScript

Before we begin, spend a few minutes looking over your existing code from the previous two parts. Familiarize yourself with the Main class, which acts as the game’s main entry point and handles much of the high-level game logic. We’ll be adding more code to it throughout today’s tutorial. We’ll also be making some changes to our Ground and Pipes class. So spend a few moments reminding yourself how those work too.

Finally, it doesn’t hurt to remind yourself what we’re aiming towards. View the final version of the Flappy Bird clone.

Getting Started

You’ll need Adobe Animate CC – download a trial version. You’ll also need the FLA file you were working with in part four.

Now let’s start writing the code to bring our little flappy hero to life!

Looping the Flapping Animation

Before we start writing our Bird class, let’s revisit the actual Bird movie-clip and add a timeline script to ensure its flapping animation loops. At present the animation runs once then stops, but we really want it to run indefinitely.

Within the Library panel, find and double-click on the Bird movie-clip.

You should now see the timeline for the Bird movie-clip, complete with all of its layers. Add a new layer above all the others and name it Script. Click on frame 15 at the end of the layer’s timeline and create a keyframe by selecting Insert | Timeline | Keyframe (F6) from the dropdown menu.

figure-1

Figure 1. Adding JavaScript to the timeline.

Now open the Actions panel by selecting Window | Actions (F9) and write the following line of JavaScript:

this.gotoAndPlay(0);

This will ensure that when the playhead reaches the end of the timeline, it will be sent back to the first frame of the timeline and continue to play. Effectively it creates an endless flapping animation.

Figure 1 above illustrates the steps we’ve just followed.

There are two things to note here. Firstly, you may be tempted to place your script on frame 14 rather than 15. However, this will result in the playhead being immediately moved to the first frame before the final frame of the flapping animation actually gets rendered to the screen.

Secondly, you may be wondering why we passed a value of 0 to the gotoAndPlay() call rather than using a value of 1. For HTML5 projects, Animate CC uses zero-based indexing when referring to frame numbers. This means that the first frame is actually referred to as frame 0, the second frame is frame 1, and so on.

Save your changes and move back to the main stage. Now we’re in a position to start writing our Bird class.

The Bird Class

Create a new timeline layer directly above the Pipes class layer. Name the layer Bird class and click on the first frame of its timeline. Open the Actions panel by selecting Window | Actions (F9) from the dropdown menu.

We’ll start by adding a constructor to the class. Initially it won’t do much other than store a reference to the bird movie-clip instance that sits on the timeline, and also store the bird’s initial y-position:

function Bird()
{
  this.mc = exportRoot.bird;
  this.initY = this.mc.y;
}

We’ll also need two public methods: one that initiates the bird when a new game begins and one that acts as the bird’s update loop. We won’t worry about the exact implementation details for either of these methods just yet. Instead we’ll simply have each write some text to the web browser’s JavaScript console.

Add the following below your constructor:

Bird.prototype.startFlying = function()
{
  console.log("Bird::startFlying()");
}

Bird.prototype.update = function()
{
  console.log("Bird::update()");
}

Now let’s go ahead and hook up our Bird class to the Main class.

Find the Main class within the Action panel’s Script Navigator and select it.

Within its constructor, declare a member variable that holds an instance of your Bird class:

function Main()
{
  this.ground = new Ground();
  this.pipes = new Pipes();
  this.bird = new Bird();
  
  exportRoot.screenFlash.visible = false;
  exportRoot.gameOverPrompt.visible = false;
  exportRoot.getReadyPrompt.visible = false;
  exportRoot.startInstructions.visible = false;
  
  createjs.Ticker.addEventListener("tick", this.update.bind(this));
  
  canvas.onmousedown = this.userPressed.bind(this);
  window.onkeydown = this.userPressed.bind(this);
}

Also ensure that the bird instance gets updated within the Main class’ update() method. Add the following line:

Main.prototype.update = function(evt)
{
  this.bird.update();
  this.ground.update();
  this.pipes.update();
}

Finally, start the bird flying by adding the following line within the startGame() method:

Main.prototype.startGame = function()
{
  this.ground.startScrolling();
  this.pipes.startScrolling();
  this.bird.startFlying();
}

Let’s test that everything is hooked up correctly. Save your changes then publish to the browser by selecting Control | Test Movie | In Browser (Cmd-Enter | Ctrl-Enter) from Animate CC’s dropdown menu. Firstly, you should notice that the bird’s wing flapping animation loops as expected. Secondly, you should see the following being repeatedly written to the browser’s JavaScript console, which verifies that our bird’s update() method is successfully being called:

> Bird::update()

Finally, the bird’s startFlying() method should get called every time the user clicks the screen (or presses a key) to start a new game. To verify this, click on the game’s viewport and check that the following gets written to the JavaScript console:

> Bird::startFlying()

If everything is working as expected we can start to add some implementation detail to our Bird class.

Adding State

Our flappy bird can be in one of three states during the lifetime of the game: alive, dying, and dead.

The bird is ‘alive’ when the player has control of it and is attempting to fly through each of the pipes. When the bird has collided with a pipe and is falling to the ground then it will be in its ‘dying’ state. And finally, when the bird has collided with the ground or the player hasn’t started their very first game, then the bird will be in its ‘dead’ state.

We need to model and track these three states within our Bird class. Begin by adding the following three constants directly below the class’ constructor:

function Bird()
{
  this.mc = exportRoot.bird;
  this.initY = this.mc.y;
}

Bird.ALIVE = 0;
Bird.DYING = 1;
Bird.DEAD  = 2;

Also, add a member variable named state and initially set it to the Bird.DEAD state:

function Bird()
{
  this.mc = exportRoot.bird;
  this.initY = this.mc.y;
  this.state = Bird.DEAD;
}

Bird.ALIVE = 0;
Bird.DYING = 1;
Bird.DEAD  = 2;

We’ll also need a few public methods that will allow other classes to determine whether the bird is alive or dead. Add them directly above the class’ startFlying() method:

Bird.ALIVE = 0;
Bird.DYING = 1;
Bird.DEAD  = 2;

Bird.prototype.isAlive = function()
{
  return (this.state == Bird.ALIVE);
}

Bird.prototype.isDead = function()
{
  return (this.state == Bird.DEAD);
}

Bird.prototype.startFlying = function()
{
  console.log("Bird::startFlying()");
}

Finally, we’ll change the bird’s state to ‘alive’ whenever its startFlying() method gets called. Make the following changes:

Bird.prototype.startFlying = function()
{
  console.log("Bird::startFlying()");
  this.state = Bird.ALIVE;
}

Save your work.

We’re now in a position to actually start added some physics to our game world. This will force our little flappy friend to fall to earth due to gravity and also allow the bird to overcome that gravity by applying a vertical impulse to it every time the player makes the bird flap its wings.

Configuring the Physics

We’ll need to define some constants that will be used to define our physics world. Move to the Main class and add the following three constants:

Main.SCROLL_SPEED = 3.0;
Main.GRAVITY = 0.55;
Main.FLAP_IMPULSE = -8.15;
Main.MAX_VELOCITY = 15;

The first of our constants – GRAVITY – simply holds a decimal value that simulates the force of gravity on our flappy bird. This gravitational force will be applied downwards onto our bird, forcing him to accelerate towards the ground unless an upward force is applied that negates the game world’s gravity.

It’s worth noting that we aren’t aiming for complete realism here. We’re making a simple fun game after all! Therefore, the value of 0.55 that I’ve used is fairly arbitrary and one that I’ve arrived at through trial and error. Once you have things up and running, feel free to tinker with these values.

The second constant that we’ve defined is FLAP_IMPULSE and is used to apply an upward force on our bird whenever the player attempts to flap the bird’s wings. Our game’s gravitational force will be applied on every frame update, whereas the flap impulse is applied on a single frame to provide a massive upwards thrust against gravity. As with the value assigned to the GRAVITY constant, the value I’ve used for FLAP_IMPULSE has been derived through trial and error. A negative value is used for this constant because it will be applied in the opposite direction (upwards) to the game engine’s gravity.

The final constant is simply used to ensure that the bird’s vertical speed when falling never exceeds a certain value. This is required to ensure an element of balance to the gameplay and makes things a little easier for the player.

Save your changes.

Implementing the Physics

Let’s go ahead and implement the game’s physics. Only the bird itself is affected by the game’s physics, so we’ll add the implementation within the Bird class.

Begin by declaring a velocity member variable within the constructor:

function Bird()
{
  this.mc = exportRoot.bird;
  this.initY = this.mc.y;
  this.state = Bird.DEAD;
  this.velocity = 0;
}

Velocity typically describes the speed of something in a given direction. For 2D games this typically results in an object’s horizontal speed and direction being modelled as well as its vertical speed and direction. However, we’re only concerned with the bird’s vertical velocity in our game – its horizontal movement is actually faked by moving each of the game’s pipes towards it. Therefore our velocity member variable is only used to represent the bird’s vertical speed and direction.

As you can see. The bird is given an initial velocity of 0 meaning that it is neither moving upwards or downwards.

We’ll also need to ensure that the bird’s velocity gets reset every time a new game is started. This can be done within the Bird class’ startFlying() method. While we’re at it, we’ll also ensure that the bird gets placed at its initial y-position every time this method gets called. Add the following lines:

Bird.prototype.startFlying = function()
{
  this.state = Bird.ALIVE;
  this.mc.y = this.initY;
  this.velocity = 0;
}

We now need to ensure that the game’s gravitational force is applied to the bird on each frame update. We’ll write an updateVelocity() method to do that. Add the following at the end of your Bird class:

Bird.prototype.updateVelocity = function()
{
  this.velocity += Main.GRAVITY;

  if (this.velocity > Main.MAX_VELOCITY)
  {
    this.velocity = Main.MAX_VELOCITY;
  }

  this.mc.y += this.velocity;
}

There are a few things going on in this method. Firstly, we add the gravitation force to our bird’s vertical velocity. This constant force on each frame update will effectively force the bird towards the ground, simulating the effects of gravity.

Next, we check to see if the bird’s velocity has exceeded our stipulated MAX_VELOCITY. If it has then we simply limit the bird’s velocity, which will prevent it from picking up any more vertical speed.

Finally, we actually update the y-position of our bird on the screen. This is done by adding the bird’s current vertical velocity to its movie-clip’s y-position.

Believe it or not, but that’s all that’s required to simulate physics within our game! Now all we need to do is call our updateVelocity() method from the class’ update() method. Make the following changes:

Bird.prototype.update = function()
{
  console.log("Bird::update()");
  if (this.state == Bird.ALIVE || this.state == Bird.DYING)
  {
    this.updateVelocity();
  }
}

As you can see from the code above, we only want to apply gravity to our bird when it’s alive or dying. In other words, we don’t want to apply any gravity to it when it’s already lying dead on the ground.

Save your changes then publish and test your latest code within the browser. Verify things are working as expected by clicking anywhere on the game’s viewport. You should see our little flappy bird drop like a stone straight out of the bottom of the screen. Every time you click, he’ll be placed back at his original start position before falling out of the screen again.

Congratulations you have successfully simulated gravity within your game! But what about the bird itself? He can’t fly yet. We’ll let’s go ahead and fix that.

Adding Flapping

Every time the player makes our little hero flap his wings we need to apply an upward force that will push him vertically upwards, defeating the effect of gravity.

We’ll simply set the bird’s velocity to the value set within our Main.FLAP_IMPULSE constant. This will give us the behaviour we’re looking for in our game.

Within the Bird class, add a new method named flap():

Bird.prototype.startFlying = function()
{
  this.state = Bird.ALIVE;
  this.mc.y = this.initY;
  this.velocity = 0;
  this.flap();
}

Bird.prototype.flap = function()
{
  if (this.state == Bird.ALIVE && this.mc.y > 0)
  {
    this.velocity = Main.FLAP_IMPULSE;
  }
}

As you can see from the code above, we only want to apply the flap impulse if the following two conditions are met:

  • The bird is currently alive
  • The bird has not left the top of the screen

The second of the two conditions here is important because it prevents the player from simply flying out of the screen and over the top of each of the pipes.

Also, look closely at the startFlying() method above. We make a call to flap() from here to ensure the bird immediately flaps when each new game is started.

Once a new game has begun, we need to make sure that each successive mouse click (or key press) results in the bird flapping upwards again. Make the following changes to the Main class’ userPressed() method to achieve this:

Main.prototype.userPressed = function(evt)
{
  if (this.bird.isDead())
  {
    this.startGame();
  }
  else
  {
    this.bird.flap();
  }
}

The logic here is straightforward. If the bird is dead then a user press will start a new game. If the bird isn’t dead then each user press will force the bird to flap upwards.

Save your changes and republish. Now when you run the game within the browser you’ll be able to click the mouse (or press any key) to make the bird flap upwards. Give it a try.

There currently isn’t any collision detection. The bird won’t die if it touches any of the pipes, and it simply falls off the bottom of the screen rather than slamming into the ground. Let’s go ahead and address these things.

Detecting Ground Collision

Let’s start by checking to see if the bird has collided with the ground.

If you remember back to the previous parts in the series, we added a number of blue dots around our bird. These dots act as collision points and are used to check if the bird has collided with the ground or any of the pipes. This is done by examining the on-screen position of each dot and checking to see if it’s within the bounding area of each pipe or the ground strips.

Before we can work with these collision points we’ll need to obtain and store references to them. Do that by adding the following method to your Bird class:

Bird.prototype.setupCollisionPoints = function()
{
  this.collisionPoints = [this.mc.hit0, this.mc.hit1, this.mc.hit2,
                          this.mc.hit3, this.mc.hit4, this.mc.hit5,
                          this.mc.hit6, this.mc.hit7, this.mc.hit8];

  for (var i = 0; i < this.collisionPoints.length; i++)
  {
    this.collisionPoints[i].visible = false;
  }
}

Our method stores the nine collision points within a member variable named collisionPoints. Our member variable is an array and our code also loops through that array and hides each of the hit points from view. This is done by setting each collision point movie-clip’s visible property to false. The player will no longer see the collision points surrounding the bird during play.

Although the blue dots are now hidden during game play, we can still reference them and use their on-screen position to perform collision detection. They may now be invisible but they do still surround the bird during play.

Now, call setupCollisionPoints() within the Bird class’ constructor to ensure the method gets run:

function Bird()
{
  this.mc = exportRoot.bird;
  this.initY = this.mc.y;
  this.state = Bird.DEAD;
  this.velocity = 0;
  this.setupCollisionPoints();
}

Now that we have a reference to each of our collision points, we can write a new method that checks to see if any of the points are within a target movie-clip’s bounding region. That target movie-clip could be, for example, a pipe or a ground slice.

Add the following code:

Bird.prototype.hitTest = function(target)
{
  for (var i = 0; i < this.collisionPoints.length; i++)
  {
    var pt = this.collisionPoints[i].localToLocal(0, 0, target);
    var collision = target.hitTest(pt.x, pt.y);
    if (collision == true)
    {
      return true;
    }
  }

  return false;
}

Our method takes a single movie-clip instance as a parameter named target. It then loops through each of our collision points and checks to see if the position of each is within the bounding region of the target movie-clip. If it is, the method returns true to indicate that a collision has been detected. Otherwise false is returned.

To do this we take advantage of the hitTest() method provided by CreateJS’s MovieClip class. It takes two parameters: the x and y positions to check within the target movie-clip’s local coordinate space. Since we need to work within our target movie-clip’s co-ordinate space, we need to convert the position of each of our collision points to the same co-ordinate space before calling hitTest(). We use another CreateJS method named localToLocal() to do this. It takes a movie-clip’s (in this case a collision point) position and converts it to the same co-ordinate space as our target movie-clip.

For the time being, let’s add one more method to our Bird class. This method will get called whenever the bird hits the ground:

Bird.prototype.hitGround = function()
{
  this.state = Bird.DEAD;
  this.mc.stop();
}

This method simply changes the bird’s state to ‘dead’ and also stops its wing flapping animation.

Since we’ve stopped the wings from flapping when the bird hits the ground we’ll need to remember to start them again when a new game starts. Make a small alteration to your startFlying() method to achieve this:

Bird.prototype.startFlying = function()
{
  this.state = Bird.ALIVE;
  this.mc.y = this.initY;
  this.mc.play();
  this.velocity = 0;
  this.flap();
}

Save your changes. Let’s now move onto our Ground class where we’ll write a public method that is used to check if the bird has collided with the ground.

Add the following method at the end of your Ground class:

Ground.prototype.isBirdTouchingGround = function(bird)
{
  for (var i = 0; i < this.slices.length; i++)
  {
    if (bird.hitTest(this.slices[i]))
    {
      return true;
    }
  }

  return false;
}

This method takes an instance of our bird movie-clip as a parameter and returns true if the bird is touching the ground, and false otherwise. It checks by looping through the vertical slices that the ground is made up from and seeing if the bird is touching any of them. To detect for collision, we simply call the hitTest() method that we added to the Bird class.

Now that both our Bird and Ground classes facilitate collision detection, we can write some logic within our Main class to tie everything together and check to see if the bird has collided with the ground.

Move to the Main class. We’ll write a method that gets called on every frame update to actively check if the bird is touching the ground. Add the following:

Main.prototype.checkForBirdCollidingWithGround = function()
{
  if (this.bird.isDead() == false)
  {
    if (this.ground.isBirdTouchingGround(this.bird))
    {
      this.birdHitGround();
    }
  }
}

var main = new Main();

Our checkForBirdCollidingWithGround() method is easy to read. If the bird isn’t dead, then it calls the ground instance’s isBirdTouchingGround() method to see if the bird is touching the ground. If it is touching then another method named birdHitGround() is called. We’ve still to add this method so lets go ahead and do that:

Main.prototype.startGame = function()
{
  this.ground.startScrolling();
  this.pipes.startScrolling();
  this.bird.startFlying();
}

Main.prototype.birdHitGround = function()
{
  this.bird.hitGround();
  this.ground.stopScrolling();
  this.pipes.stopScrolling();
}

Essentially the birdHitGround() method brings the game to a halt. It simply calls the bird’s hitGround() method and also stops the ground and pipes from scrolling. The hitGround() method remember, changes the bird’s state to ‘dead’.

Now all that’s left to do is actually call our checkForBirdCollidingWithGround() method from within the game’s main loop. Add the following line of code:

Main.prototype.update = function(evt)
{
  this.bird.update();
  this.ground.update();
  this.pipes.update();
  
  this.checkForBirdCollidingWithGround();
}

Save your changes. Now republish and test the latest version of the game within the browser.

Now when the bird collides with the ground, the game stops and can be restarted again by clicking anywhere within the viewport or by pressing any key.

As you can no doubt tell, we’ve yet to implement collision detection for the pipes. Let’s move onto that now.

Colliding With Pipes

We’ll begin by adding a new method to the bird that will make it fall from the sky. This method will eventually get called when the bird collides with a pipe. Add the following to the Bird class:

Bird.prototype.fallFromSky = function()
{
  if (this.state == Bird.ALIVE)
  {
    this.state = Bird.DYING;
    this.velocity = 0;
  }
}

Our new method simply sets the bird to its ‘dying’ state and sets its vertical velocity to 0, which will result in it immediately falling towards the ground.

We’ll also need to add a method to the Pipes class that will check to see if the bird has collided with any of the pipes. Add the following:

Pipes.prototype.isBirdTouchingAPipe = function(bird)
{
  for (var i = 0; i < this.pipes.length; i++)
  {
    if (bird.hitTest(this.pipes[i]))
    {
      return true;
    }
  }
  
  return false;
}

Our isBirdTouchingAPipe() method returns true if the bird collides with any of the pipes and false if no collisions are detected. It simply loops through our array of pipes and passes each to the bird’s hitTest() method.

Now we can move to the main application class and hook up the logic to check for collision with the pipes and react to it.

Move to the Main class and add the following method to it:

Main.prototype.checkForBirdCollidingWithGround = function()
{
  if (this.bird.isDead() == false)
  {
    if (this.ground.isBirdTouchingGround(this.bird))
    {
      this.birdHitGround();
    }
  }
}

Main.prototype.checkForBirdCollidingWithPipes = function()
{
  if (this.bird.isAlive())
  {
    if (this.pipes.isBirdTouchingAPipe(this.bird))
    {
      this.birdHitPipe();
    }
  }
}

It’s not that different from our checkForBirdCollidingWithGround() method. If the bird is currently alive then we check to see if it has just collided with a pipe. If a collision is detected then a method named birdHitPipe() gets called. We’ve yet to write this method but first add the following line to the update() method to ensure that checkForBirdCollidingWithPipes() gets called on each frame update:

Main.prototype.update = function(evt)
{
  this.bird.update();
  this.ground.update();
  this.pipes.update();
  
  this.checkForBirdCollidingWithGround();
  this.checkForBirdCollidingWithPipes();
}

Now let’s add the birdHitPipe() method:

Main.prototype.birdHitGround = function()
{
  this.bird.hitGround();
  this.ground.stopScrolling();
  this.pipes.stopScrolling();
}

Main.prototype.birdHitPipe = function()
{
  this.bird.fallFromSky();
  this.ground.stopScrolling();
  this.pipes.stopScrolling();
}

Save your work, republish, and test your latest changes in the browser.

Start flapping and you should now notice that your little hero can now slam straight into a pipe before plummeting to his death. Once he hits the ground you can restart the game by clicking the mourse or pressing any key. However you may notice that the game currently doesn’t reset properly. Each of the pipes should start outside the screen on each retry. At the moment they don’t, which can result in the bird appearing directly over the pipe that just killed him, which in turn will result in the bird immediately dying again.

This can easily be rectified. Move to the Pipes class and add the following line of code:

Pipes.prototype.startScrolling = function()
{
  this.scrolling = true;
  this.setupStartPosition();
}

Now save and republish. The pipes should properly reset after you start a new game.

Improving Flapping and Falling

Things are looking good but our flappy bird looks a little unnatural when in flight. We can make the bird’s motion look more realistic by rotating his body based on his vertical velocity. This will result in the bird pointing skywards when the player initiates a flap, and rotating towards a downwards position the faster he drops towards the ground.

To do this we need to decide how far skywards the bird can tilt and also how far forward the bird needs to tilt when nosediving towards the ground. At the moment our bird’s movie-clip is orientated to an angle of 0 degrees. To tilt upwards we can simply set a negative angle. Let’s try setting the bird’s rotation property to a value of -22 just to see how that looks. Add the following temporary line into the Bird class’ constructor:

function Bird()
{
  this.mc = exportRoot.bird;
  this.initY = this.mc.y;
  this.state = Bird.DEAD;
  this.velocity = 0;
  this.setupCollisionPoints();
  this.mc.rotation = -22;
}

Save your change, re-publish, and refresh the browser. You should now see the bird pointing skywards.

This looks good. We’ll make sure the bird doesn’t rotate beyond this point when flapping upwards. Now let’s decide upon a maximum rotation value for when the bird is falling towards the ground. Remove your previous test line and let’s try a rotation of 90:

function Bird()
{
  this.mc = exportRoot.bird;
  this.initY = this.mc.y;
  this.state = Bird.DEAD;
  this.velocity = 0;
  this.setupCollisionPoints();
  this.mc.rotation = -22;
  this.mc.rotation = 90;
}

Save, re-publish, and test. You should now see the bird pointing straight down as if performing an impressive nosedive. We’ll make sure the bird does not rotate beyond this angle when heading towards the ground.

Now let’s add two new constants to the Bird class that store both angles. Also, remove your previous line of code as it’s no longer required:

function Bird()
{
  this.mc = exportRoot.bird;
  this.initY = this.mc.y;
  this.state = Bird.DEAD;
  this.velocity = 0;
  this.setupCollisionPoints();
  this.mc.rotation = 90;
}

Bird.ALIVE = 0;
Bird.DYING = 1;
Bird.DEAD  = 2;

Bird.MAX_UP_ANGLE   = -22.5;
Bird.MAX_DOWN_ANGLE =  90;

The first constant holds the maximum angle we’ll rotate to when the bird is flying skywards, and the second constant holds the maximum angle the bird will rotate towards when falling downwards.

Now let’s go ahead and write a method that actually calculates the current angle of rotation based on the bird’s velocity. We’ll call this method on every frame update to ensure the orientation of the bird’s body always reflects the bird’s current vertical velocity. Add the following method:

Bird.prototype.updateRotation = function()
{
  if (this.velocity > 0)
  {
    var diff = Bird.MAX_DOWN_ANGLE - Bird.MAX_UP_ANGLE;
    var norm = this.velocity / Main.MAX_VELOCITY;
    this.mc.rotation = Bird.MAX_UP_ANGLE + (diff * norm);
  }
  else
  {
    this.mc.rotation = Bird.MAX_UP_ANGLE;
  }
}

Let’s review the code we’ve just written. If the bird’s vertical velocity is greater than 0 then we know that the bird is falling towards the ground. If this is the case then we use the following three lines of code to calculate the angle of rotation and apply it to the bird’s movie-clip:

var diff = Bird.MAX_DOWN_ANGLE - Bird.MAX_UP_ANGLE;
var norm = this.velocity / Main.MAX_VELOCITY;
this.mc.rotation = Bird.MAX_UP_ANGLE + (diff * norm);

If the bird’s vertical velocity is 0 or less then we know that it is moving upwards. If that is the case then we simply snap the bird’s orientation skywards. The following line takes care of that by simply setting the bird’s rotation property to the value of the Bird.MAX_UP_ANGLE constant:

this.mc.rotation = Bird.MAX_UP_ANGLE;

Now all that’s left to do is call our updateRotation() method on every frame update. Add the following line:

Bird.prototype.update = function()
{
  if (this.state == Bird.ALIVE || this.state == Bird.DYING)
  {
    this.updateVelocity();
    this.updateRotation();
  }
}

Save your changes, re-publish, and test everything within the browser. Hopefully you’re happy with the tilting of the bird each time he flaps upwards and then falls towards the ground. It’s a nice effect and you can also easily tweak the settings by adjusting the Bird.MAX_UP_ANGLE and Bird.MAX_DOWN_ANGLE constants.

Detecting When The Bird Passes Through A Pipe

In Flappy Bird, the player is awarded a point each time the bird passes through a pair of pipes. We’ll wrap today’s tutorial up by writing some code to actually detect when the bird has successfully flown between a pair of pipes.

The first thing we’ll need to do is add a new method to the Bird class that will return the bird’s position. Add the following:

Bird.prototype.getPosition = function()
{
  return {x: this.mc.x, y: this.mc.y};
}

The above method simply returns an Object with two properties: x and y, which represent the stage position of the bird’s movie-clip.

Now we need to move to the Pipes class and add some code that will help determine if the bird has flown through a pair of pipes. We’re particularly interested in the pipe that the bird is currently approaching. Let’s create a member variable that will hold a reference to the pair of pipes that the bird is currently approaching. Add the following:

Pipes.prototype.startScrolling = function()
{
  this.scrolling = true;
  this.setupStartPosition();
  this.approachingPipe = this.pipes[0];
}

When the game starts, the first pipe stored in the pipes member variable will be the pipe that the bird is currently approaching. We therefore set that first pipe to our approachingPipe member variable.

Each time the bird passes through a pair of pipes, we’ll need to set the approachingPipe member variable to point to the next upcoming pipe pair. Add the following method to do this:

Pipes.prototype.setNextApproachingPipe = function()
{
  for (var i = 0; i < this.pipes.length; i++)
  {
    if (this.pipes[i] == this.approachingPipe)
    {
      this.approachingPipe = this.pipes[i + 1];
      return;
    }
  }
}

This method will get called every time the bird passes through the current approaching pipe. It simply loops through our array of pipe movie-clip references until it finds the current approaching pipe. Once found, the method simply sets the approachingPipe member variable to point to the very next pipe stored in the array.

We also need to add a method to the Pipes class that is used to determine if our flappy hero has actually passed through the approaching pipes pair. Add the following method:

Pipes.prototype.hasBirdPassedApproachingPipe = function(bird)
{
  var birdXPosition = bird.getPosition().x;
  if (birdXPosition > this.approachingPipe.x)
  {
    return true;
  }

  return false;
}

There’s not much to this method. It takes a reference to our Bird instance as a parameter. It then gets the bird’s x-position using its getPosition() method and compares that value against the x-position of the approaching pipe. If the bird’s x-position is greater than the pipe’s then we know that the bird has passed through the pipe.

Now all we need to do is add some code to the Main class to hook everything together. Move to the Main class and add the following method to it:

Main.prototype.checkForBirdPassingPipe = function()
{
  if (this.bird.isAlive())
  {
    if (this.pipes.hasBirdPassedApproachingPipe(this.bird))
    {
      this.pipes.setNextApproachingPipe();
      console.log("Main::checkForBirdPassingPipe() - Scored a point!");
    }
  }
}

The above method will get called on every frame update. If the bird is alive then is simply calls the Pipes instance’s hasBirdPassedApproachingPipe() method to see if the bird has successfully flown through a pipe. If it has then we make another call to the Pipes instance to set the next approaching pipe. Finally we write some text to the JavaScript console informing us that the player has scored a point.

At the moment we don’t have anything in place to actually track, increment, or display the player’s updated score, but we’ll address that in the next tutorial. For the time being, writing text to the console will do.

Finally, make a call to our checkForBirdPassingPipe() method so that it does indeed get called on every frame update:

Main.prototype.update = function(evt)
{
  this.bird.update();
  this.ground.update();
  this.pipes.update();
  
  this.checkForBirdCollidingWithGround();
  this.checkForBirdCollidingWithPipes();
  this.checkForBirdPassingPipe();
}

Save your FLA and test the your latest changes in the browser.

Now when your fly through a pair of pipes, the following text will be written to the console:

Main::checkForBirdPassingPipe() - Scored a point!

This validates that our code is now successfully tracking the bird as it flies through each of the game’s pipes.

Summary

The game is almost finished! The scrolling of the ground and pipes is in place and the player can now control and navigate the bird through each of the game’s pipes. We have basic but effective game physics and working collision detection too!

Keep reading:

Part 1, Part 2Part 3Part 4, Part 5, Part 6

Gaming, HTML5

Join the discussion

  • By Mirela - 12:40 AM on September 23, 2016  

    Need more time just to understand some of those commands! Thank you for sharing!