Welcome to the final part of this HTML5 Flappy Bird tutorial series. We’ll work on the game’s user interface and finish up by adding some sound effects.
What you will learn…
- How to programmatically update text fields
- How to apply a tween to a movie-clip using JavaScript
- How to preload and playback sound within your projects
What you will need…
- A basic understanding of at least one programming language, such as JavaScript or ActionScript
Before you continue, spend a few moments looking at the final version of the game here: www.yeahbutisitflash.com/projects/flappy-animatecc/flappy.html.
Take a look at the user interface elements and listen out for the sound effects that are triggered as the game plays. We’ll be adding these bells and whistles onto your project today.
Getting Started
You’ll need Adobe Animate CC. A trial version can be downloaded from www.adobe.com/products/animate.html. You’ll also need the FLA file you were working with in part five.
Okay let’s get coding.
Tracking the Score
Last time we got as far as detecting when the bird had successfully passed through the pipes. However we hadn’t gone as far as actually awarding a point to the player. Let’s go ahead and do that.
We’ll begin by adding a member variable to our Main
class that will track the player’s current score. Add the following line:
function Main() { this.ground = new Ground(); this.pipes = new Pipes(); this.bird = new Bird(); this.score = 0; 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); }
Each time the player begins a game we’ll need to reset the score. Add the following line to the startGame()
method to do that:
Main.prototype.startGame = function() { this.score = 0; this.ground.startScrolling(); this.pipes.startScrolling(); this.bird.startFlying(); }
Now let’s write a method within our Main
class that will increment the score and display it on the screen:
Main.prototype.scoredPoint = function() { this.score++; exportRoot.score.text = this.score; }
In the method above, we target the stage’s score text field and update it by setting its text
property to the value stored within our score
member variable.
To date we’ve made use of the MovieClip
class provided by CreateJS to update our game’s display objects. However, text fields are instances of CreateJS’ Text
class. The Text
class provides an API for handling text, including the text
property for obtaining and changing the text field’s value. You can find our more about the Text
class on the official CreateJS site: createjs.com/docs/easeljs/classes/Text.html.
Finally, call this method from the class’ checkForBirdPassingPipe()
method. We’ll also remove the code we wrote previously that sent some text to the browser’s JavaScript console:
Main.prototype.checkForBirdPassingPipe = function() { if (this.bird.isAlive()) { if (this.pipes.hasBirdPassedApproachingPipe(this.bird)) { this.pipes.setNextApproachingPipe();
console.log("Main::checkForBirdPassingPipe() - Score a point!");this.scoredPoint(); } } }
Save your changes then publish your document to the browser.
Now each time you successfully fly between a pair of pipes you should see your score update on the screen. You will probably notice that the score doesn’t yet reset each time you start a new game. Don’t worry though, we’ll get onto that in just a moment. But first let’s do some refactoring.
Creating the UI Class
At the moment our UI code is being handled within the Main
class. Let’s move that code into its own class. This will give us a good place to add code that handles the rest of the game’s user interface elements.
Create a new timeline layer directly above the Main class layer. Name the layer UI 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 begin by adding a constructor to the class that will store a reference to each of our game’s user interface elements:
function UI() { this.scoreLabel = exportRoot.score; this.gameOverPrompt = exportRoot.gameOverPrompt; this.getReadyPrompt = exportRoot.getReadyPrompt; this.anyKeyPrompt = exportRoot.anyKeyPrompt; this.screenFlash = exportRoot.screenFlash; }
Now let’s write a method within our class that is responsible for display the player’s current score. Add the following to your class:
UI.prototype.updateScore = function(score) { this.scoreLabel.text = score; }
This method takes the current score as a parameter and updates the text field to reflect that score.
Now move back to your Main
class and we’ll go ahead and wire-up the UI
class to it.
Within the Main
class’ constructor, instantiate the UI
class and store it within a member variable:
function Main() { this.ground = new Ground(); this.pipes = new Pipes(); this.bird = new Bird(); this.ui = new UI(); this.score = 0; 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); }
Next, alter the scoredPoint()
method to call the UI
class’ updateScore()
method:
Main.prototype.scoredPoint = function() { this.score++;
exportRoot.score.text = this.score;this.ui.updateScore(this.score); }
Save your changes then publish and test within the browser.
If everything has gone according to plan then the score should still update each time you fly between the pipes. We haven’t added any more functionality. All our changes are under the hood. The code that handles the updating of the score now sitting within our new UI
class.
Now we are in a position to reset the score when each game begins.
Clearing the Score on Each New Game
We’ll write a new method within the UI
class that gets called each time the player begins a game. It’s within this method that we’ll reset the on-screen score. Add the following method named gameStart()
:
UI.prototype.gameStart = function() { this.updateScore(0); }
We’ll add more to this method shortly.
Now let’s add some code to our Main
class to ensure that gameStart()
gets called when the player starts a game. Add the following line to the Main
class:
Main.prototype.startGame = function() { this.score = 0; this.ground.startScrolling(); this.pipes.startScrolling(); this.bird.startFlying(); this.ui.gameStart(); }
Save your changes and republish. Now the score will reset each time you start a new game.
Let’s go ahead and start lacing in the other user interface elements. We’ll begin by showing the Get Ready prompt and start instructions.
Showing the Start Instructions
Do this by adding the following method to the UI
class:
UI.prototype.updateScore = function(score) { this.scoreLabel.text = score; } UI.prototype.getReady = function() { this.gameOverPrompt.visible = false; this.getReadyPrompt.visible = true; this.startInstructions.visible = true; this.screenFlash.visible = false; }
The getReady()
method above takes the various member variables that represent each of the UI elements and sets their visibility. We make the Get Ready prompt and the start instructions visible and hide the others.
Also, make a call to getReady()
from within the class’ constructor. This will ensure that the user interface gets properly setup when the game is first loaded:
function UI() { this.scoreLabel = exportRoot.score; this.gameOverPrompt = exportRoot.gameOverPrompt; this.getReadyPrompt = exportRoot.getReadyPrompt; this.startInstructions = exportRoot.startInstructions; this.screenFlash = exportRoot.screenFlash; this.getReady(); }
You may remember that in part three we added some code to the Main
class to temporarily hide the game’s user interface elements. With the UI
class’ getReady()
method in place, we no longer need this code. Move to the Main
class and remove the following lines:
function Main() { this.ground = new Ground(); this.pipes = new Pipes(); this.bird = new Bird(); this.ui = new UI(); this.score = 0;
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); }
Save your changes and re-publish.
Now when you load the game you’ll see a Get Ready prompt and text informing the user how to start the game. If you try playing the game you’ll notice that these UI elements don’t disappear. Let’s go ahead and address that.
Removing the Start Instructions
Remember that the UI
class’ gameStart()
method gets called every time the player starts a new game. This is the ideal place to hide the game’s start instructions. Add the following lines of code:
UI.prototype.gameStart = function() { this.gameOverPrompt.visible = false; this.getReadyPrompt.visible = false; this.startInstructions.visible = false; this.screenFlash.visible = false; this.updateScore(0); }
Save your changes and re-publish. This time the user interface elements will be hidden when a new game is started.
Game Over Prompt
We’re making real progress. You’ll notice that there’s nothing to inform the player that their game is over. Let’s rectify that by displaying the Game Over message when our little flappy bird is killed.
Let’s add a new method to our UI
class named gameOver()
:
UI.prototype.gameOver = function() { this.gameOverPrompt.visible = true; this.getReadyPrompt.visible = false; this.startInstructions.visible = true; }
We’ll call this method from the Main
class’ birdHitGround()
method:
Main.prototype.birdHitGround = function() { this.bird.hitGround(); this.ground.stopScrolling(); this.pipes.stopScrolling(); this.ui.gameOver(); }
Test your latest changes in the browser. You’ll now be greeted with a Game Over message at the end of each game, which will then get hidden from view when you start another.
The Screen Flash
We’re almost done with the user interface work. All that’s left to do is add a screen flash effect when the bird collides with a pipe.
If you think back to part one you’ll remember that we created a white rectangular movie-clip that covered the screen. In part two we added that movie-clip to the stage and gave it an instance name of screenFlash. At the moment that clip remains hidden from view during play. What we want to do is make it visible when the player collides with a pipe then apply a tween to it to simulate a quick screen flash effect. We’ll write a new method to take care of this.
Within the UI
class, add the following method:
UI.prototype.triggerScreenFlash = function() { this.screenFlash.visible = true; this.screenFlash.alpha = 1; createjs.Tween.get(this.screenFlash).to({alpha:0}, 100); }
Our method above performs the tween using code. CreateJS provides a Tween
class that is used to change the value of properties over a period of time. In our method, we use it to change the alpha
transparency of our screenFlash
member variable from 1 to 0 over the course of 100 milliseconds. This will give us a quick and snappy screen flash effect.
Tween
class from the official docs: createjs.com/docs/tweenjs/classes/Tween.html.Now all we need to do is move to our Main
class and call our triggerScreenFlash()
method. We’ll make the call from within the birdHitPipe()
method:
Main.prototype.birdHitPipe = function() { this.bird.fallFromSky(); this.ground.stopScrolling(); this.pipes.stopScrolling(); this.ui.triggerScreenFlash(); }
Save your changes and test everything within the browser.
Now when you collide with a pipe the screen will flash. This does an effective job of further communicating to the player that a collision has taken place.
It’s looking good! That’s us finished with the user interface but there’s still one thing missing. That’s right, sound.
Adding Sound
To finish things off we’ll add three sound effects to our game. Specifically we’ll need sounds for:
- When a point is scored by successfully flying between a pair of pipes.
- When the player flaps the bird’s wings.
- When the bird hits a pipe or strikes the ground.
Three sound files encoded in the popular WAV format have been made available for you to use in your project. They have been packaged within a ZIP file and can be found here: www.yeahbutisitflash.com/projects/flappy-animatecc/sound.zip.
Once downloaded, uncompress the ZIP file. You’ll find the files within a folder named sound. Copy this folder to the same location as your project’s flappy.fla file. We’ll write JavaScript to load and use these sounds.
The CreateJS suite provides a library named SoundJS, which makes it easy to handle audio. We’ll take advantage of it to preload and playback our game’s sound effects.
Preloading the Sound
The first thing that’s needing done is to preload our three audio files into memory. We’ll add a method to our Main
class to take care of that. Add the following:
Main.prototype.registerSound = function() { createjs.Sound.registerSound("sound/point.wav", "point"); createjs.Sound.registerSound("sound/flap.wav", "flap"); createjs.Sound.registerSound("sound/hit.wav", "hit"); }
We make use of SoundJS’ registerSound()
method. It takes two arguments. The first is the relative file path to a sound file to be loaded. The second is an author specified ID that is used to actually play the sound later. We’ve assigned the IDs of point, flap, and hit to each of our sounds respectively.
Call our registerSound()
method from the Main
class’ constructor. Add the following line:
function Main() { this.ground = new Ground(); this.pipes = new Pipes(); this.bird = new Bird(); this.ui = new UI(); this.score = 0; this.registerSound(); createjs.Ticker.addEventListener("tick", this.update.bind(this)); canvas.onmousedown = this.userPressed.bind(this); window.onkeydown = this.userPressed.bind(this); }
Save your changes. Okay, let’s hook-up the playback of one of the sounds.
Playing Sound When a Point is Scored
We’ll playback our point sound every time the player scores a point. This can quite easily be done with a single line of JavaScript. Add the following to our Main
class’ scoredPoint()
method:
Main.prototype.scoredPoint = function() { this.score++; this.ui.updateScore(this.score); createjs.Sound.play("point"); }
The SoundJS library provides a play()
method that will playback the sound that was preloaded and registered using the specified ID. In our case, we request that the sound with the ID of point be played back.
Save your changes and re-publish your FLA. Try the game in your browser and you should now hear a sound effect being played every time your successfully fly between a pair of pipes.
Playing the Flap Sound
We’ll trigger the remaining two sounds from within the Bird
class. Open it within the Actions panel and find the flap()
method. As you can probably guess, this is a good place to actually trigger a flapping sound. Add the following line:
Bird.prototype.flap = function() { if (this.state == Bird.ALIVE && this.mc.y > 0) { this.velocity = Main.FLAP_IMPULSE; createjs.Sound.play("flap"); } }
Save and test your latest changes. Every time you click the screen or press a key you’ll hear a flapping sound to accompany your action.
Playing the Collision Sound
The last thing we need to implement is the playback of a sound when the bird either collides with a pipe or hits the ground.
One thing that’s important when working with audio is that you don’t bombard the player with too many sounds as it can quickly annoy. This is very true of our hit sound. To avoid irritating the user, we’ll only play the sound when the bird hits a pipe or collides with the ground, but not both.
If the player nosedives the bird into the ground then we’ll trigger the hit sound. If, on the other hand, the bird collides with a pipe first before falling to the ground, we’ll make sure the hit sound is only triggered in response to striking the pipe but not when the bird finally hits the ground.
Start by triggering the hit sound from within the Bird
class’ fallFromSky()
method. After all, this gets called when the bird has just collided with a pipe. Add the following line:
Bird.prototype.fallFromSky = function() { if (this.state == Bird.ALIVE) { this.state = Bird.DYING; this.velocity = 0; createjs.Sound.play("hit"); } }
We’ll also need to trigger the same sound from within the hitGround()
method. However, we’ll need to ensure that the sound is played only if the bird has struck the ground without first hitting a pipe. This can be done quite easily by checking the bird’s state:
Bird.prototype.hitGround = function() { if (this.state == Bird.ALIVE) { createjs.Sound.play("hit"); } this.state = Bird.DEAD; this.mc.stop(); }
Save your changes, re-publish, and test everything within the browser.
Congratulations! You now have a fully working Flappy Bird clone complete with audio.
Summary
Thanks for sticking with me across all six parts! I hope you’ve learned how easy it is to leverage Adobe Animate CC to create exciting responsive HTML5 content that will work across all modern browsers. As well as working with the design and animation tools within Animate CC, we’ve also spent considerable time working with JavaScript and seeing how the CreateJS suite integrates with Animate CC’s HTML5 Canvas document. And hopefully you’ve also picked up a few games programming tips along the way!
You can find the final version of the project on GitHub at: github.com/ccaleb/flappy-bird-animate-cc-tutorial. As an added bonus I’ve separated the JavaScript code into external files that are loaded at runtime using the PreloadJS library, which is part of the CreateJS suite.
We’ve only scratched the surface of the excellent CreateJS suite. I highly encourage you to spend as much time as possible with the official CreateJS documentation as you work on your own HTML5 Canvas projects. It can be found on the official site here: createjs.com.
Thanks again and good luck with any future projects and experiments!
Christopher Caleb
Christopher Caleb is a freelance developer and published author. His body of work spans a wide range of projects that encompass casual games, interactive kiosks, social networks, and mobile applications. Christopher’s expertise covers both native and cross-platform technologies, with a focus on optimisation within hardware constraints. He specialises in delivering highly engaging experiences with usability being a primary concern.
He blogs at www.yeahbutisitflash.com and tweets as @chriscaleb.