Creating an Endless Runner Game in Phaser Part 6 Add the Runner

Real Graphics

Whenever we decide to develop a game in the office, I usually want to get started on it right away. Of course, the artists need time to develop the artwork. In the meantime, I use temporary graphics or what one of my artist friends calls “Dev Art”. I also sometimes use this on a game I’m developing which I’m not sure will be released, and don’t want to put a lot of time in before I’m sure.

The Runner

For the runner, I’m using one of my go-to character animations, a freebie courtesy of gameart2d.com.

I’ve used the TexturePackerGUI software to create the sprite sheet. You can read about how I use this to make sprite sheets in this TexturePackerGUI Tutorial.

I’ve also made a standalone character download. You can download those files if you wish to use them to follow along with this post.

Load the Sprite Sheet

After copying the explorer.png and explorer.json files to the images folder, we now need to assign the hero key to the sprite sheet instead of our temporary hero.png. I’ve commented out the old code and added one to load the sprite sheet.

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

        game.load.atlasJSONHash('hero', 'images/explorer.png', 'images/explorer.json');        

    },

Add The Animations

Next, we need to add the animations. I’ve left out the slide and idle animations as we won’t be needing them.

I’ve also added a support function called makeArray so we don’t have to type out [31,32,33,…

Place this just after the create function

makeArray:function(start,end)
    {
        var myArray=[];
        for (var i = start; i < end; i++) {
            myArray.push(i);
        }
        return myArray;
    }

Add these lines inside your create function to define the animations

 this.hero.animations.add("die",this.makeArray(0,10),12,false);
 this.hero.animations.add("jump",this.makeArray(20,30),12,false); 
 this.hero.animations.add("run",this.makeArray(30,40),12,true);

Next, we set the hero’s animation to running.

this.hero.animations.play("run");

At the end of the mouseUp function, we can play the jump animation

this.hero.animations.play("jump");

To detect when the hero is back on the ground we need to add a function to the collision detection between the hero and the ground called onGround

game.physics.arcade.collide(this.hero, this.ground,this.onGround,null,this);

If the onGround function is called, it sets the hero’s animation back to running. For some reason, this was being called even before the hero sprite was being created, so I’ve put a quick if-then to check that the hero already exists.

onGround()
    {
        if (this.hero)
        {
            this.hero.animations.play("run");
        }        
    }

In the delayOver function we will play the die animation for when the hero gets hit

if (this.hero) {
this.hero.animations.play("die");
this.hero.body.velocity.y = 100;
}

Scaling the Runner

To make up for the changes in height I’ve put the hero on the ground by changing:

this.hero = game.add.sprite(game.width * .2, this.ground.y-25, "hero");

to

this.hero = game.add.sprite(game.width * .2, this.ground.y, "hero");

And to resize the hero I’ve made it 1/12 of the game’s with and scaled it proportionately and set the anchor so the foot is at the y position of the hero.

this.hero.width=game.width/12;
this.hero.scale.y=this.hero.scale.x;
this.hero.anchor.set(0.5,1);

This is the entire StateMain.js

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");
        game.load.atlasJSONHash('hero', 'images/explorer.png', 'images/explorer.json');
    },
    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, "hero");
        //make animations
        this.hero.animations.add("die", this.makeArray(0, 10), 12, false);
        this.hero.animations.add("jump", this.makeArray(20, 30), 12, false);
        this.hero.animations.add("run", this.makeArray(30, 40), 12, true);
        this.hero.animations.play("run");
        this.hero.width = game.width / 12;
        this.hero.scale.y = this.hero.scale.x;
        this.hero.anchor.set(0.5, 1);
        //add the power bar just above the head of the hero
        this.powerBar = game.add.sprite(this.hero.x + this.hero.width / 2, this.hero.y - this.hero.height / 2, "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();
    },
    makeArray: function(start, end) {
        var myArray = [];
        for (var i = start; i < end; i++) { myArray.push(i); } return myArray; }, 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); this.hero.animations.play("jump"); }, 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);
    },
    onGround() {
        if (this.hero) {
            this.hero.animations.play("run");
        }
    },
    update: function() {
        game.physics.arcade.collide(this.hero, this.ground, this.onGround, null, this);
        //
        //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;
        if (this.hero) {
            this.hero.animations.play("die");
            this.hero.body.velocity.y = 100;
        }
        game.time.events.add(Phaser.Timer.SECOND, this.gameOver, this);
    },
    gameOver: function() {
        game.state.start("StateOver");
    }
}

Here is the result:

Click Here to Play

3 thoughts on “Creating an Endless Runner Game in Phaser Part 6 Add the Runner”

  1. Hi, your tutorial has been quite useful for me, I’m learning how to code JS so I don’t know a lot. I’ve been trying to add coins for the player to collect them but all attempts have been unsuccessful. Any ideas how to do it?

    It would be awesome if you could help me,
    thanks!

Leave a Comment