Exploring Phaser 3 with a Game: Adding Tweens

In previous articles (part 1 and part 2) we created a game with multiple scenes. Now we are going to add some tweens to it to make pegs jump over the holes.

let loadGame = function () {
    let config = {
        type: Phaser.AUTO,
        width: 500,
        height: 500,
        backgroundColor: 0xFF0000,
        scene: TheGame
    }

    let game = new Phaser.Game(config);
};

if (document.readyState === "complete" || (document.readyState !== "loading" && !document.documentElement.doScroll)) {
    loadGame();
} else {
    document.addEventListener("DOMContentLoaded", loadGame);
}

class TheGame extends Phaser.Scene {

    constructor() {
        super("TheGame");
    }

    preload() {
        this.load.spritesheet("pegs", "images/pegs.png", {
            frameWidth: 60,
            frameHeight: 60
        });
    }

    create() {
        this.boardDef = [
            [-1, -1, 1, 1, 1, -1, -1],
            [-1, -1, 1, 1, 1, -1, -1],
            [1, 1, 1, 1, 1, 1, 1],
            [1, 1, 1, 0, 1, 1, 1],
            [1, 1, 1, 1, 1, 1, 1],
            [-1, -1, 1, 1, 1, -1, -1],
            [-1, -1, 1, 1, 1, -1, -1]
        ];

        //  If a Game Object is clicked on, this event is fired.
        //  We can use it to emit the 'clicked' event on the game object itself.
        this.input.on('gameobjectup', function (pointer, gameObject) {
            gameObject.emit('clicked', gameObject);
        }, this);

        // add our sprites
        this.board = [];
        this.selectedPeg = null;
        this.movesCount = 0;

        for (let i = 0, len = this.boardDef.length; i < len; i++) {
            let r = this.boardDef[i];
            let row = [];
            this.board.push(row);
            for (let j = 0, cnt = r.length; j < cnt; j++) {
                let c = r[j];
                if (c >= 0) {
                    let cell = this.add.image(45 + i * 60, 45 + j * 60, "pegs");
                    cell.setFrame(c > 0 ? 1 : 0);
                    cell.setOrigin(0);

                    // enable input events
                    cell.setInteractive();
                    cell.on('clicked', this.clickPeg, this);
                    cell.gridX = i;
                    cell.gridY = j;
                    row.push(cell);
                } else {
                    row.push(null);
                }
            }
        }
        this.movesLabel = this.add.text(0, 0, 'Moves: ' + this.movesCount, { font: '24px Courier', fill: '#000000' });

        this.tempPeg = this.add.sprite(-200, -200, "pegs");
        this.tempPeg.setFrame(1);
        this.tempPeg.setOrigin(0);
    }

    updateMoves(movesCount) {
        this.movesLabel.setText('Moves: ' + movesCount);
    }

    gameOver() {
        this.registry.set('gamedata', { movesCount: this.movesCount, remainingPegs: this.remainingPegs() });
        this.scene.remove('TheGame');
        let gameOver = new GameOver('GameOver');
        this.scene.add('GameOver', gameOver, true);
    }

    isAnyValidMove() {
        let colsCount = this.board.length;
        for (let i = 0; i < colsCount; i++) {
            let col = this.board[i];
            for (let j = 0, endIndex = col.length - 3; j <= endIndex; j++) {
                let c1 = col[j];
                let c2 = col[j + 1];
                let c3 = col[j + 2];

                if (c1 && c2 && c3) {
                    if (c1.frame.name !== 0 && c2.frame.name !== 0 && c3.frame.name === 0) return true;
                    if (c1.frame.name === 0 && c2.frame.name !== 0 && c3.frame.name !== 0) return true;
                }
            }
        }

        var rowsCount = this.board[0].length;
        for (let i = 0, len = colsCount - 3; i <= len; i++) {
            let r1 = this.board[i];
            let r2 = this.board[i + 1];
            let r3 = this.board[i + 2];
            for (let j = 0; j < rowsCount; j++) {
                let c1 = r1[j];
                let c2 = r2[j];
                let c3 = r3[j];

                if (c1 && c2 && c3) {
                    if (c1.frame.name !== 0 && c2.frame.name !== 0 && c3.frame.name === 0) return true;
                    if (c1.frame.name === 0 && c2.frame.name !== 0 && c3.frame.name !== 0) return true;
                }
            }
        }
        return false;
    }

    remainingPegs() {
        var pegs = 0;
        for (let i = 0, len = this.board.length; i < len; i++) {
            let row = this.board[i];
            for (let j = 0, cnt = row.length; j < cnt; j++) {
                let cell = row[j];
                if (cell && cell.frame.name !== 0) {
                    pegs++
                }
            }
        }
        return pegs;
    }

    clickPeg(peg) {

        if (peg.frame.name === 0) {
            // if we have not selected a peg to jump then no need to move any further
            if (!this.selectedPeg)
                return;

            let clickedX = peg.gridX;
            let clickedY = peg.gridY;
            let selectedX = this.selectedPeg.gridX;
            let selectedY = this.selectedPeg.gridY;

            if ((clickedX + 2 === selectedX || clickedX - 2 === selectedX) && clickedY === selectedY) {
                // move horizontal
                let pegToRemove = this.board[(selectedX + clickedX) / 2][clickedY];
                if (pegToRemove.frame.name === 0)
                    return;

                this.updateMoves(++this.movesCount);
                this.removePeg(this.tempPeg, this.selectedPeg, peg, pegToRemove);

                this.selectedPeg.setFrame(0);
                this.selectedPeg = null;

            } else if ((clickedY + 2 === selectedY || clickedY - 2 === selectedY) && clickedX === selectedX) {
                // move vertical
                let pegToRemove = this.board[clickedX][(selectedY + clickedY) / 2];
                if (pegToRemove.frame.name === 0)
                    return;

                this.updateMoves(++this.movesCount);
                this.removePeg(this.tempPeg, this.selectedPeg, peg, pegToRemove);

                this.selectedPeg.setFrame(0);
                this.selectedPeg = null;
            }

        } else {
            if (this.selectedPeg) {
                if (peg === this.selectedPeg) {
                    peg.setFrame(1);
                    this.selectedPeg = null;
                } else {
                    this.selectedPeg.setFrame(1);
                    this.selectedPeg = peg;
                    peg.setFrame(2);
                }
            } else {
                this.selectedPeg = peg;
                peg.setFrame(2);
            }
        }
    }

    removePeg(tempPeg, selectedPeg, targetPeg, pegToRemove) {
        tempPeg.setPosition(selectedPeg.x, selectedPeg.y);
        tempPeg.targetPeg = targetPeg;
        tempPeg.removePeg = pegToRemove;
        tempPeg.visible = true;
        var self = this;
        this.pegTween = this.tweens.add({
            targets: tempPeg,
            x: targetPeg.x,
            y: targetPeg.y,
            duration: 200,
            delay: 50,
            onStart: function (tween) {
                var sprite = tween.targets[0];
                sprite.removePeg.setFrame(0);
            },
            onComplete: function (tween) {
                var sprite = tween.targets[0];
                sprite.targetPeg.setFrame(1);
                sprite.visible = false;
                if (!self.isAnyValidMove()) {
                    let timedEvent = self.time.addEvent({
                        delay: 3000,
                        callbackScope: this,
                        callback: function () {
                            self.gameOver();
                        }
                    });
                }
            }
        });
    }
}

class GameOver extends Phaser.Scene {
 
    constructor() {
        super("GameOver");
    }

    preload() {
        this.load.image("restart", "images/restart.png");
    }
 
    create() {
        var gamedata = this.registry.get('gamedata');

        this.add.text(140, 100, 'Game Over', { font: '42px Courier', fill: '#000000' });
        this.add.text(155, 160, 'Moves: ' + gamedata.movesCount, { font: '42px Courier', fill: '#000000' });
        if (gamedata.remainingPegs > 1) {
            this.remainingPegs = this.add.text(30, 220, 'Remaining Pegs: ' + gamedata.remainingPegs, { font: '42px Courier', fill: '#000000' });
        }
        var btn = this.add.image(175, 300, 'restart');
        btn.setInteractive();
        btn.setOrigin(0);
        btn.on('pointerup', this.startGame, this);

    }

    startGame() {
        this.scene.remove('GameOver');
        let theGame = new TheGame('TheGame');
        this.scene.add('TheGame', theGame, true);
    }
}

Since the main board is just switching the sprite frames, we needed a temporary sprite which we will move between the holes. We added “removePeg” method which starts the tween on temporary sprite. clickPeg method is changed to call “removePeg” and we also removed code to update sprite frames in clickPeg. Those frames will now be changed in the tween onStart and onComplete events. Code to check for “gameover” is also moved to the tween onComplete event since we want to wait for the pegs to move before checking for “gameover” and switching to another scene.

The updated game code now runs as following


Leave A Comment

Your email address will not be published. Required fields are marked *