Phaser 3 : ブロック崩しを作ってみる ④ Dataを使ってみる

引き続きブロック崩しを作っていきます。
今回はブロックを配置してみましょう。 gpnotes.hatenablog.jp

ブロックのグループを作る

まずはじめにブロックのオブジェクトを作成しましょう。
ブロック用のスプライトは自作しました。
Phaserの機能で色付けをしようと思っているので、真っ白なスプライトになっています。
(ここにブロック用のスプライトを張り付けているのですが真っ白なので見えないと思います)
f:id:Phaser_3:20181126150219p:plain ブロックオブジェクトは複数追加する予定なのではじめにブロック管理用のグループを作成します。
add.Groupでグループを作成し、group.addでグループにオブジェクトを追加できます。

            //ブロック用のグループを作成
            this.blockGroup = this.add.group();


            for (let i = 0; i < 10; i++) {
                //物理オブジェクトを作成
                let block = this.matter.add.image(
                    this.sys.canvas.width / 2,
                    50,
                    'block', null,
                    { label: "block", ignorePointer: true }
                );

                //スプライトに色付け
                block.setTint(0xff00ff, 0xffff00, 0x0000ff, 0xff0000);

                //ブロックの反発係数などを設定
                block.setBounce(1.2);
                (block.body as Phaser.Physics.Arcade.Body).mass = 10

                //グループにブロックを追加
                this.blockGroup.add(block)
            }

block.setTintでブロックのTintを設定しています。
Phaser2までは確か単純な色変更しかできなかったのですが、3からはグラデーションが使えるようになりました。
上記のまま実行してみると、ブロックをすべて同じ位置に置いているので重なっている部分がはじかれてしまいます。 f:id:Phaser_3:20181126150457p:plain

Phaser.Actionを使ってみる

適当にブロックを配置するためにPhaser3の新機能、Actionを使ってみましょう。

Phaser 3 API Documentation - Namespace: Actions Phaser 3 Examples

Phaser.actionは配列内のオブジェクトに一括で操作を行える関数です。
PlaceOnLineなどの関数を使えばオブジェクトを一直線に配置したりもできます。
今回はPlaceOnCircleを使って円周上にブロックを配置してみます。
まずPhaser.Geomを使い配置対象となる円オブジェクトを作成し、Phaser.Actions.PlaceOnCircleに配置したいオブジェクト群=blockgroupとcircleを渡します。

            //円周上に配置
            var circle = new Phaser.Geom.Circle(400, 300, 260);
            Phaser.Actions.PlaceOnCircle(this.blockGroup.getChildren(), circle);

実行してみるときれいに円状に配置されています。
f:id:Phaser_3:20181126152103p:plain

Dataを使ってブロックのHPを設定する

次はPhaser3からの新機能、Dataを用いてブロックにHPを設定してみます。

DataはPhaserのゲームオブジェクトならなんにでも設定できる

DataはPhaserのゲームオブジェクトに任意の値を持たせられる仕組みです。

Phaser 3 API Documentation - Class: DataManager

gameObject.setData("任意のキー",任意の値)でDataを設定でき、gameObject.getData("任意のキー")でデータを取得できます。
さらにgameObject.on('changedata', function () , context); を用いることでデータ変動時の処理を記述することができるので、今回はDataを用いてブロックとボールの衝突回数が一定の数値を超えた場合に破壊する処理を作ってみます。
先ほどのブロックオブジェクト作成ループの中に

                //Dataの初期設定
                block.setData("count", 0);

を追加します。
次にデータ変動時の処理を追加します。

                //Dataの変更時に実行されるイベントを記述
                block.on('changedata', function (gameObject, key, value, resetValue) {
                    //countが変更された場合
                    if (key === 'count') {
                        //ぶつかった回数が3回以上ならオブジェクトを削除
                        if (value > 3) {
                            block.destroy();
                        }
                    }
                }, this);

この処理はblockの保持しているデータのいずれかに処理があった場合に呼ばれます。
上記の処理はキーがcountの場合、数値が3以上であればブロックを破壊するという処理になっています。

ブロックとボールの衝突時にDataを変動させる

次はブロックとボールの衝突時の処理を書きます。
matterによる衝突の検知はupdateで待ち受けるのではなく、matterが常時実行している衝突判定関数に処理を追加していく形になるようです。
MyScene1のcreateの中に以下の処理を記述します。

            //物理オブジェクトの当たり判定
            this.matter.world.on('collisionstart', function (event) {
                for (var i = 0; i < event.pairs.length; i++) {
                    var bodyA = this.GetCollide(event.pairs[i].bodyA);
                    var bodyB = this.GetCollide(event.pairs[i].bodyB);

                    if ((bodyA.label === "ball" && bodyB.label === "block") || (bodyA.label === "block" && bodyB.label === "ball")) {
                        let body: Phaser.Physics.Matter.Image;
                        if (bodyA.label === "block") {
                            body = bodyA.parent.gameObject;
                        } else {
                            body = bodyB.parent.gameObject;
                        }
                        body.setData("count", body.getData("count") + 1);
                    }
                }
            }, this);

上の処理は物理オブジェクトの衝突があった場合に呼ばれ、その時点で衝突しているオブジェクトの組み合わせすべてをチェックし衝突しているオブジェクトのlabelがballとblockであった場合にブロックとボールの衝突があったと判断し、衝突したブロックのcountを+1します。
(labelはオブジェクトの作成時に設定しています)
上記の処理の補佐のために以下の関数を用意します。
これはPhaserの掲示板にあったものを持ってきたので正直中身がよくわからないのですが、おそらく物理ボディに親子関係があった場合親を返すものだと思います。
今回は親子設定を特にしていないのでいらない気もしますが使っておきます。

        //衝突を処理するための関数
        public GetCollide(body): Phaser.Physics.Arcade.Body {
            if (body.parent === body) {
                return body;
            }
            while (body.parent !== body) {
                body = body.parent;
            }
            return body;

        }


以上でブロックへの衝突判定ができました。
実行結果はこちらになります。
(gifが長くなってしまったので途中からの録画になります)

f:id:Phaser_3:20181126155530g:plain

DataはPhaserのオブジェクトにちょっとした処理を付け足したいときに大変便利だと思います。
またシーンそのものもデータの構造を持っており、UI要素の更新にも使えると思うので、自分は積極的に組み込んでいくつもりです。

まとめ

  • add.Groupでグループを作成し、group.addでグループにオブジェクトを追加

  • Phaser.Actiopnでオブジェクトの配列やグループに一括処理を行える

  • setDataでDataの設定、getDataでDataの取得

ソースは以下にアップロードしました。 http://firestorage.jp/download/60c4bb4f1b251b8d5b65e0af49601f2941f8c38e