HTML5 prototype of “Bricks” hyper casual game with Phaser and Arcade physics – Step 2: adding score

Read all posts about "" game

If you enjoyed the first step in Bricks series, you will be happy to know we are going to add a scoring system to the game.

In the original game, you get one point for each row you break, and the score is shown in the middle of the moving brick.

Just like in this example:

Tap the proper column to make top brick match the color of the brick on the line and destroy it.

Although is easy to figure out it’s just a matter of placing a text with some effect and move it to player position at each frame, since each number has a different width – at least with the font I used – to keep the score centered I determined text bounding box and moved it accordingly.

getBounds method you probably are already using to get sprite and images bounding boxes, works with texts too.

And this is the completely commented source code:

let game;
let gameOptions = {

    // number of columns
    columns: 5,

    // number of rows, must be high enough to allow object pooling
    rows: 20,

    // tile speed in pixels per second
    tileSpeed: 200
}
window.onload = function() {
    let gameConfig = {
        type: Phaser.AUTO,
        scale: {
            mode: Phaser.Scale.FIT,
            autoCenter: Phaser.Scale.CENTER_BOTH,
            parent: "thegame",
            width: 750,
            height: 1334
        },
        physics: {
            default: "arcade"
        },
       scene: playGame
    }
    game = new Phaser.Game(gameConfig);
}
class playGame extends Phaser.Scene{
    constructor(){
        super("PlayGame");
    }
    preload(){
        this.load.spritesheet("tiles", "tiles.png", {
            frameWidth: 100,
            frameHeight: 100
        });
    }
    create(){

        // physics group which manages all tiles in game
        this.tileGroup = this.physics.add.group()

        // determining tile size according to game width and columns
        this.tileSize = game.config.width / gameOptions.columns;

        // time to add tiles to the game
        for(let i = 0; i < gameOptions.rows; i++){

            // build an array with integers between 0 and gameOptions.columns - 1: [0, 1, 2, ..., gameOptions.columns - 1]
            let values = Phaser.Utils.Array.NumberArray(0, gameOptions.columns - 1);

            // then we shuffle the array
            Phaser.Utils.Array.Shuffle(values);

            // save middle column color of first row
            if(i == 0){
                var middleColor = values[Math.floor(gameOptions.columns / 2)];
            }

            // now we place the tiles, row by row
            for(let j = 0; j < gameOptions.columns; j++){

                // add a tile. Tile frame is set according to "values" shuffled array
                let tile = this.tileGroup.create(j * this.tileSize, i * this.tileSize + game.config.height / 4 * 3, "tiles", values[j]);

                // call adjustTile method to adjust tile origin and display size
                this.adjustTile(tile);
            }
        }

        // let's build once again an array with integers between 0 and gameOptions.columns - 1
        let values = Phaser.Utils.Array.NumberArray(0, gameOptions.columns - 1);

        // remove the item at "middlecolor" position because we don't want it to be randomly selected
        values.splice(middleColor, 1);

        // add the player to the game. Player color is picked amoung "values" array items, which does not contain anymore "middlecolor" value
        this.player = this.tileGroup.create(this.tileSize * Math.floor(gameOptions.columns / 2), game.config.height / 4 * 3 - this.tileSize, "tiles", Phaser.Utils.Array.GetRandom(values));

        // adjust player origin and display size
        this.adjustTile(this.player);

        // the score
        this.score = 0;

        // add score text
        this.scoreText = this.add.text(0, 0, "0", {
            fontFamily: "Arial Black",
            fontSize: this.tileSize / 3,
            color: "#ffffff"
        })

        // set a stroke to score text
        this.scoreText.setStroke("#000000", this.tileSize / 6);

        // method to adjust score position
        this.adjustScorePosition();

        // move entire tile group up by gameOptions.tileSpeed pixels / second
        this.tileGroup.setVelocityY(-gameOptions.tileSpeed);

        // can the player move? Yes, at the moment
        this.canMove = true;

        // did we match any tile? No, at the moment
        this.matched = false;

        // wait for player input
        this.input.on("pointerdown", this.moveTile, this);
    }

    // method to set tile origin and display size
    adjustTile(sprite){

        // set origin at the top left corner
        sprite.setOrigin(0);

        // set display width and height to "tileSize" pixels
        sprite.displayWidth = this.tileSize;
        sprite.displayHeight = this.tileSize;
    }

    // method to adjust score position
    adjustScorePosition(){

        // adjust score position according to its bounding box and player position
        this.scoreText.x = this.player.x + (this.tileSize - this.scoreText.getBounds().width) / 2;
        this.scoreText.y = this.player.y + (this.tileSize - this.scoreText.getBounds().height) / 2;
    }

    // method to move player tile
    moveTile(pointer){

        // if we can move...
        if(this.canMove){

            // determine column according to input coordinate and tile size
            let column = Math.floor(pointer.x / this.tileSize);

            // get the ditance from current player tile and destination
            let distance = Math.floor(Math.abs(column * this.tileSize - this.player.x) / this.tileSize);

            // did we actually move?
            if(distance > 0){

                // we can't move anymore
                this.canMove = false;

                // tween the player to destination tile
                this.tweens.add({
                    targets: [this.player],
                    x: column * this.tileSize,
                    duration: distance * 30,
                    callbackScope: this,
                    onComplete: function(){

                        // at the end of the tween, check for tile match
                        this.checkMatch();
                    }
                });
            }
        }
    }

    // method to check tile matches
    checkMatch(){

        // get tile below player tile
        let tileBelow = this.physics.overlapRect(this.player.x + this.tileSize / 2, this.player.y + this.tileSize * 1.5, 1, 1);

        // "tileBelow" is an array so we have to compare the first - and only - item frame with player frame. Are the two frames the same?
        if(tileBelow[0].gameObject.frame.name == this.player.frame.name){

            // we have a match
            this.matched = true;

            // check the whole row below player tile
            let rowBelow = this.physics.overlapRect(0, this.player.y + this.tileSize * 1.5, game.config.width, 1);

            // tween down the player
            this.tweens.add({
                targets: [this.player],
                y: tileBelow[0].gameObject.y,
                duration: 100,
                callbackScope: this,
                onUpdate: function(tween, target){

                    // at each update, we have to adjust player position because tiles continue moving up
                    this.player.y = Math.min(this.player.y, tileBelow[0].gameObject.y)
                },

                // at the end of the tween, we have to move the row at the bottom, to reuse sprites
                onComplete: function(){

                    // increment score
                    this.score ++;

                    // update score text
                    this.scoreText.setText(this.score)

                    // the good old array with all integers from zero to gameOptions.columns - 1
                    let values = Phaser.Utils.Array.NumberArray(0, gameOptions.columns - 1);

                    // let's shuffle the array
                    Phaser.Utils.Array.Shuffle(values);

                    // place all tiles below the lowest row
                    for(let i = 0; i < gameOptions.columns; i++){
                        rowBelow[i].gameObject.setFrame(values[i]);
                        rowBelow[i].gameObject.y += this.tileSize * gameOptions.rows;
                    }

                    // check for matches again, there could be a combo
                    this.checkMatch();
                }
            });
        }

        // what to do when player moved but there isn't any match?
        else{

            // we can move again
            this.canMove = true;

            // is there a previous match? Did we come here from a previous match?
            if(this.matched){

                // no more matches
                this.matched = false;

                // get the tile below the player
                let tileBelow = this.physics.overlapRect(this.player.x + this.tileSize / 2, this.player.y + this.tileSize * 1.5, 1, 1);

                // the good old array with all integers from zero to gameOptions.columns - 1
                let values = Phaser.Utils.Array.NumberArray(0, gameOptions.columns - 1);

                // remove the item at "frame" value of tile below the player pbecause we don't want it to be randomly selected
                values.splice(tileBelow[0].gameObject.frame.name, 1);

                // change player frame
                this.player.setFrame(Phaser.Utils.Array.GetRandom(values));
            }
        }
    }

    // method to be executed at each frame
    update(){

        // we need to adjust score position at each frame
        this.adjustScorePosition();

        // if the player touches the top of the screen...
        if(this.player.y < 0){

            // gmae over man, restart the game
            this.scene.start("PlayGame");
        }
    }
}

And we are done with the score. Which feature would you add now? Download the source code and give me feedback.

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