Creating an Endless Runner Game in Phaser Part 5 Game Over

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

  1. Add the gameOver.js to the index.html file
  2. 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, collideCallbackprocessCallbackcallbackContext) 

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

 

Leave a Comment