シューティングゲームでよく使う数式をまとめてみた 

数学に強くなって、

綺麗なプログラムを組みたい・・・!

はじめてシューティングゲーム作るとき、弾の挙動ってどう動かせばよいか悩まれる方も多いと思います。

今回は、簡単な弾の挙動から始めて、最終的には「ターゲットの方向へ撃つ処理」をどのように計算するのかを説明します。

使用言語:javascript

※ゲームライブラリとして、enchant.jsというライブラリを使っています。2022年現在、使う人がほぼいない化石ライブラリとなっているのですが、計算式だけ見てもらえれば幸いです。

自分の使用予定の言語が違う場合でも、わかるように心がけているので、参考になれば幸いです。

ページ下では、実際に動作しているプログラムとソースコードが閲覧できるようになっているので、理解の一助になれば幸いです。

固定方向に撃ち続ける

See the Pen 上に向かって球を打つ処理 by ほっとフクロウ(作っちゃうおじさん) (@hothukurou) on CodePen.

ソースコードを大きく見たい方は右上のCODEPENと書かれている部分をクリックしてください。CODEPENページにジャンプして編集することができます。

また、左側のソースコードの数値を書き換えて、右下の「Rerun」ボタンを押すことで、弾の挙動を自由に変更することができます。お試しください。

・弾生成ソースコード(弾の挙動はこの関数内に書かれているので抜粋)

 //TIPS:引数のspriteを発射元にして赤四角の弾を作成して返す関数 
 function createBullet(sprite) {
     const bullet = new Sprite(8, 8); //width:8px,height:8pxのSpriteを設置
     bullet.moveTo(sprite.x, sprite.y); //発射元の座標に弾を合わせる 
     bullet.backgroundColor = "#ff0000"; //赤に設定 
     bullet.onenterframe = function() { //毎フレーム毎に実行する関数 
         this.y -= shotSpeed; //真上に向けて移動する
         if (bullet.x = -30 || bullet.x = 430 || bullet.y = -30 || bullet.y = 430) { //画面外か判定
             this.parentNode.removeChild(this); //画面外なら自身を削除する
         }
     }
     return bullet; //上で作成したspriteを返す 
 }

onenterframeは毎フレーム毎に行われる処理を記述するイベント処理です。

(今回は30fpsなので、1/30[s]毎に中身の関数が処理されます。)

createBullet()関数内で生成したbulletが毎フレーム毎にthis.y-=shotSpeedされています。

これにより、毎フレームにshotSpeed分だけ上方向に進むことができます。

bulletのy座標が毎フレームでマイナスされるので、上に進んでいくということですね。

プログラムを本格的に理解したい人向けに、詳細説明を書きました。

興味のある方は下の欄をクリックしてください。流し見したい方は流してください。

なんとなくわかるゲームプログラムの知識

座標系

左上が原点で、右方向がX方向、下方向がY方向になっています。

「Y軸は上向いてないのか!」と思った方もいらっしゃるとは思いますが、

画像処理ではこの座標系がよく使われています。

enchant.js の知識

下図にenchant.js特有の概念を説明します。今回enchant.jsは画像表示目的で使っています。

今回は弾の挙動を知ることが目的なので、ざっくりとした説明にとどめます。

この図を何となく見ておけば、後でソースコードを見るときに理解の一助になるかと思います。

移動角度を設定して撃ち続ける

See the Pen 角度を決めて球を打つ処理 by ほっとフクロウ(作っちゃうおじさん) (@hothukurou) on CodePen.

・弾生成ソースコード

        //TIPS:引数のspriteを発射元にして赤四角の弾を作成して返す関数
        function createBullet(sprite) {
            const bullet = new Sprite(8, 8);   //width:8px,height:8pxのSpriteを設置
            bullet.moveTo(sprite.x, sprite.y);  //発射元の座標に弾を合わせる
            bullet.backgroundColor = "#ff0000"; //赤に設定
            bullet.rad = (sprite.time / shotInterval) * rotateInterval;    //現在の時間から角度を生成
            bullet.onenterframe = function () {
                this.x += Math.cos(this.rad) * shotSpeed;    //移動角度のcosがX軸移動方向
                this.y += Math.sin(this.rad) * shotSpeed;    //移動角度のsinがY軸移動方向
                if (bullet.x = -30 || bullet.x = 430 || bullet.y = -30 || bullet.y = 430) { //画面外か判定
                    this.parentNode.removeChild(this);  //画面外なら自身を削除する
                }
            }
            return bullet; //上で作成したspriteを返す
        }

bullet.radに移動角度を格納し、cos,sinによりx,yの移動量を決定しています。

bullet.rad=(sprite.time/shotInterval)*rotateInterval;

でbulletの移動角度を設定しています。

sprite.timeは毎フレーム毎に加算されます。shotIntervalは何フレーム毎に弾を生成するかを決めています。

sprite.time/shotIntervalで発射された弾数が計算できるので、この値にrotateIntervalを掛け算することで、発射角度を弾数ごとに変更しています。

移動角度this.radに対して

Math.cos(this.rad)でX軸の移動量の単位ベクトル

Math.sin(this.rad)でY軸の移動量の単位ベクトル

を求めることができます。

単位ベクトルとは「長さが1のベクトル」のことです。

つまり、現在の座標値にこの単位ベクトルを足すと移動角度方向に1pxずれるということです。

これがとても都合の良い話で、この単位ベクトルに移動量shotSpeedを掛け算することで

shotSpeed[px]分だけ移動させることができます。

this.x+=Math.cos(this.rad)*shotSpeed; //移動角度のcosがX軸移動方向
this.y+=Math.sin(this.rad)*shotSpeed; //移動角度のsinがY軸移動方向

shotSpeedの値を変更すれば、移動量を自由に調節できるので、とても便利です。

いよいよ三角関数の本領発揮です!

クリックした位置に設置されるターゲット方向に撃ち続ける

See the Pen クリックした位置に向かって打つ処理 by ほっとフクロウ(作っちゃうおじさん) (@hothukurou) on CodePen.

・弾生成ソースコード

        //TIPS:引数のspriteを発射元にして赤四角の弾を作成して返す関数
        function createBullet(sprite) {
            const bullet = new Sprite(8, 8);   //width:8px,height:8pxのSpriteを設置
            bullet.moveTo(sprite.x, sprite.y);  //発射元の座標に弾を合わせる
            bullet.backgroundColor = "#ff0000"; //赤に設定
            //target方向を移動角度とするため、まずはblackRect-targetへのベクトルを計算する(終点-始点で計算可能)
            const vector = {
                x: target.x - sprite.x,
                y: target.y - sprite.y
            };
            //角度を計算するためには、Math.atan2(y,x)関数を使えばよい
            bullet.rad = Math.atan2(vector.y, vector.x);
            bullet.onenterframe = function () {
                this.x += Math.cos(this.rad) * shotSpeed;    //移動角度のcosがX軸移動方向
                this.y += Math.sin(this.rad) * shotSpeed;    //移動角度のsinがY軸移動方向
                if (bullet.x = -30 || bullet.x = 430 || bullet.y = -30 || bullet.y = 430) { //画面外か判定
                    this.parentNode.removeChild(this);  //画面外なら自身を削除する
                }
            }
            return bullet; //上で作成したspriteを返す
        }


さて、本ページの目的、ターゲットに向けて発射する処理です。

画面をクリックすると、ターゲットの位置を変更することができます。

以下vectorはx,yを持つ連想配列と呼ばれるものです。連想配列は、まとまったデータを格納する時に使います。

さて、終点(target)-始点(発射元)でベクトルを求めることができます。

const vector = {
    x: target.x - sprite.x,
    y: target.y - sprite.y
}

ここで求めたvectorをMath.atan2関数に以下の順番で引数にすることで、目標方向の移動角度を計算することができます。

bullet.rotation=Math.atan2(vector.y,vector.x);

あとは、「移動角度を設定して打ち続ける」と同じく、sin,cosで移動量を決めたら終了です。

さて、これで一通りの説明が終わりました。

ここで実際に書き換えることで、今後のシューティング制作ライフも向上するはずです・・・!

以上です!よいゲーム制作ライフを!

おまけ 上記プログラムを改造した暗黒黒炎攻撃

See the Pen カッコいいエフェクトで打つ by ほっとフクロウ(作っちゃうおじさん) (@hothukurou) on CodePen.

Sprite は以下パラメータを設定できます。回転とか透明度変更とか拡大縮小ができるので、そのあたりを上手く使って作りました。

Sprite.rotation=0;//度数表記で代入値に回転

Sprite.opacity=1;// 非透明度を0~1で指定 0で透明 1で非透明

Sprite.scaleX=1;//X方向の拡大率を指定 1がデフォ値

Sprite.scaleY=1;//Y方向の拡大率を指定 1がデフォ値

Sprite.backgroundColor="rgba(0,0,0,1)"; //(red,green,blue,opacity)を設定 rgbは0~255, aは0~1で透明度設定

あとは、enchant.jsの公式リファレンスを見たり、ググったりするといいです。

さらにおまけ

以前、あほげーという一日でゲームを作るイベントで制作したシューティングゲームです。

https://hothukurou.com/game/yariika99/index.html