弾幕シューティングに現れる数学

# はじめに
皆さんは弾幕シューティングといったゲームの種類を知っているでしょうか？「弾」といわれる図形が飛んでくるのを、ひたすら「自機」と呼ばれる操作可能な図形に当たらないように避けて相手を撃破するゲーム――――の中でも避けることに大きなフォーカスを当てたゲームの事です。本記事では弾幕シューティングを想定して書きますが、(~~筆者が特に弾幕シューティングが好きなだけ~~)一般的なシューティングゲームにも大体適用されます。

...この記事はクリスマスに公開されました......まぁ、弾幕シューティングが恋人みたいなものなので！

本記事ではJavaを使用しますが、Java特有の書き方はできるだけ省いていきます。
# 三平方の定理
「弾」と呼ばれる図形や、「自機」と呼ばれる図形は大体が円となっています。よって、「当たった」という判定は、**二つの円が重なっているか**という検査となります。円同士が重なっている...という検査は、一見難しそうに見えますが、**円の中心同士の距離が、それぞれの半径の合計以下か**という確認で済むのです。で、二点間の距離を求めるには...そうです、**三平方の定理**です！

**「自機」**の中心を(*xp*, *yp*)、半径を*rp*とし、
**「弾」**の中心を(*xb*, *yb*)、半径を*rb*とします。
この時、その求め方は、

```java:CheckHit.java
public class CheckHit{
    public static void main(String[] args){
        final int xp = 1, yp = 3, rp = 3;
        final int xb = 1, yb = 3, rb = 3;
        int xdelta = xp - xb;
        int ydelta = yp - yb;
        int radiusSum = rp + rb;
        //距離の 2乗が 半径の合計の 2乗よりも 小さい ならば
        if(xdelta * xdelta + ydelta * ydelta <= radiusSum * radiusSum){
            //ヒットした！
        }else{
            //ヒットしてない！
        }
    }
}
```

# sine cosine
「弾」と呼ばれる図形はそれぞれ決められた角度、速さで動くものがほとんどです。では、ある点が、角度*θ*、速さ*v*で進むとき、次はどの点に移動するのでしょうか？それを計算するには...**三角関数**なんですよね。(~~tangentは除く~~)
単位円(半径1の円)の上で、ある角度に進むとき、そのx座標は*cosθ*、y座標は*sinθ*となります。
で、ここで重要なのが、この**進んだ距離が1**ということです。つまり、これを*v*倍にして、最終的なx,y座標の**進んだ長さ**は
x = vcosθ
y = vsinθ
になるということですね！(~~絶対図があったほうが良かった~~)

**「弾」**の中心を(*xb*, *yb*)、進む角度(degree)を*angle*、進む速さを*v*とします。
この時、その求め方は、

```java:BulletSpeed.java
import static java.lang.Math.*;

public class BulletSpeed{
    public static void main(String[] args){
        final int xb = 1, yb = 3, angle = 90, v = 2;
        
        //angleをラジアンに変更したもの
        double rad = angle * PI / 180;
        
        //xbにx方向に進んだ距離を加える
        xb += (int)(v * cos(rad));
        //ybにy方向に進んだ距離を加える
        yb += (int)(v * sin(rad));
        
        //計算完了
    }
}
```
# arctangent
「弾」と呼ばれる図形は、自機の方向へ真っすぐ飛んでくるものがあります。
では、ある点から、ある点までの角度を求めるには、どうすればいいでしょうか？そうです。**三角関数**...のうちでも**arctangent**さんです！
二点を斜辺の端とし、x軸、y軸にそれぞれ平行な辺を持つ直角三角形を書きます。求める角度(向き)を*θ*とし、*x*軸、*y*軸それぞれに対応する辺の長さを*dx*,*dy*とします。この時、*tanθ* = *dy* / *dx*が成り立ちますよね。逆関数をとって*θ* = *arctangent(dy* */* *dx)*となります。...なお、純粋なarctangentでは、x方向が0だとエラーが出ますので、大体の言語に用意されている、改善版関数**atan2**を使います。

**「自機」**の中心を(*xp*, *yp*)とし、
**「弾」**の中心を(*xb*, *yb*)とします。
この時、その求め方は、

```java:BulletAngle.java
import static java.lang.Math.*;

public class BulletAngle{
    public static void main(String[] args){
        final int xp = 1, yp = 3;
        final int xb = 1, yb = 3;        

        int degAngle = (int)(atan2(yp - yb, xp - xb) / PI * 180);
        //計算完了
    }
}
```
# レーザー(参考)
「弾」の中でも、「レーザー」と呼ばれるものは、当たり判定が長方形(傾きあり)となっています。長方形と、円が重なっているかを判定する...やっぱり**三角関数**なんですよね。(~~tangentは再び除く~~)
今回は細かい説明はややこしいので省きます。

「レーザー」の中心を( *xl* , *yl* )とし、向いている角度を*angle*、幅を*laserwidth*、長さを*laserlength*とします。
「自機」の中心を( *xp* , *yp* )、半径を*rp*とします。

この時、その求め方は、(角度が90の倍数の場合には別計算)

```java:Laser.java
import static java.lang.Math.*;

public class Laser{
    public static void main(String[] args){
         final int xl = 3, yl = 5;
         final int xp = 6, yp = 3;
         final int rp = 10;
         final int angle = 30;
         final int laserwidth = 10;
         final int laserlength = 300;

         int deltax = xl - xp;
         int deltay = yl - yp;

         double c = cos(angle * PI / 180);
         double s = sin(angle * PI / 180);
         //Width方向
         double distance = abs(s * 
         deltax - c * deltay);
         //Length方向
         double distancel = abs(c * deltax + s * deltay);

         if(distance <= laserwidth / 2.0 + rp &&
            distancel <= lazerlength / 2.0 + rp){
             //当たった！
         }else{
             //当たってない！
         }
    }
}
```

# おわりに
まぁ、ほんの少しだけ紹介しましたが、これだけでも数学は弾幕シューティングにとって非常に重要な要素であることが分かってもらえたでしょうか。
**弾幕シューティングは、やるのも、作るのも楽しいですよ！**