HTML5 prototype of mobile hit “Flipping Legend” with top down view made with Phaser – adding holes

Read all posts about "" game
Welcome to the second step of the Flipping Legend HTML5 prototype. We are going to add deadly obstacles in front of our hero: holes. It’s quite simple: you walk over a hole, you die and the game restarts.
Click or tap on the left/right half of the canvas to move the hero accordingly. If you have a mobile device, play it directly from this link. Holes are sprites, but just like in every tile based game, we aren’t checking for collision but looking at the value of the array item the hero is walking on. I used object pooling to prevent the creation of unnecessary sprites. To have more information about object pooling, read this post. There’s a cool game too. Now, let me show you the completely commented source code:
// the game itself
var game;

// global object with game options
var gameOptions = {

    // width of the game, in pixels
    gameWidth: 640,

    // tint colors to be applied to tiles
    tileColors: [0x00ff00, 0x00aa00],

    // number of tiles visible, works better if it's even, in this first prototype
    verticalTiles: 9
}
window.onload = function() {

    // determining window width and height
    var windowWidth = window.innerWidth;
    var windowHeight = window.innerHeight;

    // if we are in ladscape mode, then set window height to fake a potrait mode
    if(windowWidth > windowHeight){
        windowHeight = windowWidth * 1.8;
    }

    // defining game height
    var gameHeight = windowHeight * gameOptions.gameWidth / windowWidth;

    // creation of the game istelf
    game = new Phaser.Game(gameOptions.gameWidth, gameHeight);

    // game states
    game.state.add("PreloadGame", preloadGame);
    game.state.add("PlayGame", playGame);
    game.state.start("PreloadGame");
}
var preloadGame = function(game){}
preloadGame.prototype = {
    preload: function(){

        // making the game cover the biggest window area possible while showing all content
        game.scale.scaleMode = Phaser.ScaleManager.SHOW_ALL;
        game.scale.pageAlignHorizontally = true;
        game.scale.pageAlignVertically = true;
        game.stage.disableVisibilityChange = true;

        // floor tile
        game.load.image("tile", 'tile.png');

        // hero sprite
        game.load.image("hero", 'hero.png');

        // hole sprite
        game.load.image("hole", 'hole.png');
    },
    create: function(){
        game.state.start("PlayGame");
    }
}
var playGame = function(game){}
playGame.prototype = {
    create: function(){

        // useful to count travelled distance
        this.moves = 0;

        // determining tile size, according to game height and the amount of vertical tiles we want
        this.tileSize = game.height / gameOptions.verticalTiles;

        // amount of placed tiles, useful to tint even/odd tiles with different colors
        var placedTiles = 0;

        // horizontal offset to keep tiles centered in the game
        var offsetX = (game.width - this.tileSize * 3) / 2;

        // array which will contain the holes pool
        this.holePool = [];

        // array which will contain all tiles
        this.tileArray = [];

        // group which will contain all tiles
        this.tileGroup = game.add.group();

        // placing the group to have tiles centered in the game
        this.tileGroup.x = offsetX;

        // creation of a tween which will scroll the terrain down by one tile
        this.tileTween = game.add.tween(this.tileGroup).to({
            y: this.tileSize
        }, 100, Phaser.Easing.Linear.None);

        // since the endless runner thing is a fake, once we moved the terrain down by a tile
        // we reset its position, then move the lowest tiles to the top, giving the idea of an
        // infinite terrain
        this.tileTween.onComplete.add(function(){
            this.tileGroup.y = 0;
            this.tileGroup.forEach(function(child){
                child.y += this.tileSize;
            }, this);

            // bottomIndex is the index of the array of tiles placed at the very bottom of the cancas
            var bottomIndex = this.moves % this.tileArray.length;

            // looping through the bottom row
            for(var i = 0; i < 3; i++){

                // moving the tile to the top
                this.tileArray[bottomIndex][i].tileSprite.y -= (gameOptions.verticalTiles + 1) * this.tileSize;

                // if we have a hole sprite...
                if(this.tileArray[bottomIndex][i].holeSprite != null){

                    // kill it (set its alive, exists and visible properties to false)
                    this.tileArray[bottomIndex][i].holeSprite.kill();

                    // pushing hole sprite in hole pool
                    this.holePool.push(this.tileArray[bottomIndex][i].holeSprite);

                    // removing the hole from tiles array
                    this.tileArray[bottomIndex][i].holeSprite = null;
                }
            }

            // placeHoles method will place holes on a row.
            // arguments are the current array index and the y position
            this.placeHoles(bottomIndex, this.tileArray[bottomIndex][0].tileSprite.y);

            // one more move has been made! Normally score is based on moves
            this.moves ++;

            // checking if the hero is over a hole
            if(this.tileArray[(this.moves + 2) % this.tileArray.length][this.heroColumn].holeSprite != null){
                game.state.start("PlayGame");
            }
        }, this);

        // placing and tinting terrain tiles
        for(var i = 0; i < gameOptions.verticalTiles + 1; i ++){
            this.tileArray[i] = [];
            for(var j = 0; j < 3; j ++){
                var tile = game.add.sprite(j * this.tileSize, game.height - i * this.tileSize, "tile");
                tile.anchor.set(0, 1);
                tile.width = this.tileSize;
                tile.height = this.tileSize;
                tile.tint = gameOptions.tileColors[placedTiles % 2];
                this.tileGroup.add(tile);

                // each item in the tile array has a tile sprite and may have a hole sprite
                this.tileArray[i][j] = {
                    tileSprite: tile,
                    holeSprite: null
                };
                placedTiles ++;
            }

            // we start placing holes from the 6th row on
            if(i &gt; 4){
                this.placeHoles(i, game.height - i * this.tileSize);
            }
        }

        // column numvers ramge from 0 to 2. Hero starts at column 1, the one in the middle
        this.heroColumn = 1;

        // at the moment the hero can move
        this.heroCanMove = true;

        // adding and sizing hero sprite
        this.hero = game.add.sprite(this.tileGroup.x + this.tileSize, game.height - 2 * this.tileSize, "hero");
        this.hero.width = this.tileSize;
        this.hero.height = this.tileSize;
        this.hero.anchor.set(0, 1);

        // tween to move the sprite
        this.heroTween = game.add.tween(this.hero);

        // callback function to be called once the tween is complete
        this.heroTween.onComplete.add(function(){
            this.heroCanMove = true;
            this.hero.x = this.tileGroup.x + this.tileSize * this.heroColumn;
            this.heroWrap.visible = false;
        }, this);

        // and this is the second hero sprite, the one we will use to create the wrap effect
        this.heroWrap = game.add.sprite(this.tileGroup.x + this.tileSize, game.height - 2 * this.tileSize, "hero");
        this.heroWrap.width = this.tileSize;
        this.heroWrap.height = this.tileSize;
        this.heroWrap.anchor.set(0, 1);
        this.heroWrap.visible = false;
        this.heroWrapTween = game.add.tween(this.heroWrap);

        // mask to hide both hero and wrapHero once outside the path of tiles
        var mask = game.add.graphics(this.tileGroup.x, this.tileGroup.y);
        mask.beginFill(0xffffff);
        mask.drawRect(0, 0, this.tileSize * 3, game.height);
        this.hero.mask = mask;
        this.heroWrap.mask = mask;

        // waiting for player input
        game.input.onDown.add(this.moveHero, this);
    },
    placeHoles: function(row, posY){

        // random number to see if we'll place a hole
        if(game.rnd.integerInRange(0, 1) == 0){

            // random hole position
            var holeSpot = game.rnd.integerInRange(0, 2);

            // retrieve the hole from the pool when possible...
            if(this.holePool.length &gt; 0){
                var hole = this.holePool.pop();
                hole.x = holeSpot * this.tileSize;
                hole.y = posY;
                hole.revive();
            }

            // ... or create a new one
            else{
                var hole = game.add.sprite(holeSpot * this.tileSize, posY, "hole");
                hole.anchor.set(0, 1);
                hole.width = this.tileSize;
                hole.height = this.tileSize;
                this.tileGroup.add(hole);
            }

            // adding the hole to tileArrays
            this.tileArray[row][holeSpot].holeSprite = hole;
        }
    },
    moveHero: function(e){

        // can the hero move?
        if(this.heroCanMove){

            // start the tween which moves the terrain
            this.tileTween.start();

            // the hero can't move at the moment
            this.heroCanMove = false;

            // setting hero direction to left if the player clicked/touched the left half of the canvas, or right otherwise
            var direction = e.position.x < game.width / 2 ? -1 : 1;

            // calculating hero next column
            var nextColumn = Phaser.Math.wrap(this.heroColumn + direction, 0, 3);

            // setting hero tween timeline to an empty array to prevent adding waypoints with "to" method
            this.heroTween.timeline = [];

            // new hero destination
            this.heroTween.to({
                x: this.hero.x + this.tileSize * direction
            }, 100, Phaser.Easing.Cubic.InOut, true);

            // this is the case with the wrapping hero coming into play
            if(Math.abs(nextColumn - this.heroColumn) != 1){

                // making it visible
                this.heroWrap.visible = true;

                // placing it outside the final column
                this.heroWrap.x = nextColumn == 0 ? this.tileGroup.x - this.tileSize: this.tileGroup.x + 3 * this.tileSize;

                // resetting tween timeline
                this.heroWrapTween.timeline = [];

                // finally making the wrap hero move
                this.heroWrapTween.to({
                    x: this.heroWrap.x + this.tileSize * direction
                }, 100, Phaser.Easing.Cubic.InOut, true);

            }
            this.heroColumn = nextColumn;
        }
    }
}
Next time I’ll add enemies, coins and energy, meanwhile download the source code.

Get the most popular Phaser 3 book

Through 202 pages, 32 source code examples and an Android Studio project you will learn how to build cross platform HTML5 games and create a complete game along the way.

Get the book

214 GAME PROTOTYPES EXPLAINED WITH SOURCE CODE
// 1+2=3
// 100 rounds
// 10000000
// 2 Cars
// 2048
// A Blocky Christmas
// A Jumping Block
// A Life of Logic
// Angry Birds
// Angry Birds Space
// Artillery
// Astro-PANIC!
// Avoider
// Back to Square One
// Ball Game
// Ball vs Ball
// Ball: Revamped
// Balloon Invasion
// BallPusher
// Ballz
// Bar Balance
// Bejeweled
// Biggification
// Block it
// Blockage
// Bloons
// Boids
// Bombuzal
// Boom Dots
// Bouncing Ball
// Bouncing Ball 2
// Bouncy Light
// BoxHead
// Breakout
// Bricks
// Bubble Chaos
// Bubbles 2
// Card Game
// Castle Ramble
// Chronotron
// Circle Chain
// Circle Path
// Circle Race
// Circular endless runner
// Cirplosion
// CLOCKS - The Game
// Color Hit
// Color Jump
// ColorFill
// Columns
// Concentration
// Crossy Road
// Crush the Castle
// Cube Jump
// CubesOut
// Dash N Blast
// Dashy Panda
// Deflection
// Diamond Digger Saga
// Don't touch the spikes
// Dots
// Down The Mountain
// Drag and Match
// Draw Game
// Drop Wizard
// DROP'd
// Dudeski
// Dungeon Raid
// Educational Game
// Elasticity
// Endless Runner
// Erase Box
// Eskiv
// Farm Heroes Saga
// Filler
// Flappy Bird
// Fling
// Flipping Legend
// Floaty Light
// Fuse Ballz
// GearTaker
// Gem Sweeper
// Globe
// Goat Rider
// Gold Miner
// Grindstone
// GuessNext
// Helicopter
// Hero Emblems
// Hero Slide
// Hexagonal Tiles
// HookPod
// Hop Hop Hop Underwater
// Horizontal Endless Runner
// Hundreds
// Hungry Hero
// Hurry it's Christmas
// InkTd
// Iromeku
// Jet Set Willy
// Jigsaw Game
// Knife Hit
// Knightfall
// Legends of Runeterra
// Lep's World
// Line Rider
// Lumines
// Magick
// MagOrMin
// Mass Attack
// Math Game
// Maze
// Meeblings
// Memdot
// Metro Siberia Underground
// Mike Dangers
// Mikey Hooks
// Nano War
// Nodes
// o:anquan
// One Button Game
// One Tap RPG
// Ononmin
// Pacco
// Perfect Square!
// Perfectionism
// Phyballs
// Pixel Purge
// PixelField
// Planet Revenge
// Plants Vs Zombies
// Platform
// Platform game
// Plus+Plus
// Pocket Snap
// Poker
// Pool
// Pop the Lock
// Pop to Save
// Poux
// Pudi
// Pumpkin Story
// Puppet Bird
// Pyramids of Ra
// qomp
// Quick Switch
// Racing
// Radical
// Rebuild Chile
// Renju
// Rise Above
// Risky Road
// Roguelike
// Roly Poly
// Run Around
// Rush Hour
// SameGame
// SamePhysics
// Save the Totem
// Security
// Serious Scramblers
// Shrink it
// Sling
// Slingy
// Snowflakes
// Sokoban
// Space Checkers
// Space is Key
// Spellfall
// Spinny Gun
// Splitter
// Spring Ninja
// Sproing
// Stabilize!
// Stack
// Stick Hero
// String Avoider
// Stringy
// Sudoku
// Super Mario Bros
// Surfingers
// Survival Horror
// Talesworth Adventure
// Tetris
// The Impossible Line
// The Moops - Combos of Joy
// The Next Arrow
// Threes
// Tic Tac Toe
// Timberman
// Tiny Wings
// Tipsy Tower
// Toony
// Totem Destroyer
// Tower Defense
// Trick Shot
// Tunnelball
// Turn
// Turnellio
// TwinSpin
// vvvvvv
// Warp Shift
// Way of an Idea
// Whack a Creep
// Wheel of Fortune
// Where's my Water
// Wish Upon a Star
// Word Game
// Wordle
// Worms
// Yanga
// Yeah Bunny
// Zhed
// zNumbers