phaser 3: 迷路ゲームを作る② generateTexutureによるテクスチャの動的生成

前回に引き続き探索ゲームを作成します。

gpnotes.hatenablog.jp

generateTexture()を用いて動的に迷路スプライトを用意する

前回使用したやり方では迷路用のgraphicsの中で膨大な量のfillRectが動作しているので、graphicsをそのままゲームに使用するのはパフォーマンス的に望ましくありません。
graphics.generateTextureを用いてスプライト化し負荷を軽減します。
前回は道のほうを描画していたのですが、今回は壁のほうをスプライト化したいのでdrawWall関数を用意します。
サンプル元ではmaze配列の値が1以外のときに道と判断していたので、単純に1のときに壁と判断してみます。

public drawWall(posX, posY) {
    //道ではなく壁を描画する
    for (let i = 0; i < this.mazeHeight; i++) {
        for (let j = 0; j < this.mazeWidth; j++) {
            if (this.maze[i][j] == 1) {
                this.graphics.fillStyle(0xffffff, 1);
                this.graphics.fillRect(j * this.tileSize, i * this.tileSize, this.tileSize, this.tileSize);
            }
        }
    }

}

上記でgraphics内に壁用のrectangle群を描画できました。
drawMazeの代わりにdrawWallを呼び出し、そのあとにgenerateTextureを実行します。
generateTextrure(key, width, height)でkeyをキーとしたwidth * heightのテクスチャが作成され、keyを用いてsprite等のテクスチャに使用することができます。
テクスチャの作成後はパフォーマンス改善のためにgraphicsオブジェクトを破棄します。

preload(){
~~

//迷路を描画
this.drawWall(posX, posY);

//テクスチャの作成
this.graphics.generateTexture("maze");
let sprite = this.add.image(0, 0, "maze")
sprite.setTint(0x05fbff, 0x05fbff, 0x1e00ff, 0x1e00ff)
sprite.setOrigin(0, 0);

//graphicsは破棄
this.graphics.destroy();

これでgraphicsをスプライト化することができました。
tintでグラデーションをかけてみると一枚のスプライトであることがよくわかると思います。
プロシージャル生成の部分はそのままなので読み込みごとに形が変わります。 f:id:Phaser_3:20181204140939g:plain

ループ部分で物理ボディを作ってみる

テクスチャができたので、今度は迷路に物理ボディを設定してみます。
matterにはスプライトのalphaから自動で物理ボディを作ってくれるような機能はないようなので、drawWallのループ部分でボディを設定します。

public drawWall(posX, posY) {
    //道ではなく壁を描画する
    for (let i = 0; i < this.mazeHeight; i++) {
        for (let j = 0; j < this.mazeWidth; j++) {
            if (this.maze[i][j] == 1) {
                this.graphics.fillStyle(0xffffff, 1);
                this.graphics.fillRect(j * this.tileSize, i * this.tileSize, this.tileSize, this.tileSize);
                let rect = this.matter.add.rectangle(j * this.tileSize + this.tileSize / 2, i * this.tileSize + this.tileSize / 2, this.tileSize, this.tileSize, { isStatic: true });
            }
        }
    }
}

update内に画面クリックで物理ボディを追加する処理を書いてみました。

update() {

    if (this.input.activePointer.isDown) {
        let block = this.matter.add.image(this.input.activePointer.x, this.input.activePointer.y, "block");
        block.setTint(0x000000,0x000000,0x000000,0x000000)
        block.setScale(0.2);
    }

}

上記の実行結果がこちらです。
見やすくなるように迷路の道幅は40に設定しました。 f:id:Phaser_3:20181204142049g:plain スプライトの迷路と重なるように物理ボディが設定されているのがわかります。
壁はstaticに設定してあるので、迷路の幅を10にして壁用の物理ボディが増えてしまっても十分な速度がでます。 f:id:Phaser_3:20181204142529g:plain


今回のソースはこちらになります。

namespace MyGame {

    export class MyScene1 extends Phaser.Scene {

        public graphics: Phaser.GameObjects.Graphics;

        //迷路の幅、高さ
        public mazeWidth: number = 0;
        public mazeHeight: number = 0;
        public maze: number[][] = [];

        //迷路の道幅
        public tileSize: number = 10;

        public vertex = "";
        constructor() {
            super({ key: 'MyScene1', active: false });
        }
        preload() {
            this.load.image("block", "assets/images/block1.png")
            //背景を白に
            this.cameras.main.setBackgroundColor("#ffffff");

            //グラフィックスオブジェクトを追加
            this.graphics = this.add.graphics();

            //移動データ用配列
            var moves = [];

            //キャンバスの大きさから迷路の配列サイズを設定
            this.mazeWidth = this.sys.canvas.width / this.tileSize 
            this.mazeHeight = this.sys.canvas.height / this.tileSize 

            for (var i = 0; i < this.mazeHeight; i++) {
                this.maze[i] = [];
                for (var j = 0; j < this.mazeWidth; j++) {
                    this.maze[i][j] = 1;
                }
            }

            //迷路の初期位置を設定
            var posX = 1;
            var posY = 1;

            //初期位置には0(壁ではない)を設定
            this.maze[posX][posY] = 0;
            moves.push(posY + posY * this.mazeWidth);

            while (moves.length !== 0) {
                //moveに次の進行可能箇所を設定する
                var possibleDirections = "";
                if (posX + 2 > 0 && posX + 2 < this.mazeHeight - 1 && this.maze[posX + 2][posY] == 1) {
                    possibleDirections += "S";
                }
                if (posX - 2 > 0 && posX - 2 < this.mazeHeight - 1 && this.maze[posX - 2][posY] == 1) {
                    possibleDirections += "N";
                }
                if (posY - 2 > 0 && posY - 2 < this.mazeWidth - 1 && this.maze[posX][posY - 2] == 1) {
                    possibleDirections += "W";
                }
                if (posY + 2 > 0 && posY + 2 < this.mazeWidth - 1 && this.maze[posX][posY + 2] == 1) {
                    possibleDirections += "E";
                }
                if (possibleDirections) {
                    var move = Phaser.Math.Between(0, possibleDirections.length - 1);
                    switch (possibleDirections[move]) {
                        case "N":
                            this.maze[posX - 2][posY] = 0;
                            this.maze[posX - 1][posY] = 0;
                            posX -= 2;
                            break;
                        case "S":
                            this.maze[posX + 2][posY] = 0;
                            this.maze[posX + 1][posY] = 0;
                            posX += 2;
                            break;
                        case "W":
                            this.maze[posX][posY - 2] = 0;
                            this.maze[posX][posY - 1] = 0;
                            posY -= 2;
                            break;
                        case "E":
                            this.maze[posX][posY + 2] = 0;
                            this.maze[posX][posY + 1] = 0;
                            posY += 2;
                            break;
                    }
                    moves.push(posY + posX * this.mazeWidth);
                }
                else {
                    var back = moves.pop();
                    posX = Math.floor(back / this.mazeWidth);
                    posY = back % this.mazeWidth;
                }

            }

            //迷路を描画
            this.drawWall(posX, posY);

            //テクスチャの作成
            this.graphics.generateTexture("maze");
            let sprite = this.add.image(0, 0, "maze");
            sprite.setOrigin(0, 0);
            sprite.setTint(0x000000, 0x000000, 0x000000, 0x000000)

            //graphicsは破棄
            this.graphics.destroy();
        }

        create() {


        }

        update() {

            if (this.input.activePointer.isDown) {
                let block = this.matter.add.image(this.input.activePointer.x, this.input.activePointer.y, "block");
                block.setTint(0x000000,0x000000,0x000000,0x000000)
                block.setScale(0.2);
            }

        }

        public drawMaze(posX, posY) {
            //道を表示
            this.graphics.fillStyle(0x000000, 1);

            for (let i = 0; i < this.mazeHeight; i++) {
                for (let j = 0; j < this.mazeWidth; j++) {
                    if (this.maze[i][j] !== 1) {
                        let rect = this.graphics.fillRect(j * this.tileSize, i * this.tileSize, this.tileSize, this.tileSize);
                    }
                }
            }

        }

        public drawWall(posX, posY) {
            //道ではなく壁を描画する
            for (let i = 0; i < this.mazeHeight; i++) {
                for (let j = 0; j < this.mazeWidth; j++) {
                    if (this.maze[i][j] == 1) {
                        this.graphics.fillStyle(0xffffff, 1);
                        this.graphics.fillRect(j * this.tileSize, i * this.tileSize, this.tileSize, this.tileSize);
                        let rect = this.matter.add.rectangle(j * this.tileSize + this.tileSize / 2, i * this.tileSize + this.tileSize / 2, this.tileSize, this.tileSize, { isStatic: true });
                    }
                }
            }

        }
    }


}