Phaser 3 : ブロック崩しを作ってみる ⑤ Container、Particleを使ってみる

今回はContainer、Particleを使ってオブジェクトの衝突時の処理を付け足していきます。 https://github.com/samme/phaser3-faq/wiki#how-do-i-add-a-child-sprite

まずはContainerを使って自機の衝突判定回数を視覚化します。

Containerを使ってオブジェクトを関連付ける

ContainerはPhaser3から追加された機能です。
以前はSpriteなどの一部のゲームオブジェクトはaddchildを用いて親子付けすることができたのですが、Phaser3からはaddchildが軒並み廃止されたようです。
(Groupにはまだ残っています)
そのためオブジェクト同士の関係を作るためにはContainerを用いるかゲームオブジェクトを拡張する必要があります。

https://github.com/samme/phaser3-faq/wiki#how-do-i-add-a-child-sprite

厳密な親子付けを行えるのは拡張を行ったときのみで、containerを用いたやり方はコンテナと個々のオブジェクトとの親子関係しか作成できません。
containerの動きと親としてふるまわせたいオブジェクトの動きを同期することで親のように振舞わせることができるといった感じのようです。
今回の接触回数カウンタは自機の動きに追従すればいいだけなのでcontainerで事足りると思います。
こちらのサンプルを参考にして作成します。
Phaser Labに掲載されているサンプルは「Edit」でソースを見ることができます。

https://labs.phaser.io/view.html?src=src\game%20objects\container\matter%20physics%20body%20test.js

物理演算付きのコンテナを作るためには まずはcreate内で自機スプライトを用意した後にcontainerを作ります。

create(){
~~
//球を画面中央に配置
let center = this.add.image(0,0,'ball')
center.setScale(2, 2)

//コンテナを作る
let container = this.add.container(0, 0, center);
~~


次に自機の子となるテキストオブジェクトを作成し、先ほど作成したコンテナにaddします。

//テキストを作る
var text = this.add.text(0, 0, '0');
text.setColor("#fff");
text.setOrigin(0.5);
text.setFontSize(20);
container.add(text);

これでスプライトとテキストオブジェクトの親子付けができましたが、自機は物理オブジェクトとして作成したいので、上のコンテナに物理演算つきのコンテナにする必要があります。
matter.add.gameObjectでゲームオブジェクトを物理オブジェクトとして登録できます。

//コンテナを物理オブジェクト化する
this.center = (this.matter.add.gameObject(container, { shape: { type: 'circle', radius: 32 }, label: "center", ignorePointer: true }) as Phaser.Physics.Matter.Image);

以上で物理演算付きのコンテナを作ることができました。
続いて衝突時のカウント処理を書いていきます。
今回もdataを使います。

this.center.setData("count", 0);

this.center.on('changedata', function (gameObject, key, value, resetValue) {
       //countが変更された場合
       if (key === 'count') {
             text.setText(value);
            }, this);

//物理オブジェクトの当たり判定
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 === "center" && bodyB.label === "block") ||
            (bodyA.label === "block" && bodyB.label === "center") ||
            (bodyA.label === "ball" && bodyB.label === "center") ||
            (bodyA.label === "center" && bodyB.label === "ball") 
        ) {
            this.center.setData("count", this.center.getData("count") + 1);
        }
~~

これで自機に追従して動く衝突回数カウンタができました。 f:id:Phaser_3:20181127161425g:plain できたにはできた…のですが、Containerを用いた方法は直感的に作れず、処理をつくるのが結構大変でした。
containerは引き続きサンプルを引きながら慣れていきたいと思います。

Particleを使い

今回はParticleを使って衝突時のエフェクトを作成したいと思います。
ParticleはPhaser2に比べて格段に強化されており、非常に扱いやすくなっています。

https://labs.phaser.io/index.html?dir=game%20objects/particle%20emitter/&q=


emitterを作る

まずはパーティクル管理用のParticleManagerシーンを作りました。
2x2のドットスプライトを作り読み込みます。

    export class ParticleManager extends Phaser.Scene {

        public emitter: Phaser.GameObjects.Particles.ParticleEmitter;

        constructor() {
            super({ key: 'ParticleManager', active: false });
        }

        preload() {
            //パーティクル用のスプライト
            this.load.image('2x2', 'assets/images/2x2.png');
        }
    }

次にCreateでエミッターを設定します。
各値には{min: , max:}の形で範囲を設定することができます。

//エミッターを設定
this.emitter = this.add.particles('2x2').createEmitter({
    x: 0,
    y: 0,

    //パーティクルのスケール(2から0へ遷移)
    scale: { start: 2, end: 0 },

    //パーティクルの速度(minからmaxの範囲)
    speed: { min: -100, max: 100 },

    //パーティクルの角度(minからmaxの範囲)
    angle: { min: -30, max: 30 },

    //パーティクルの放出数(エミット時に指定するので0を入れておく)
    quantity: 0,

    //パーティクルの寿命
    lifespan: 500,
    tint: 0x000000
});
setEmitZoneでエミッターの形を設定

次にemitter.setEmitZoneでエミッターの形を設定します。
下記コードでは半径10の範囲からエミットされます。
typeにはedgeとrandomがあり、edgeを設定するとemitZoneとして指定した図形の縁から、randomを設定すると指定した図形の範囲内のランダムな点からエミットされます。

//エミッターの形を設定
this.emitter.setEmitZone({
    source: new Phaser.Geom.Circle(0, 0, 10),
    type: 'random',
    quantity: 0
});

これでParticleManagerシーンの作成は終わりです。
次にメインのシーンからParticleManagerシーンを読み込みます。

export class MyScene1 extends Phaser.Scene {
~~
    public particleManager: ParticleManager;
    ~~
   //ParticleManagerシーンを実行
   this.scene.launch("ParticleManager")
   this.particleManager = this.scene.get("ParticleManager") as ParticleManager;
~~
emiter.explodeでパーティクルの単発エミット

あとは衝突時の処理です。
this.matter.world.on内に

//ぶつかった場合にパーティクルをエミット
let angle = Math.atan2((event.pairs[i].bodyA as Phaser.Physics.Arcade.Body).velocity.y, (event.pairs[i].bodyA as Phaser.Physics.Arcade.Body).velocity.x) * 180 / Math.PI + 180
this.particleManager.emitter.setAngle({ min: angle - 30, max:angle+30});
this.particleManager.emitter.explode(50, event.pairs[i].collision.supports[0].x, event.pairs[i].collision.supports[0].y);

を追記します。

let angle = Math.atan2((event.pairs[i].bodyA as Phaser.Physics.Arcade.Body).velocity.y, (event.pairs[i].bodyA as Phaser.Physics.Arcade.Body).velocity.x) * 180 / Math.PI + 180
this.particleManager.emitter.setAngle({ min: angle - 30, max:angle+30});

ではぶつかったオブジェクトのベクトルからエミットする角度を計算し、

this.particleManager.emitter.explode(50, event.pairs[i].collision.supports[0].x, event.pairs[i].collision.supports[0].y);

では物理オブジェクトが衝突したポイントを取得してemitter.explodeでパーティクルをエミットしています。 上記の実行結果がこちらです。 f:id:Phaser_3:20181127164122g:plain 角度計算が間違っている感じもありますが、移動ベクトルに応じてエミット角度が変更されているのがわかると思います。
Phaser2には作成済のエミッターのAngleを変更する方法がなかったように思います。
(少なくとも直感的なものではありませんでした)
なのでこれは非常に大きな変化だと思います。


まとめ
* オブジェクトを関連付けるにはcontainerを使う

  • createEmitterでエミッタを作成、emitter.explodeでエミット


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

http://firestorage.jp/download/0728bcb572d6486128172395e5e6ef92da92db51