Phaser 3 : ブロック崩しを作ってみる ① Physicsを使う

Phaser 3の物理エンジンを使ってブロック崩しの制作に挑戦してみます。
アクションゲームを作ろうと思ったら自分で物理演算の処理を作るのが一番だと思うのですが、 ブロック崩しなどの簡単なゲームであれば組み込みの物理エンジンで十分作れるはずです。
今回の制作ではスタンダードなブロック崩しではなく、全周囲型のブロック崩しを作ってみようと思います。 ボールは画面四辺で跳ね回り、バーで自機を守りながらブロックを消していくゲームをイメージしています。

GameConfigで使用する物理エンジンを設定

Phaser3にはArcarde、Matter、Impactの三つのPhysicsプラグインが用意されています。
Arcadeは跳ね返りの処理などが味気ない(接触相手の速度や形状にかかわらず単純に接触時の入射ベクトルのみから反射ベクトルを割り出している感じです)ので、今回はmatterを使ってみました。
使用する物理エンジンはGameConfigで設定できます。

var config: GameConfig = {
    parent: 'content',
    type: Phaser.AUTO,
    width: 600,
    height: 600,
    physics: {
        default: 'matter',
        matter: {
            enableSleeping: true
        }
    },
    scene: [MyScene1],
}

ボールが跳ね返るだけのシーンを作る

次は実際に物理オブジェクトを作ってみましょう。
MyScene1でボールとバーを宣言し、画像を読み込みます。
ボールとバーの画像は自作しました。
f:id:Phaser_3:20181121154750p:plain
f:id:Phaser_3:20181121154801p:plain

class MyScene1 extends Phaser.Scene {

    //物理オブジェクトの宣言
    public center: Phaser.Physics.Matter.Image;
    public bar: Phaser.Physics.Matter.Image;
    public ball: Phaser.Physics.Matter.Image;

    constructor() {
        super({ key: 'MyScene1', active: true });
    }

    preload() {
        //画像の読み込み
        this.load.image('ball', 'assets/images/ball.png');
        this.load.image('bar', 'assets/images/bar.png');
    }

createで物理オブジェクトを作成します。
matter.add.imageで物理ボディを持ったオブジェクトを作成することができます。 こちらmatter.jsに変更があったのか、Phaser 3のサンプルコード集には使えないコードがサンプルがいくつもあったので宣言に結構苦労しました。
特に球体物理ボディを設定するsetCircleはサンプルでは引数を指定していませんが、現行のPhaserでは半径とoptionの2つの引数が必要になっています。
matter.add.imageを設定したら、あとは球の反発係数などを設定していきます。

    create() {

        //背景を白に
        this.cameras.main.setBackgroundColor('#ffffff')

        //ワールドの重力を0に設定
        this.matter.world.disableGravity()

        //画面端に当たり判定を設定
        this.matter.world.setBounds(0, 0, this.sys.canvas.width, this.sys.canvas.height);

        //物理オブジェクトを作成
        this.ball = this.matter.add.image(
            this.sys.canvas.width / 2,
            0,
            'ball', null,
            { shape: { type: 'circle', radius: 16 }, ignorePointer: true }
        );

        //ボールの反発係数、初期速度、摩擦係数を設定
        this.ball.setBounce(1.01);
        this.ball.setVelocityY(Phaser.Math.Between(-20, 20));
        this.ball.setVelocityX(Phaser.Math.Between(-20, 20));
        this.ball.setFriction(0, 0, 0)

    }

これでボールが跳ね返るだけの単純なシーンができました。 f:id:Phaser_3:20181121155616g:plain

次はボールを跳ね返すバーをと自機を追加します。
createに以下を追記します。

        //球を画面中央に配置
        this.center = this.matter.add.image(
            this.sys.canvas.width/2,
            this.sys.canvas.height/2,
            'ball', null,
            { shape: { type: 'circle', radius: 16}, ignorePointer: true }
        );
        this.center.setScale(2,2)

        //バーを球の周辺部に配置
        this.bar = this.matter.add.image(
            this.sys.canvas.width / 2,
            this.sys.canvas.height / 2 - 70,
            'bar'
        );
        //バーの反発係数を少し高くするとボールが勢いよく反射する
        this.bar.setBounce(1.2)
        this.bar.setFriction(0, 0, 0)

これでバーと自機が画面に追加されました。 f:id:Phaser_3:20181121160025g:plain

バーを自機の円周上に配置する

全周型ブロック崩しということで、バーに自機のまわりを周回させたいと思います。
updateで自機のangleを取得し、自機の位置と角度に沿った位置にバーを配置します。

    update() {
        //中心の球の向きをボールに合わせる
        this.center.setRotation(Math.atan2(this.ball.y - this.center.y, this.ball.x - this.center.x));

        //バーの位置と角度をボールの向きに合わせる
        this.bar.x = this.center.x + 70 * Math.cos(this.center.rotation);
        this.bar.y = this.center.y + 70 * Math.sin(this.center.rotation);
        this.bar.angle = this.center.angle - 90 ;

        //ボールの速度の取得
        let vel = (this.ball.body as Phaser.Physics.Arcade.Body).velocity

        //速度が落ちてきたら適当に設定する
        if ((Math.abs(vel.x) + Math.abs(vel.y)) < 10) {
            this.ball.setVelocityY(Phaser.Math.Between(-20, 20));
            this.ball.setVelocityX(Phaser.Math.Between(-20, 20));
        }
    }

以上で自機の角度に合わせて動くバーができました。
今回は入力処理を書いていないので、処理の結果をわかりやすくするために自動的に自機をボールに向かせています。
次からは入力処理、自機にあたってしまった場合のダメージ処理、ブロックの配置を行っていきます。 f:id:Phaser_3:20181121161513g:plain

まとめ

  • 使用する物理エンジンはGameConfigで指定する


今回のサンプルコード全文は以下になります。


class MyScene1 extends Phaser.Scene {

    //物理オブジェクトの宣言
    public center: Phaser.Physics.Matter.Image;
    public bar: Phaser.Physics.Matter.Image;
    public ball: Phaser.Physics.Matter.Image;

    constructor() {
        super({ key: 'MyScene1', active: true });
    }

    preload() {
        //画像の読み込み
        this.load.image('ball', 'assets/images/ball.png');
        this.load.image('bar', 'assets/images/bar.png');
    }

    create() {

        //背景を白に
        this.cameras.main.setBackgroundColor('#ffffff')

        //ワールドの重力を0に設定
        this.matter.world.disableGravity()

        //画面端に当たり判定を設定
        this.matter.world.setBounds(0, 0, this.sys.canvas.width, this.sys.canvas.height);

        //物理オブジェクトを作成
        this.ball = this.matter.add.image(
            this.sys.canvas.width/2,
            0,
            'ball', null,
            { shape: { type: 'circle', radius: 16 }, ignorePointer: true }
        );

        //ボールの反発係数、初期速度、摩擦係数を設定
        this.ball.setBounce(1.01);
        this.ball.setVelocityY(Phaser.Math.Between(-20, 20));
        this.ball.setVelocityX(Phaser.Math.Between(-20, 20));
        this.ball.setFriction(0, 0, 0)

        //球を画面中央に配置
        this.center = this.matter.add.image(
            this.sys.canvas.width/2,
            this.sys.canvas.height/2,
            'ball', null,
            { shape: { type: 'circle', radius: 16}, ignorePointer: true }
        );
        this.center.setScale(2,2)

        //バーを球の周辺部に配置
        this.bar = this.matter.add.image(
            this.sys.canvas.width / 2,
            this.sys.canvas.height / 2 - 70,
            'bar'
        );
        //バーの反発係数を少し高くするとボールが勢いよく反射する
        this.bar.setBounce(1.2)
        this.bar.setFriction(0, 0, 0)

    }

    update() {
        //中心の球の向きをボールに合わせる
        this.center.setRotation(Math.atan2(this.ball.y - this.center.y, this.ball.x - this.center.x));

        //バーの位置と角度をボールの向きに合わせる
        this.bar.x = this.center.x + 70 * Math.cos(this.center.rotation);
        this.bar.y = this.center.y + 70 * Math.sin(this.center.rotation);
        this.bar.angle = this.center.angle - 90 ;

        //ボールの速度の取得
        let vel = (this.ball.body as Phaser.Physics.Arcade.Body).velocity

        //速度が落ちてきたら適当に設定する
        if ((Math.abs(vel.x) + Math.abs(vel.y)) < 10) {
            this.ball.setVelocityY(Phaser.Math.Between(-20, 20));
            this.ball.setVelocityX(Phaser.Math.Between(-20, 20));
        }
    }
}


var config: GameConfig = {
    parent: 'content',
    type: Phaser.AUTO,
    width: 600,
    height: 600,

    physics: {
        default: 'matter',
        matter: {
            enableSleeping: true
        }
    },

    scene: [MyScene1],

}

window.onload = () => {
    new Phaser.Game(config);
}