スポンサーリンク
当サイトにはプロモーションが含まれています
※広告で得た収益はすべて利用者さんに還元しています。

ゲーム開発室通信 Vol.15 ~弾幕インベーダーの解説その⑦~

ゲーム開発室
スポンサーリンク

インベーダーの弾幕攻撃編

『弾幕インベーダー』の解説シリーズの7回目はインベーダーの弾幕攻撃について見ていきたい思います。インベーダーの攻撃は4種類用意しており、どの段にいるインベーダーかによってどの攻撃をするのかをあらかじめ決めています。弾幕を発射してくるのは最上段の黄色のインベーダーです。一般的な弾幕は放射状に弾が飛んでいくと思いますが、今回の弾幕は星形の弾が、内側と外側の二重の五角形になって回転して広がりながら飛んでいきます。かなり複雑な動きをしていますが、弾幕をどのように制御しているかを見ていきましょう。


『弾幕インベーダーゲーム』で遊びたい方は以下よりアクセスしてください。


ソースコードはGitHub上にアップしています。


弾幕制御の定義一覧

弾幕制御に使用するグローバル変数一覧

配列tamaは各種ミサイルのオブジェクトを共通で管理する二次元配列です。弾幕のオブジェクトはtamaの一次元配列の4番目の要素(tama[3])に格納しています。


弾幕のクラス定義(BombDクラス)

弾幕は自機に向かって発射されます。発射された後は回転しながら広がって飛んでいくようにしています。発射された瞬間は一旦、弾幕が収束していき、その後広がっていくという演出にしています。

弾幕は発射する瞬間の自機の位置によって発射角度を決めています。その後はその方向に向かって回転しながら広がって飛びます。かなり複雑な動きを行っているため、他の攻撃に比べてパラメータ(フィールド)が多くなっています。


弾幕オブジェクトの初期化

弾幕のオブジェクトの初期化はjsファイルが読み込まれた際に行っています。

for (let j = 0; j <= 4; j++) {
  tama[3][j] = new BombD(false, "res/tamah_16x12.png");
}

弾幕の配列は0~4までの5発分の弾幕オブジェクトをあらかじめ用意しています。他のミサイルと同様、この配列の要素数を変えることで同時に発射できる弾幕の数を制御できます。


弾幕制御の関数一覧

弾幕を制御を主としている関数一覧は以下となります。


インベーダーの攻撃の抽選処理(shotchance関数)

他のミサイル同様にビームもランダムに抽選を行っています。この抽選は10msに1回行われ、最上段(引数_iが0)にいるインベーダーを対象としています。

shotchance()
function shotchance(_i, _j){
  let ratio;
  switch (_i) {

    ~中略~

    //最上段のインベーダーのとき
    case 0:
      ratio = Math.random()*8000000; //抽選
      if(ratio < stage*10 + 5000 - tcount*100) {  //ステージ数と生き残っているインベーダーの数で抽選
        for(let k = 0; k < tama[3].length; k++) {
          if (!tama[3][k].alive) {  //ミサイルが未発射のとき
            tama[3][k].alive = true; //ミサイル発射状態にセット
            tama[3][k].x = teki[_i][_j].x + 12 + tx;  //ミサイルのx座標を対象となるインベーダーのx座標にセット
            tama[3][k].y = teki[_i][_j].y + 16;  //ミサイルのy座標を対象となるインベーダーのy座標にセット
            tama[3][k].ra = Math.random()*0.02-0.04;  //回転速度をセット
            tama[3][k].ra_now = 0;
            tama[3][k].range_mv = 0.4 - Math.random()*0.2;  //中心からの移動量をセット
            tama[3][k].range = -20; //円の半径の初期値をセット
            let x_renge = x + 20-tama[3][k].x;  //自機と弾幕とのx座標の距離を取得
            let y_renge = 470 - tama[3][k].y;  //自機と弾幕とのy座標の距離を取得
            let angle = Math.atan2(y_renge, x_renge); //自機と弾幕とのラジアン角の取得
            tama[3][k].mx = Math.cos(angle)*1;  //弾幕のx座標の移動量をセット
            tama[3][k].my = Math.sin(angle)*1;  //弾幕のy座標の移動量をセット
            break;
          }
        }
      }
      break;

      ~中略~
  }
}

弾幕は発射するときに自機に向かうようにしています。その際に自機に向かう角度を算出しないといけないのですが、座標から角度に変換するには「atan2()」という便利なメソッドがあります。atan2()は以下のブログに詳細を記載しておりますので、まだご覧になっていない方は参考にしてください。


弾幕制御のメイン関数

弾幕のメイン制御は以下のような順に各処理を行っています。

  • ローカル変数の初期化(2行目~4行目)
  • 弾幕の移動と回転(8行目~11行目)
  • 弾幕の描画(14行目~27行目)
  • 自機との当たり判定(29行目~33行目)
  • 画面外に出た時の処理(36行目~38行目)
tamamoved()
function tamamoved(){
  let rg = Math.PI*0.2;
  let xx = 0;
  let yy = 0;
  for (let k = 0; k < tama[3].length; k++) {
    if (tama[3][k].alive) { //弾幕が有効のとき
      if (!miss && !clear) { //自機が被弾状態でない、かつ、クリア状態でない
        tama[3][k].x = tama[3][k].x + tama[3][k].mx;  //弾幕のx座標の更新(自機に向かう角度)
        tama[3][k].y = tama[3][k].y + tama[3][k].my;  //弾幕のy座標の更新(自機に向かう角度)
        tama[3][k].ra_now = tama[3][k].ra_now + tama[3][k].ra;  //-0.02~-0.04のいずれかの値で毎回更新(反時計回りにするための移動量)
        tama[3][k].range = tama[3][k].range + tama[3][k].range_mv; //  //-0.2~-0.4のいずれかの値で更新(円の中心からの半径に相当)
      }
      //弾幕の画像をセット
      tamaimg = new Image();
      tamaimg.src = tama[3][k].image;
      //スター表示
      for (let i = 1; i <= 10; i++) {
        if (i%2 == 1) { //奇数番号のとき
          //内側の弾幕の座標をセット
          xx = Math.cos(tama[3][k].ra_now+rg*i) * tama[3][k].range / 2;
          yy = Math.sin(tama[3][k].ra_now+rg*i) * tama[3][k].range / 2;
        } else { //偶数番号のとき
          //外側の弾幕の座標をセット
          xx = Math.cos(tama[3][k].ra_now+rg*i) * tama[3][k].range;
          yy = Math.sin(tama[3][k].ra_now+rg*i) * tama[3][k].range;
        }
        ctx.drawImage(tamaimg, tama[3][k].x+xx, tama[3][k].y+yy, 8, 8);
        //自機との当たり判定
        if (tama[3][k].y+yy >= 460 && tama[3][k].y + yy <= 474) {
          if (tama[3][k].x + xx >= x + 14 && tama[3][k].x + xx <= x + 20) {
            hit();  //被弾処理
          }
        }
      }
      //弾幕が流れていったときの処理
      if (tama[3][k].y > 1000) { //誘導弾のy座標が1000pxを超えたとき
        tama[3][k].alive = false; //弾幕を無効にする
      }
    }
  }
}
弾幕制御の図解

コードだと分かりにくいと思いますので図でも示しておきます。


さいごに

今回はインベーダーの弾幕攻撃の制御についてのコードを見てきました。弾幕は自機に向かって回転しながら広がって飛んでいきます。制御するパラメータが多いためややこしいコードに見えますが、sin、cos、atan2の3つのライブラリを使っているだけです。このコードを応用すれば、例えば放射状に伸びて飛んでいく弾幕を作るのもそれほど難しくはないと思いますので、ぜひこのコードをいじくり倒してください。これでインベーダーの攻撃の制御に関する解説は終わりです。次回はドロップアイテムの制御について見ていく予定です。では次回もお楽しみに!