Adding A Bird
You may have noticed that it is quite easy to jump over the blocks if you just hold down the mouse long enough to jump all the way to the top of the game! So to make it more of a challenge the first thing I thought was to add a bird in the sky. Sometimes you’ll have to jump over the bird and sometimes jump between the bird and the blocks.
For now, we will just use a blue block to represent the bird.
You can download that image or download the zip file at the end of this post.
Let’s start by preloading the bird graphic. Place this in the stateMain.js preload function
game.load.image("bird", "images/bird.png");
Next, we will use a function to create the bird, and we will only have one bird on the screen at a time.
makeBird: function() { //if the bird already exists //destroy it if (this.bird) { this.bird.destroy(); } //pick a number at the top of the screen //between 10 percent and 40 percent of the height of the screen var birdY = game.rnd.integerInRange(game.height * .1, game.height * .4); //add the bird sprite to the game this.bird = game.add.sprite(game.width + 100, birdY, "bird"); //enable the sprite for physics game.physics.enable(this.bird, Phaser.Physics.ARCADE); //set the x velocity at -200 which is a little faster than the blocks this.bird.body.velocity.x = -200; //set the bounce for the bird this.bird.body.bounce.set(2, 2); }
In the update function, we need to check to see if the bird has flown off the screen
//if the bird has flown off screen //reset it if (this.bird.x < 0) { this.makeBird(); }
Adding the game over state
When the hero crashes into the bird or the block we need the game to be over, and give the player a chance to play again. For that, we need to add a new state to the game. If you are new to using states, please see this post here.
Create a new .js file in your js folder called stateOver.js
Next, let us load a button for play again. You can do this in either the stateMain or the stateOver, but personally, I like to keep all my preloads in one place.
game.load.image("playAgain", "images/playAgain.png");
Download this image or find it in the zip file at the end of this post
Here is my stateOver.js file
var StateOver={ create:function() { //add a sprite to be used as a play again button this.playAgain=game.add.sprite(game.width/2,game.height/2,"playAgain"); //center the button image this.playAgain.anchor.set(0.5,0.5); //enable for input this.playAgain.inputEnabled=true; //add an event listener this.playAgain.events.onInputDown.add(this.restartGame,this); }, restartGame:function() { //restart the game by starting stateMain game.state.start("StateMain"); } }
Before we can use the state we need to do two things
- Add the gameOver.js to the index.html file
- Tell Phaser the StateOver variable is a state
Add this line to your index.html file in the head section
<script src="js/stateOver.js"></script>
Next, in the main.js file add this line just before the line that starts stateMain
game.state.add("StateOver",StateOver);
Wait a second…
We don’t want to immediately show the game over screen because we want the player to see what they hit, and notice the blocks flying about. So we will use a timer to delay starting the gameOver state.
In the stateMain.js file we will create 2 functions delayOver and gameOver
delayOver: function() { this.clickLock = true; game.time.events.add(Phaser.Timer.SECOND, this.gameOver, this); }, gameOver: function() { game.state.start("StateOver"); }
The clickLock variable is set to lock out any further clicks after the hero has been hit.
When the game starts we will set it to false. Add this line to the top of the create statement.
this.clickLock = false;
To deny the click add this in the mouseDown function of the game.
if (this.clickLock == true) { return; }
Hooking up to the game over function
So far we have detected collisions in the update function, that allowed objects to react to each other. What we will do now is add a function to that collision detection to be called when the hero hit something.
The collide statement can take two functions a collide and a process. I won’t go into the difference here, but we will only be using the collide callback and use null in place of the process callback to ignore it.
collide(object1, object2, collideCallback, processCallback, callbackContext)
In the update function change
game.physics.arcade.collide(this.hero, this.blocks);
TO:
game.physics.arcade.collide(this.hero, this.blocks, this.delayOver, null, this);
and add this for the detection of the collision between the bird and the
game.physics.arcade.collide(this.hero, this.bird, this.delayOver, null, this);
Clouds!
After a while, although the game was more challenging, it was still too easy to just jump all the way to the top, so I decided to add some clouds, that the hero should not touch. Why? Acid rain, angry clouds, magic maybe? It is your game, you’ll have to decide why.
I used a gray image to represent the clouds since white just made it seem like part of the game was missing on a white background.
First preload the cloud image
game.load.image("clouds", "images/clouds.png");
Next, in the create statement, we will load the clouds and stretch it the width of the game. I place it towards the end of the create statement so that the hero will go under the clouds if he jumps that far.
//add the clouds this.clouds = game.add.sprite(0, 0, "clouds"); this.clouds.width = game.width;
Instead of using physics on the clouds we simply need to check if the hero is up at the top of the screen.
In the update function add:
if (this.hero.y < this.hero.height) { this.hero.body.velocity.y=200; this.delayOver(); }
Your stateMain.js file should now look like this:
var StateMain = { preload: function() { game.load.image("ground", "images/ground.png"); game.load.image("hero", "images/hero.png"); game.load.image("bar", "images/powerbar.png"); game.load.image("block", "images/block.png"); game.load.image("bird", "images/bird.png"); game.load.image("playAgain", "images/playAgain.png"); game.load.image("clouds", "images/clouds.png"); }, create: function() { this.clickLock = false; this.power = 0; //turn the background sky blue game.stage.backgroundColor = "#00ffff"; //add the ground this.ground = game.add.sprite(0, game.height * .9, "ground"); //add the hero in this.hero = game.add.sprite(game.width * .2, this.ground.y - 25, "hero"); //add the power bar just above the head of the hero this.powerBar = game.add.sprite(this.hero.x + 25, this.hero.y - 25, "bar"); this.powerBar.width = 0; //add the clouds this.clouds = game.add.sprite(0, 0, "clouds"); this.clouds.width = game.width; //start the physics engine game.physics.startSystem(Phaser.Physics.ARCADE); //enable the hero for physics game.physics.enable(this.hero, Phaser.Physics.ARCADE); game.physics.enable(this.ground, Phaser.Physics.ARCADE); //game.physics.arcade.gravity.y = 100; this.hero.body.gravity.y = 200; this.hero.body.collideWorldBounds = true; //this.hero.body.bounce.set(0, .2); this.ground.body.immovable = true; //record the initial position this.startY = this.hero.y; //set listeners game.input.onDown.add(this.mouseDown, this); this.blocks = game.add.group(); this.makeBlocks(); this.makeBird(); }, mouseDown: function() { if (this.clickLock == true) { return; } if (this.hero.y != this.startY) { return; } game.input.onDown.remove(this.mouseDown, this); this.timer = game.time.events.loop(Phaser.Timer.SECOND / 1000, this.increasePower, this); game.input.onUp.add(this.mouseUp, this); }, mouseUp: function() { game.input.onUp.remove(this.mouseUp, this); this.doJump(); game.time.events.remove(this.timer); this.power = 0; this.powerBar.width = 0; game.input.onDown.add(this.mouseDown, this); }, increasePower: function() { this.power++; this.powerBar.width = this.power; if (this.power > 50) { this.power = 50; } }, doJump: function() { this.hero.body.velocity.y = -this.power * 12; }, makeBlocks: function() { this.blocks.removeAll(); var wallHeight = game.rnd.integerInRange(1, 4); for (var i = 0; i < wallHeight; i++) { var block = game.add.sprite(0, -i * 50, "block"); this.blocks.add(block); } this.blocks.x = game.width - this.blocks.width this.blocks.y = this.ground.y - 50; // //Loop through each block //and apply physics this.blocks.forEach(function(block) { //enable physics game.physics.enable(block, Phaser.Physics.ARCADE); //set the x velocity to -160 block.body.velocity.x = -150; //apply some gravity to the block //not too much or the blocks will bounce //against each other block.body.gravity.y = 4; //set the bounce so the blocks //will react to the runner block.body.bounce.set(1, 1); }); }, makeBird: function() { //if the bird already exists //destory it if (this.bird) { this.bird.destroy(); } //pick a number at the top of the screen //between 10 percent and 40 percent of the height of the screen var birdY = game.rnd.integerInRange(game.height * .1, game.height * .4); //add the bird sprite to the game this.bird = game.add.sprite(game.width + 100, birdY, "bird"); //enable the sprite for physics game.physics.enable(this.bird, Phaser.Physics.ARCADE); //set the x velocity at -200 which is a little faster than the blocks this.bird.body.velocity.x = -200; //set the bounce for the bird this.bird.body.bounce.set(2, 2); }, update: function() { game.physics.arcade.collide(this.hero, this.ground); // //collide the hero with the blocks // game.physics.arcade.collide(this.hero, this.blocks, this.delayOver, null, this); // //collide the blocks with the ground // game.physics.arcade.collide(this.ground, this.blocks); // //when only specifying one group, all children in that //group will collide with each other // game.physics.arcade.collide(this.blocks); //colide the hero with the bird // game.physics.arcade.collide(this.hero, this.bird, this.delayOver, null, this); // //get the first child var fchild = this.blocks.getChildAt(0); //if off the screen reset the blocks if (fchild.x < -game.width) { this.makeBlocks(); } //if the bird has flown off screen //reset it if (this.bird.x < 0) { this.makeBird(); } if (this.hero.y < this.hero.height) { this.hero.body.velocity.y=200; this.delayOver(); } }, delayOver: function() { this.clickLock = true; game.time.events.add(Phaser.Timer.SECOND, this.gameOver, this); }, gameOver: function() { game.state.start("StateOver"); } }
Click here to see the result