シューティングゲームのレーザーの当たり判定式を導出

## はじめに

https://qiita.com/Snowman-s/items/56bbf19304330be4e258#%E3%83%AC%E3%83%BC%E3%82%B6%E3%83%BC%E5%8F%82%E8%80%83

↑の記事にて、シューティングゲームにおけるレーザーの当たり判定を行うコードを公開しました。

そろそろちゃんと解説を書こうかな、と言ったところで、これを２つの方法で導出します。

## 立式
「レーザー」の中心を($x_{l}$ , $y_{l}$)とし、向いている角度を$\theta$、レーザー幅の半分を$w_l$、レーザー長さの半分を$l_l$とします。
「自機」の中心を( $x_p$ , $y_p$ )、半径を$r_p$とします。これが当たっているかどうかを判定する問題です。

> よりシンプルに言い換えると、「($x_{l}$ , $y_{l}$)中心で、$\theta$だけ回転された、サイズが$2l_l \times 2w_l$の長方形」と、「( $x_p$ , $y_p$ ) 中心で、半径が$r_p$の円」が重なっているかどうかを判定する問題です。

![図式.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/496507/7851ebad-6aef-d5ec-d28f-717babf75d44.png)

この問題は、以下の条件で判定できます。

----------------
レーザーから自機への距離 (幅方向 $d_w$, 長さ方向$d_l$) を以下でおく：

$d_w =|sin(\theta) \times (x_l - x_p) - cos(\theta) \times (y_l - y_p)|$
$d_l =|cos(\theta) \times (x_l - x_p) + sin(\theta) \times (y_l - y_p)|$

以下の条件のうち１つ以上が成り立つなら、またその時に限り、レーザーは自機に当たっている。

- $d_w \le w_l$ かつ $d_l \le l_l + r_p$
- $d_w \le w_l + r_p$ かつ $d_l \le l_l$
- 角：$\sqrt{(d_w-w_l)^2+(d_l-l_l)^2} \le r_p$

-------------------- 

レーザーから自機までの距離さえ分かれば３つの条件は自明な事、として、この記事では$d_w$と$d_l$を２通りの方法で導出します。

## 導出
### 1. 点と直線の距離の公式

[点と直線の距離の公式](https://manabitimes.jp/math/857) を使います。

#### レーザーを一次方程式で書く
公式を使うにあたって、まずレーザーを一次方程式で書く必要があります。

一次方程式の形は

```math
ax+by+c =0
```

です。この式の係数 $(a, b, c)$ を求めてレーザーをこの式で表しましょう。

ここで、レーザーの傾きは$\theta$ であるため、$x$が$cos(\theta)$ 増加すると$y$が$sin(\theta)$ 増加するということが分かります。すなわち、

```math
a(x+cos(\theta))+b(y+sin(\theta))+c =0
```

も同様に成り立つということです。先ほどの式からこれを引くことで

```math
-a \times cos(\theta) - b \times sin(\theta) =0
```

が得られます。このようなaとbは無数にありますが、一番簡単なのは $(a=-sin(\theta), b=cos(\theta))$ でしょうね。これを最初の式に代入することで

```math
-sin(\theta)x+cos(\theta)y+c =0
```
が導かれます。後はレーザーの中心がもちろんレーザー上にあることから、$(x_l, y_l)$を代入して整理することで、$c=sin(\theta)x_l-cos(\theta)y_l$ が導かれます。

以上より、**レーザーは以下の一次方程式で表現できます。**

```math
-sin(\theta)x+cos(\theta)y+(sin(\theta)x_l-cos(\theta)y_l) =0
```

#### 幅方向の距離を求める

距離の公式は 

```math
d = \frac{|ax_0+by_0+c|}{\sqrt{a^2+b^2}}
```

ということでなんやかんや代入すると

```math
d = \frac{|-sin(\theta)x_p+cos(\theta)y_p+sin(\theta)x_l-cos(\theta)y_l|}{\sqrt{(-sin(\theta))^2+cos^2(\theta)}}
```

ただし、この式で **分母は1となり奇跡的に消えます**。 

整理して、結局求める式は

```math
d_w = |sin(\theta) \times (x_l - x_p) - cos(\theta) \times (y_l - y_p)|
```

#### 長さ方向の距離を求める

幅方向だけでなく、長さ方向にも当たっているかどうかを判定する必要があります。
手短に、レーザーを90度回転したものとして扱って同じ式を適用するだけです。

つまり、先ほど出てきた次の式：

```math
d = |sin(\theta) \times (x_l - x_p) - cos(\theta) \times (y_l - y_p)|
```

の$\theta$ を　$\theta+\pi/2$ に置き換えれば長さ方向の条件が分かります。

```math
d = |sin(\theta+\pi/2) \times (x_l - x_p) - cos(\theta+\pi/2) \times (y_l - y_p)| 
```
```math
d = |-cos(\theta) \times (x_l - x_p) - sin(\theta) \times (y_l - y_p)| 
```
(絶対値なので)

```math
d_l = |cos(\theta) \times (x_l - x_p) + sin(\theta) \times (y_l - y_p)| 
```

これで、長さ方向の距離が求められました。

### 2. 回転行列

レーザーが回転しておらず、横に真っすぐだったなら当たり判定の検証はすこぶる簡単なため、そうなるように座標全体を回転させます。レーザーが $\theta$ 回転しているなら、すべてをレーザーの中心...$(x_l,y_l)$を原点として $-\theta$ 回転させれば横真っすぐになりますね。

回転させるには [回転行列](https://mathlandscape.com/rotation-matrix/)を使いましょう。

```math
\begin{pmatrix}
   cos(-\theta) & -sin(-\theta) \\
   sin(-\theta) & cos(-\theta)
\end{pmatrix}
=
\begin{pmatrix}
   cos(\theta) & sin(\theta) \\
   -sin(\theta) & cos(\theta)
\end{pmatrix}
```

ということで、以下の式を計算すると、レーザーの中心から見た、回転済みのプレイヤーの座標$(x_p',y_p')$が分かります。

```math
\begin{pmatrix}
   x_p' \\
   y_p' 
\end{pmatrix}
=
\begin{pmatrix}
   cos(\theta) & sin(\theta) \\
   -sin(\theta) & cos(\theta)
\end{pmatrix}

\begin{pmatrix}
   x_p - x_l \\
   y_p - y_l 
\end{pmatrix}
=
\begin{pmatrix}
   cos(\theta)(x_p - x_l) + sin(\theta)(y_p - y_l) \\
   -sin(\theta)(x_p - x_l) + cos(\theta)(y_p - y_l)
\end{pmatrix}
```

これがそのままレーザー中心からの距離だと見做せるので求めたい式が求まったことになります！

## 終わりに
書いている途中で元記事コードの間違いに気づいた

## 補足

> 以下の条件のうち１つ以上が成り立つなら、またその時に限り、レーザーは自機に当たっている。
>
> - $d_w \le w_l$ かつ $d_l \le l_l + r_p$
> - $d_w \le w_l + r_p$ かつ $d_l \le l_l$
> - 角：$\sqrt{(d_w-w_l)^2+(d_l-l_l)^2} \le r_p$

$r_p$ が十分に小さいなどの理由で角を無視できるのであれば、この条件は単に以下で書けます。

```math
d_w \le w_l + r_p \text{  かつ  } d_l \le l_l + r_p
```