Phaser 3 : ブロック崩しを作ってみる ⑥ 演出を作っていく

引き続きブロック崩しを作っていきます。
今回は演出を強化していきます。

gpnotes.hatenablog.jp

接触演出を強化する

開始までボールを待機させる

演出を強化する前に、入力があるまでボールをバーにくっつけておき、任意のタイミングでボールを発射してゲームを開始できるようにします。
MyScene1にwaitフラグを、InputManagerシーンにlaunchフラグをそれぞれ設定します。
InputManagerシーンでスペースかゲームパッドのAの入力があればlaunchフラグをtrueにし、MyScene1のwaitフラグがtrueの時にlaunchフラグがtrueになればボールを発射します。

MyScene1
~~
update() {
~~
    if (this.wait) {
        this.ball.x = this.center.x + 100 * Math.cos(this.center.rotation);
        this.ball.y = this.center.y + 100 * Math.sin(this.center.rotation);
        if (this.controller.launch) {
            this.ball.setVelocityX(this.ball.x - this.center.x);
            this.ball.setVelocityY(this.ball.y - this.center.y);
            this.wait = false;
        }
    }

~~
InputManager
~~
//発射ボタンの入力
public launch: boolean = false;
~~
private keySpace: Phaser.Input.Keyboard.Key;
~~
create() {
~~
    this.keySpace = this.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.SPACE);
~~

update() {
    if (this.gamepad) {
~~
        if (this.gamepad.A) {
            this.launch = true;
        } else {
            this.launch = false;
        }

    } else {  
~~
        //ゲームパッドの発射ボタンを取得
        if (this.keySpace.isDown) {
            this.launch = true;
        } else {
            this.launch = false;
        }
    }
}

以上でゲーム開始時の入力待機処理ができました。
次にPhaser.Actionを用いてブロックに円運動をさせるようにしました。

MyScene1
update(){
~~
    Phaser.Actions.RotateAroundDistance(this.blockGroup.getChildren(), { x: 400, y: 300 }, 0.02,300);
~~
}

Actions.RotateAroundDistanceをupdateで用いると、第二引数の座標を中心に第四引数で指定した半径で円運動させることができます。
次にmatter.onに追記して衝突時の速度に応じてパーティクルの勢いが変わるようにします。

this.matter.world.on('collisionstart', function (event) {
~~
this.particleManager.splash.setSpeed({ min: 0, max: (event.pairs[i].bodyA as Phaser.Physics.Arcade.Body).velocity.x * (event.pairs[i].bodyA as Phaser.Physics.Arcade.Body).velocity.x + (event.pairs[i].bodyA as Phaser.Physics.Arcade.Body).velocity.y * (event.pairs[i].bodyA as Phaser.Physics.Arcade.Body).velocity.y  });
~~

ここまでの実行結果がこちらです。 f:id:Phaser_3:20181128155202g:plain

ボールがくっついているのと、ブロックの回転が確認できます。
自機の回転時にボールとバーが接触した判定ができているのか、パーティクルもエミットされています。

ボールの速度を監視する

ボールが加速しすぎると画面外に飛び出てしまうことがあります。
かといってボールの反発係数を弱めるとボールが止まってしまいブロック崩しが進行できなくなってしまうので、updateでボールの速度を監視して一定の数値以上ならボールの速度を弱めます。
ArcadeのPhysicsにはvelocityの上限値を決めるsetVelocity関数があるのですが、matterにはないようなので、ここの処理は自作するしかないようです。
update間に加速して画面外に飛び出てしまうことがあったので、whileで確実に速度を落とすようにしました。
Phaser.Physics.Arcade.Body.speedで物理オブジェクトの速度をとることができます。
だいたい40以上になると画面外に飛び出してしまうようなので、安全のために30を超えたら減衰させるようにします。

MyScene1
update(){
~~
    //ボールの速度の取得
    let body = this.ball.body as Phaser.Physics.Arcade.Body;
    while (body.speed > 30) {
        this.ball.setVelocity(body.velocity.x * 0.9, body.velocity.y * 0.9);
    }
~~
パーティクルとカメラシェイクを使って自機のダメージ表現

ParticleManagerに新しいエミッタringを追加します。
波紋状のエフェクトにしたいため、自作したリング型のスプライトを拡大するようなエフェクトにしてみました。

ParticleManager
create(){
~~
//エミッターを設定
this.ring = this.add.particles('ring').createEmitter({
    x: 0,
    y: 0,

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

    quantity: 0,
    lifespan: 500,
    tint: 0x000000
});
~~

次にカメラシェイクを追加します。
this.cameras.揺らしたいカメラ.shake(揺らしたいピクセル)でカメラを揺らすことができます。
今回はmainしかカメラを使っていないので、mainを揺らすようにします。

MyScene1
Create(){
~~
this.matter.world.on('collisionstart', function (event) {
~~
//自機がボールかブロックにぶつかった場合
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);
    this.particleManager.ring.explode(1, this.center.x, this.center.y);
    this.cameras.main.shake(50);
}

以上までの処理を追加した結果がこちらです。 f:id:Phaser_3:20181128162006g:plain カメラシェイクはgifではわかりにくいようです。
結構揺れるのですが、揺り戻しの周期と撮影タイミングが一致してしまっているのでしょうか。
リングエフェクトはしっかり確認できます。

ブロック数監視用のグループをつくる

次にブロックが減ってきたら追加する処理を作成します。
まずはブロック数監視のためのallblockグループを追加します。

MyScene1

public allBlock: Phaser.GameObjects.Group;
~~
create(){
~~
this.allBlock = this.add.group();
~~

ブロック作成時にallBlockへの登録も行うようにします。
Group.children.sizeでグループ内の要素を取得することができるので、updateで実行すればブロックの数を監視することができます。
ブロック追加のためのAddBlock関数を作成し、ブロックの数が10以下になったら追加します。

MyScene1
update(){
~~
//画面内のブロックが10以下の場合
if (this.allBlock.children.size < 10) {
    this.AddBlock();
}
~~
}

public AddBlock() {

    let c = this.matter.add.image(
        Phaser.Math.Between(0,this.sys.canvas.width),
        Phaser.Math.Between(0,this.sys.canvas.height),
        'ball', null,
        { label: "block", ignorePointer: true }
    );
    c.setData("count", 0);
    c.setTint(0x000000, 0x000000, 0x000000, 0x000000);

    this.tweens.add({
        targets: c,
        x:Phaser.Math.Between(0, this.sys.canvas.width),
        y:Phaser.Math.Between(0, this.sys.canvas.height),

        duration: 3000,
        ease: 'Power2',
        completeDelay: 3000
    });

    //ブロックグループに追加
    this.allBlock.add(c);

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

    }, this);

    let array:Phaser.Physics.Matter.Image[] =[] 
    for (let i = 0; i < 10; i++) {
        //物理オブジェクトを作成
        let block = this.matter.add.image(
            this.sys.canvas.width / 2,
            50,
            'block1', null,
            { label: "block", ignorePointer: true }
        );
        //スプライトに色付け
        block.setTint(0x000000, 0x000000, 0x1a1a1a, 0x1a1a1a);

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

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

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

        }, this);
        block.setFrictionAir(1);

        //コンストレイントを追加
        this.matter.add.constraint(c, block, 50)

        this.allBlock.add(block);
        array.push(block);
    }
    //円状に配置
    var circle = new Phaser.Geom.Circle(c.x, c.y, 70);
    Phaser.Actions.PlaceOnCircle(array, circle);

}

上記の実行結果がこちらです。
f:id:Phaser_3:20181128163621g:plain 黒い丸を中心としたブロック塊が追加されています。
塊を作りたかったのでconstraintを使いましたが、constraintは相互に位置を決めあうものらしく、動きがぎこちなくなっています。
次からはいったんブロック崩しの作成は中断し、containerの使い方について詳しく学習していこうと思います。

まとめ

  • camera.shakeで画面を揺らすことができる