Get the full commented source code of

HTML5 Suika Watermelon Game

Talking about Flipping Legend game, Game development, HTML5, Javascript and Phaser.

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 > 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 > 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.

Never miss an update! Subscribe, and I will bother you by email only when a new game or full source code comes out.