phina.js が面白そうなのでお勉強中 その9


phina.js が面白そうなのでお勉強中 その8

自機の当たり判定を作ってみたい

前回、自機の弾の当たり判定を作ったので、今回は自機の当たり判定を作ります。

やりたいことは以下です。
・自機が、敵か敵の弾に当たると、自機が爆発する。
・自機がしばらく点滅して動かなくなる。でも敵とか敵の弾は一定時間動き続ける。
・一定時間たったら、敵とか敵の弾とかをリセットして、自機を初期位置に表示させる。
・敵の出現をちょっと巻き戻して途中から始まる。

自機との当たり判定処理

「自機が、敵か敵の弾に当たる」ってことは、敵と敵の弾で処理が共通化できそうです。
自機は動かせなくなるけど、敵とかはしばらく普通に動き続ける という動きをさせるため、自機クラスに shotdownFrameCount って変数を作ります。
これが0の場合は撃墜されていなくて、0では無い場合は撃墜されている、って考えると、撃墜処理が作れそうです。
自機クラスの init 処理に shotdownFrameCount を追加しました。

    init: function() {
        // 親クラスの初期化
        this.superInit("ship", 32, 32);
        // 画像フレームの初期値
        this.frameIndex = 1;

        // 自機の速度
        this.speed = 8;
        // 連打のフラグ
        this.triggerFlag = false;
        // 弾を撃ってからのフレーム数
        this.trigerFrameCount = 0;
        // 撃墜されてからのフレーム数
        this.shotdownFrameCount = 0;        
    },

自機との当たり判定処理 を作ってみました。

// 自機との当たり判定
function collisionShip(obj) {
    // 自機の撃墜フレームが0ではない場合、処理を抜ける
    if (ship.shotdownFrameCount != 0) {
        return;
    }

    // 判定する円
    var objCircle = Circle(obj.x, obj.y, obj.radius);
    // 自機の円
    var shipCircle = Circle(ship.x, ship.y, ship.radius / 4);

    // 当たり判定
    if (Collision.testCircleCircle(objCircle, shipCircle)) {
        // 爆発を表示
        Explosion(ship.x, ship.y).addChildTo(mainScene.group.explosionGroup);
        // 爆発音を出す
        SoundManager.play('bom');
        // 自機の撃墜フレームを 1 にする
        ship.shotdownFrameCount = 1;
    }   
}

パラメータの obj は、敵か敵の弾のSpriteが来ることを想定しています。
これで、敵でも敵の弾でも同じ処理ができます。

最初に、既に自機が撃墜中で点滅している場合は、当たり判定する意味が無いので、処理を抜けるようにしています。

で、敵か敵の弾のスプライトの大きさの円を作ります。
自機の円は、自機の大きさの 1/4 にしておきます。
こうすると、敵とかにかすったぐらいだとやられないので、弾幕いっぱい飛んでくるシューティングにはいいんじゃないだろうか。
1/4 ってのはすんげー適当に考えたので、後々大きさを調整することになると思います。

Collision.testCircleCircle で円の当たり判定を行います。
自機の大きさの円と、敵か敵の弾の円が当たっている場合は、自機の位置に爆発を表示させて、shotdownFrameCount を 1 にして撃墜処理中にします。

自機の update 処理の先頭に、撃墜処理中かどうか判定する処理を追加します。

    update: function(app) {
        // 撃墜されている場合
        if (this.shotdownFrameCount !=0) {
            // 撃墜処理
            this.shootDown();
            return;
        }

        var key = app.keyboard;
        // 上下左右移動・・・・

自機撃墜中処理

で、自機に、撃墜中の処理を追加しました。

    shootDown : function () {
        this.shotdownFrameCount++;
        // 点滅させる
        this.alpha = this.alpha == 1 ? 0 : 1;

        // 100フレームを過ぎた場合、再配置処理に移る
        if (this.shotdownFrameCount > 100) {

            // ゲームオーバーの判定
            // TODO

            // 自機を減らす
            // TODO

            // 再配置処理
            mainScene.relocation();
        }
    },  

撃墜処理中は、shotdownFrameCount を毎回カウントしていきます。
自機の点滅は、自機のスプライトを毎フレーム毎に透明にしたり元に戻したりするようにしました。

で、shotdownFrameCount が100フレームを超えたら、撃墜処理を終了します。
自機の数減らしたりとか、ゲームオーバーになるとかは、この辺に作ることになるのかなと。

再配置処理

自機がやられたら、敵とかをリセットする処理です。
いろいろな group で表示している内容を消すのがメイン。
これは group を保持している MainScene の中に作りました。

    // 再配置処理
    relocation: function () {
        // 敵を削除
        this.group.enemyGroup.children.clear();
        // 敵の弾を削除
        this.group.enemyBulletGroup.children.clear();
        // 自機の弾を削除
        this.group.myBulletGroup.children.clear();

        // 自機の位置を初期化
        ship.x = this.gridX.center();
        ship.y = this.gridY.center(5);      
        ship.shotdownFrameCount = 0;
        ship.alpha = 1;

        // シナリオを1つ戻す
        if (this.scenarioNo > 0) {
            this.scenarioNo--;
        }
        // シナリオのフレーム数を戻す
        this.scenarioFrame = this.scenarioNowFrame - 50;
    },

敵グループ、敵の弾グループ、自機の弾グループ で表示している内容を全て削除します。
最初ここの処理、グループを forEach で回しつつ 表示しているスプライトを remove していく処理にしたんだけど、全部消えてくれなくて思うように動かなかった。
で、調べてみたら、ここが参考になりました。
【phina.js】グループ管理の基本テクニック
group.children.clear();
だけで良かったのか・・・。
良く考えてみたら、forEach で回しつつ forEach の中で配列の要素を削除するってのは、配列とかリストの絶対やっちゃいけないアンチパターンだよな・・・。

で、自機の位置を初期表示位置に戻して、敵のシナリオをちょっと戻す、ってことをやっています。
その際、
shotdownFrameCount を 0にして、撃墜処理中のままにならないようにしています。
同様に、透明のままにならないように、自機の alpha に 1 を入れています。
こういう念のため処理をきちんと入れておくと、バグを減らせるよね。

自機との当たり判定処理の組み込み

ここまでで、自機との当たり判定に必要な処理が完成したので、実際に呼び出してみます。
呼び出すのは、敵を動かす所と、敵の弾を動かす所。

敵共通の処理になるから、敵個別のクラスで行うのではなく、Enemyクラスで行えば良いです。

    update: function(app) {
        // 固さが0以下の場合
        if (this.hardness <= 0) {
            // 爆発音を出す
            SoundManager.play('bom');
            // 点数を加算
            mainScene.score += this.bulletScore;
            mainScene.scoreLabel.text = 'SCORE:'+ ('0000000000' + mainScene.score).slice(-10);
            // 画面から消す
            this.remove();
            delete this;
        }

        // 自機との当たり判定
        collisionShip(this);

        // フレームカウントを進める
        this.frameCount++;          
        // 動かす
        this.move();

        // 画面の外に出たら消す
        if (this.right < 0 || this.left > mainScene.width || this.bottom < 0 || this.top > mainScene.height) {
            this.remove();
            delete this;
        }

update 処理で、
collisionShip(this);
と1行入れているだけです。

同様に、EnemyBullet クラスも

    update: function(app) {
        // 自機との当たり判定
        collisionShip(this);

        // 弾の移動処理
        this.x += this.moveX;
        this.y += this.moveY;

        // 画面の外に出たら消す
        if (this.right < 0 || this.left > mainScene.width || this.bottom < 0 || this.top > mainScene.height) {
            this.remove();
            delete this;
        }
    },

update 処理で、
collisionShip(this);
と1行入れているだけです。

これで完成。

いつも、こういう処理にしたいな、って関数を設計して、最終的にそれを単純に呼び出す瞬間がプログラミングで一番好きです。
呼び出す時に単純であればあるほど、やったぜ!って感じです。

今日の成果

今日の成果をここに上げました。
自機が敵とか敵の弾に当たると、爆発して点滅して表示が初期化されます。
http://hirotyan.my.coocan.jp/phinajs/Shooting/007/index.html

phina.js が面白そうなのでお勉強中 その10