Creating an Endless Runner Game in Phaser Part 7 Scrolling

Now that we have the real runner graphics in place, it is time to add some scrolling to the game to really make it look like our guy is really running.

First though, let’s clean up some of the graphics.

The Bird

The first thing is to is to replace the bird block with an animation.  We will do this in the same way that we did for the runner, by using a sprite sheet with a .json file

 

Bird Sprite Sheet

Replace the line

game.load.image("bird", "images/bird.png");

With

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

Then we just need to add the animations to the bird. lines 3 and 4 below.

//add the bird sprite to the game
this.bird = game.add.sprite(game.width + 100, birdY, "bird");
this.bird.animations.add("fly",this.makeArray(0,8),12,true);
this.bird.animations.play("fly");

If we leave the bird graphics as they are, it will be too big for the screen, so we can set the bird’s width to 10 percent of the game’s width and then scale the bird’s height proportionately.

this.bird.width=game.width*.1;
this.bird.scale.y=this.bird.scale.x;

If you run the code now you’ll see the bird is the right size, but facing the wrong direction. We can reverse it by flipping the scale.x property

this.bird.scale.x=-this.bird.scale.x;

I also replaced some of the temporary graphics with real ones and added a background and simply stretched it to fit the game’s width and height.

//in preload
game.load.image("background","images/background.png");
//in the create function
 //background
  this.background=game.add.sprite(0,0,"background");
  this.background.width=game.width;
  this.background.height=game.height;

Scroll The Ground

Currently, we are using the graphic below and stretching it to fit the width.

ground.png

Let’s replace it with something more suitable for our game.

grass.png

Replace the line

game.load.image("ground", "images/ground.png");

with

game.load.image("grass", "images/grass.png");

Now, of course, if we are to take that block and stretch it, the ground would look distorted.  By one of the very nice features of Phaser is the tileSprite.

It is similar in nature to a normal sprite, but as the name implies, it tiles, or repeat the image.

A tilesprite is added to the game by using the following code

game.add.tileSprite(x,y,width,height,imageKey);

The tile sprite will fill in the space provided by width and height settings with the image. In other words tile the space as if it were a floor.

If we were to give the width and height as 250 x 250 Phaser would fill in our sprite like this

game.add.tileSprite(0,0,250,250,"grass");

We would get this result.

To get a horizontal line, we need only restrict the height in the tileSprite to equal that of the image, which in this case is 50 px.

game.add.tileSprite(0,0,250,50,"grass");

So to get a line of grass blocks to stretch across the bottom we just need to set the width to equal the game width and adjust the y position.

We will replace the original ground sprite code with

// this.ground = game.add.sprite(0, game.height * .9, "ground");
this.ground=game.add.tileSprite(0,game.height*.9,game.width,50,"grass");

Now here is were the magic come in. A tileSprite has a function called autoScroll. We can set that to scroll by an x and y value and just leave Phaser to do its work. (I won’t take the time to explain all the hours I wasted getting this to work in Flash)

this.ground.autoScroll(xScroll,yScroll);
//we only want to scroll by x, so leave y at 0
this.ground.autoScroll(-150,0);

Now our ground moves along with our runner! I had to try several different values to get the running and the scrolling just right.

I also used the same technique to add some mountains and replaced the clouds, and put autoScroll on all of them.

Here is what my StateMain.js looks like now:

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("grass", "images/grass.png");

        game.load.atlasJSONHash('bird','images/bird.png','images/bird.json');
        game.load.image("playAgain", "images/playAgain.png");
        game.load.image("clouds", "images/clouds.png");
        game.load.atlasJSONHash('hero', 'images/explorer.png', 'images/explorer.json');
       //
       //
       //
       game.load.image("background","images/background.png");
       game.load.image("mountains1","images/mountains1.png");
       game.load.image("mountains2","images/mountains2.png");

    },
    create: function() {
        this.clickLock = false;
        this.power = 0;
        //turn the background sky blue
        game.stage.backgroundColor = "#00ffff";


        //background
        //
        this.background=game.add.sprite(0,0,"background");
        this.background.width=game.width;
        this.background.height=game.height;

        //mountains
        //
        this.mountain1=game.add.tileSprite(0,0,game.width,game.height/2,"mountains1");
        this.mountain1.y=game.height-this.mountain1.height;

        this.mountain2=game.add.tileSprite(0,0,game.width,game.height/3,"mountains2");
        this.mountain2.y=game.height-this.mountain2.height;

        this.mountain1.autoScroll(-50,0);
        this.mountain2.autoScroll(-150,0);

        //add the ground
       // this.ground = game.add.sprite(0, game.height * .9, "ground");
        this.ground=game.add.tileSprite(0,game.height*.9,game.width,50,"grass");
        this.ground.autoScroll(-150,0);
        //
        //
        //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.tileSprite(0,0,game.width*1.1,100,"clouds");
        this.clouds.autoScroll(-50,0);
        //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");
        this.bird.animations.add("fly",this.makeArray(0,8),12,true);
        this.bird.animations.play("fly");
        //
        //
        //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);

        this.bird.width=game.width*.1;
        this.bird.scale.y=this.bird.scale.x;
        this.bird.scale.x=-this.bird.scale.x;

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

You can see the result here

2 thoughts on “Creating an Endless Runner Game in Phaser Part 7 Scrolling”

  1. Michelle Messenger

    Hello! I have a question. I’m trying to run this in Phaser 3. atlasJSONHash is not a function is the error I am getting. Is there a different want to do the bird and explorer?

    1. There are ways to do this. You could just space out the images and load it a spritesheet with frameHeight and frameWidth set.

      This tutorial is for Phaser 2. If you want to use Phaser 3 with JSON data and sprites, it needs a different format. I use a program called texture packer. It has settings for Phaser 2 and 3.

Leave a Comment