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


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

敵を作る前に考えること

自機を作って、自機の弾も作って、なんかうまく動いて満足した所で、敵を作ることになるんだけど、ここが一番面倒。ゲームを最後まで作れない人って、ここが面倒で辞めてしまうんじゃないかなあって思ってます。
それぐらい、敵を作るのはゲーム作りの天王山だと思います。

その前にスコアとか残機について考える

敵のことを考えると、スコアや自機の残数についても考えることになるので、まずは表示させておこうと思います。
凝ったゲームだと、数字の画像を用意して表示したりするのだけど、難易度高過ぎなので、文字表示用のラベルって無いのかな?と思ったらやっぱありました。
Label ってのが使えそうです。

もっとその前に、メインシーンをどこからでも参照させる

スコアとか自機の残数はメインシーンに持たせようと思いますが、後々ハイスコア表示とかを考えると、メインシーン以外でもスコアを参照したりするよなあと思いました。
が、phina.js ってこういう時にどうロジックを組むべきか、いろいろなサンプルを見てもよく解りませんでした。
メインシーンってGameApp内で勝手に生成されて勝手に使われているっぽいので。

そこで、グローバルちっくな変数を宣言しておいて、メインシーンのinit処理内でその変数に自分自身を代入すればいいかなと思いました。

複数の敵と複数の弾の当たり判定をどうするのか?

敵を作って動かすのはそうでもないんだけど、複数の敵と複数の弾をどうやって当たり判定させるのかが超難問です。
背景の星では、ランダムに星を生成して、生成された星の方で勝手に動いて勝手に削除されるようにしておけばよかったのですが、当たり判定が必要な敵が、自機の複数の弾の場所を知っていないと当たり判定処理できないです。

よくあるのが、敵とか弾とかを生成した際に配列に格納しておき、オブジェクト削除時に配列からも削除するってな方法ですが、オブジェクト指向っぽく無いなあって思ってました。
いろんなサンプルを見ていると、ここの所をグループに入っているもの全てに対し当たり判定を行っているって処理を見つけ、この方法なら実装しやすいかもと思い、真似することにしました。

必要なグループを考える

自機は画面上に1機だけなんで、グループにする必要も無いかなと思ったのですが、グループの役割が表示順も兼ねることを考えると、グループにしておくべきかなと思いました。
そこで必要なグループを洗い出すと、一番下から順に
・背景用
・自機用
・敵用
・自機の弾用
・爆発のエフェクト用
・敵の弾用
・スコアとか表示用
の7つ必要かなと思いました。
これをメインシーンに設定して参照できるようにしておけば、メインシーンを参照すればどのグループ内のオブジェクトも参照できるはず。

ここまでを実装してみた

メインシーンに、ここまでの考えを追加してみました。
ラベルの位置のかっこいい指定方法が解らなかったので、x座標、y座標に目検で確認しつつ値を固定で設定しました。topとかleftでは想定通りに指定できなかったんだよなあ。

// メインシーンを保持する変数
var mainScene;

// MainScene クラスを定義
phina.define('MainScene', {
    superClass: 'DisplayScene',
    init: function(option) {
        this.superInit(option);
        // メインシーンを変数に格納
        mainScene = this;
        // 背景色を指定
        this.backgroundColor = '#000000';

        // グループを生成する
        this.group = {
            // 背景用
             backgroungGroup: DisplayElement().addChildTo(this),
             // 自機用
             shipGroup: DisplayElement().addChildTo(this),
             // 敵用
             enemyGroup: DisplayElement().addChildTo(this),
             // 自機弾用
             myBulletGroup: DisplayElement().addChildTo(this),
             // 爆発用
             explosionGroup: DisplayElement().addChildTo(this),
             // 敵弾用
             enemyBulletGroup: DisplayElement().addChildTo(this),
             // スコア用
             scoreGroup: DisplayElement().addChildTo(this),          
        };
        // グループの大きさをこのシーンに合わせる
        for (var groupKey in this.group) {
            this.group[groupKey].width = this.width;
            this.group[groupKey].height = this.height;
        }       

        // 星を生成
        (50).times(function() {
            Star(this.group.backgroungGroup, Random.randint(0, this.width), Random.randint(0, this.height));
        }, this);

        // スコア
        this.score = 0;
        // 残機数
        this.remainingShip = 3;

        // スコア表示
        this.scoreLabel =
            Label({
                  text: 'SCORE:'+ ('0000000000' + this.score).slice(-10),
                  fontSize: 15,
                  x: 80,
                  y: 15,
                  fill: "white",
                  stroke: false,
              }).addChildTo(this.group.scoreGroup);
        // 残機数表示
        this.remainingShipLabel =
            Label({
                  text: 'SHIP:' + ('00' + this.remainingShip).slice(-2),
                  fontSize: 15,
                  x: this.width - 50,
                  y: 15,
                  fill: "white",
                  stroke: false,
              }).addChildTo(this.group.scoreGroup);

        // スプライトを作成
        var ship = Ship().addChildTo(this.group.shipGroup);
        // 初期位置
        ship.x = this.gridX.center();
        ship.y = this.gridY.center(5);      


    },
    update: function () {
        // ランダムで星を生成する
        if (Random.randint(1, 3) == 1) {
            Star(this.group.backgroungGroup, Random.randint(0, this.width), 0);
        }
    },
});

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