Add borders in Puzzle games in Phaser 3

In puzzle games where we need to add borders to our puzzles and mark various zones, we can use Phase Path to draw those lines. For this example we are going to draw something like the image below

Let us start by creating our html file and including Phaser 3 js and our game js in it.

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title></title>
    <meta http-equiv="Content-type" content="text/html; charset=utf-8">
    <meta name="viewport" content="width=device-width, minimum-scale=1, initial-scale=1, user-scalable=no">
    <script src="js/phaser.min.js"></script>
    <script src="js/game.js"></script>
</head>
<body>
	<div id="mygame"></div>
</body>
</html>

Now our JS code for would be

const BLANK= 0;

let launchGame = function () {
    let config = {
        type: Phaser.AUTO,
        scale: {
            parent: 'mygame',
            mode: Phaser.Scale.FIT,
            autoCenter: Phaser.Scale.CENTER_BOTH,
            width: window.innerWidth * window.devicePixelRatio,
            height: window.innerHeight * window.devicePixelRatio
        },
        autoResize: true,
        scene: [TheGame]
    }

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

class TheGame extends Phaser.Scene {

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

    preload() {
        this.load.image("blank", "images/blank.png");
    }

    init(config) {
    }

    create() {
        this.cameras.main.setBackgroundColor('#004050');

		this.size = 9;
		this.board = [
			[1, 1, 1, 1, 1, 1, 2, 2, 2],
			[1, 3, 1, 3, 3, 3, 4, 2, 2],
			[1, 3, 3, 3, 3, 5, 4, 4, 2],
			[6, 6, 6, 3, 5, 5, 4, 2, 2],
			[7, 6, 6, 5, 5, 5, 4, 4, 2],
			[7, 7, 6, 5, 5, 8, 4, 4, 4],
			[7, 6, 6, 5, 8, 8, 8, 8, 9],
			[7, 7, 6, 8, 8, 8, 9, 8, 9],
			[7, 7, 7, 9, 9, 9, 9, 9, 9]
		];
		
        this.gameArray = [];
        for (let i = 0; i < this.size; i++) {
            this.gameArray[i] = [];
            for (let j = 0; j < this.size; j++) {
                let v = this.board[i][j] ? this.board[i][j] : BLANK;
                let tile = this.add.image(-900, -900, "blank").setOrigin(0);
                this.gameArray[i][j] = {
                    sprite: tile
                };
            }
        }

        this.lines = this.add.graphics();
        this.graphics = this.add.graphics();

        this.scale.on('resize', this.resize, this);
        this.positionControls(this.cameras.main.width, this.cameras.main.height);
    }

    getPosition(i, j) {
        return { x: this.boardLeft + j * this.tileSize, y: this.boardTop + i * this.tileSize };
    }

    positionControls(width, height) {

        let availableWidth = width, tileSize, scale, availableButtonSize;
        tileSize = Math.min(availableWidth / (this.size + 1), height / (this.size + 3));
        let tile = this.gameArray[0][0].sprite;
        scale = scaleManager.scaleSprite(tile, tileSize, tileSize, 0, 1, true);

        this.boardLeft = (width - tileSize * this.size) / 2;
        this.boardTop = height * 0.5 - this.size * tileSize * 0.5;
        this.tileSize = tileSize;

        for (let x = 0; x < this.size; x++) {
            for (let y = 0; y < this.size; y++) {
                let pos = this.getPosition(x, y);
                scaleManager.scaleSpriteTo(this.gameArray[x][y].sprite, scale);
                this.gameArray[x][y].sprite.setPosition(pos.x, pos.y);
            }
        }

        this.lines.clear();
        this.lines.lineStyle(2, 0xC5C6C6, 1);

        this.graphics.clear();
        this.graphics.lineStyle(this.size > 10 ? 4 : 5, 0xE10C30, 1);

        let path, lines;
        for (let x = 0; x < this.size; x++) {
            for (let y = 0; y < this.size; y++) {

                let directions = { 'up': [-1, 0], 'down': [1, 0], 'left': [0, -1], 'right': [0, 1] }; // left, right, bottom, top

                for (let key in directions) {
                    if (directions.hasOwnProperty(key)) {
                        let d = directions[key];
                        let dx = d[0];
                        let dy = d[1];
                        if (x + dx >= 0 && x + dx < this.size) {
                            if (y + dy >= 0 && y + dy < this.size) {
                                if (this.board[x + dx][y + dy] !== this.board[x][y]) {
                                    switch (key) {
                                        case 'left':
                                            path = new Phaser.Curves.Path(this.gameArray[x][y].sprite.x, this.gameArray[x][y].sprite.y);
                                            path.lineTo(this.gameArray[x][y].sprite.x, this.gameArray[x][y].sprite.y + tileSize);
                                            break;
                                        case 'right':
                                            path = new Phaser.Curves.Path(this.gameArray[x + dx][y + dy].sprite.x, this.gameArray[x + dx][y + dy].sprite.y);
                                            path.lineTo(this.gameArray[x + dx][y + dy].sprite.x, this.gameArray[x + dx][y + dy].sprite.y + tileSize);
                                            break;
                                        case 'down':
                                            path = new Phaser.Curves.Path(this.gameArray[x + dx][y + dy].sprite.x, this.gameArray[x + dx][y + dy].sprite.y);
                                            path.lineTo(this.gameArray[x + dx][y + dy].sprite.x + tileSize, this.gameArray[x + dx][y + dy].sprite.y);
                                            break;
                                        case 'up':
                                            path = new Phaser.Curves.Path(this.gameArray[x][y].sprite.x, this.gameArray[x][y].sprite.y);
                                            path.lineTo(this.gameArray[x][y].sprite.x + tileSize, this.gameArray[x][y].sprite.y);
                                            break;
                                    }
                                    path.draw(this.graphics);
                                } else {
                                    switch (key) {
                                        case 'left':
                                            lines = new Phaser.Curves.Path(this.gameArray[x][y].sprite.x, this.gameArray[x][y].sprite.y);
                                            lines.lineTo(this.gameArray[x][y].sprite.x, this.gameArray[x][y].sprite.y + tileSize);
                                            break;
                                        case 'right':
                                            lines = new Phaser.Curves.Path(this.gameArray[x + dx][y + dy].sprite.x, this.gameArray[x + dx][y + dy].sprite.y);
                                            lines.lineTo(this.gameArray[x + dx][y + dy].sprite.x, this.gameArray[x + dx][y + dy].sprite.y + tileSize);
                                            break;
                                        case 'down':
                                            lines = new Phaser.Curves.Path(this.gameArray[x + dx][y + dy].sprite.x, this.gameArray[x + dx][y + dy].sprite.y);
                                            lines.lineTo(this.gameArray[x + dx][y + dy].sprite.x + tileSize, this.gameArray[x + dx][y + dy].sprite.y);
                                            break;
                                        case 'up':
                                            lines = new Phaser.Curves.Path(this.gameArray[x][y].sprite.x, this.gameArray[x][y].sprite.y);
                                            lines.lineTo(this.gameArray[x][y].sprite.x + tileSize, this.gameArray[x][y].sprite.y);
                                            break;
                                    }
                                    lines.draw(this.lines);
                                }
                            }
                        }
                    }
                }
            }
        }

        // top
        path = new Phaser.Curves.Path(this.gameArray[0][0].sprite.x, this.gameArray[0][0].sprite.y);
        path.lineTo(this.gameArray[0][0].sprite.x + this.size * tileSize, this.gameArray[0][0].sprite.y);
        path.draw(this.graphics);

        // bottom
        path = new Phaser.Curves.Path(this.gameArray[0][0].sprite.x, this.gameArray[0][0].sprite.y + this.size * tileSize);
        path.lineTo(this.gameArray[0][0].sprite.x + this.size * tileSize, this.gameArray[this.size - 1][0].sprite.y + tileSize);
        path.draw(this.graphics);

        // left
        path = new Phaser.Curves.Path(this.gameArray[0][0].sprite.x, this.gameArray[0][0].sprite.y);
        path.lineTo(this.gameArray[0][0].sprite.x, this.gameArray[this.size - 1][0].sprite.y + tileSize);
        path.draw(this.graphics);

        // right
        path = new Phaser.Curves.Path(this.gameArray[0][0].sprite.x + this.size * tileSize, this.gameArray[0][0].sprite.y);
        path.lineTo(this.gameArray[0][0].sprite.x + this.size * tileSize, this.gameArray[this.size - 1][0].sprite.y + tileSize);
        path.draw(this.graphics);
    }

    resize(gameSize, baseSize, displaySize, resolution) {
        let width = gameSize.width;
        let height = gameSize.height;

        this.cameras.resize(width, height);
        this.positionControls(width, height);
    }
}


function LocalScaleManager() {
}

LocalScaleManager.prototype = {
    scaleSprite: function (sprite, availableSpaceWidth, availableSpaceHeight, padding, scaleMultiplier, isFullScale) {
        let scale = this.getSpriteScale(sprite.frame.width, sprite.frame.height, availableSpaceWidth, availableSpaceHeight, padding, isFullScale);
        sprite.setScale(scale * scaleMultiplier);
        return scale;
    },
    scaleSpriteTo: function (sprite, scale) {
        sprite.setScale(scale);
    },
    getSpriteScale: function (spriteWidth, spriteHeight, availableSpaceWidth, availableSpaceHeight, minPadding, isFullScale) {
        let ratio = 1;
        let currentDevicePixelRatio = window.devicePixelRatio;
        // Sprite needs to fit in either width or height
        let widthRatio = (spriteWidth * currentDevicePixelRatio + 2 * minPadding) / availableSpaceWidth;
        let heightRatio = (spriteHeight * currentDevicePixelRatio + 2 * minPadding) / availableSpaceHeight;
        if (widthRatio > 1 || heightRatio > 1) {
            ratio = 1 / Math.max(widthRatio, heightRatio);
        } else {
            if (isFullScale)
                ratio = 1 / Math.max(widthRatio, heightRatio);
        }
        return ratio * currentDevicePixelRatio;
    }
};

let scaleManager = new LocalScaleManager;

launchGame();

Try running this locally in your web server and you will see the result as shown in the screenshot.


Leave A Comment

Your email address will not be published.