ベクトル
向きと大きさを持つ量であるベクトルと、その計算(内積・外積)を使って、狙い撃ちや反射などの処理を組み立てる方法について説明します。
<注意>
このページでは、数式を表示するためにMathJaxを使用しています。ブラウザの設定でスクリプト実行をブロックしている場合、ページ内の数式が正しく表示されません。
ベクトルとは
速度や、ある点から別の点への移動のように、向きと大きさを持つ量をベクトルと言います。
平面のベクトルは、\(\ x\ \)方向と\(\ y\ \)方向の成分の組で表します。
点Pから点Qへのベクトルは、Qの座標からPの座標を引いたものです。
\[ \overrightarrow{\mathrm{PQ}} = (Q_x - P_x,\ Q_y - P_y) \]ゲームでは、キャラクターの位置は点、速度や「自機への向き」はベクトルです。
毎フレーム位置に速度を足す処理(固定小数点で動かす)は、点にベクトルを足して、新しい点を求めていることにあたります。
和とスカラー倍
定義
\[ \mathbf{a} + \mathbf{b} = (a_x + b_x,\ a_y + b_y), \qquad k\mathbf{a} = (k a_x,\ k a_y) \]
ベクトルの和は、矢印をつなげた先を表します。
スカラー倍(数を掛けること)は、向きを変えずに長さを\(\ k\ \)倍します(\(\ k\ \)が負なら逆向きになります)。
長さ
ベクトルの長さ(大きさ)は、三平方の定理で求めます。
\[ |\mathbf{a}| = \sqrt{a_x^2 + a_y^2} \]2点の距離は、2点を結ぶベクトルの長さです。
平方根を使わずに比べる
距離を比べるだけなら、平方根を求める必要はありません。
\(\ |\mathbf{a}| < r\ \)と\(\ a_x^2 + a_y^2 < r^2\ \)は同じことなので、2乗のまま比べます。
「半径\(\ r\ \)の円の中に入ったか」の判定は、掛け算2回と足し算1回で済みます。
長さの近似
長さそのものが必要で、多少の誤差が許されるときは、次の近似がよく使われます。
\[ |\mathbf{a}| \approx \max(|a_x|,\ |a_y|) + k \cdot \min(|a_x|,\ |a_y|) \]
\(\ k = 1/2\ \)なら、シフト1回で計算でき、誤差は0~+11.8%です。
\(\ k = 3/8\ \)にすると、誤差は−2.8~+6.8%に収まります。
どちらもベクトルが軸に平行なときは誤差がなく、向きによって誤差が変わります。
正確な平方根の求め方は、多項式近似の「繰り返しで求める」で説明します。
単位ベクトル
長さが1のベクトルを単位ベクトルと言います。
ベクトルを自分の長さで割ると、同じ向きの単位ベクトルになります(正規化)。
向きを角度\(\ \theta\ \)で持っている場合、単位ベクトルは\(\ (\cos\theta,\ \sin\theta)\ \)です(三角関数)。
これに速さを掛けたものが速度になります(角度と三角関数の表)。
内積
定義 (内積)
\[ \mathbf{a} \cdot \mathbf{b} = a_x b_x + a_y b_y \]
定理
2つのベクトルのなす角を\(\ \theta\ \)とすると、\(\ \mathbf{a} \cdot \mathbf{b} = |\mathbf{a}|\,|\mathbf{b}|\cos\theta\ \)である。

内積は、掛け算2回と足し算1回で計算できますが、2つのベクトルの向きの関係を表しています。
| 内積の値 | 2つのベクトルの向き |
|---|---|
| 正 | 同じような向き(なす角が90度未満) |
| 0 | 直角 |
| 負 | 反対のような向き(なす角が90度より大きい) |
たとえば、敵の向きのベクトルと、敵から自機へのベクトルの内積が正なら、自機は敵の前方にいます。
「敵の視界に入ったか」のような判定に使えます。
\(\ \mathbf{a}\ \)が単位ベクトルのとき、\(\ \mathbf{a} \cdot \mathbf{b}\ \)は、\(\ \mathbf{b}\ \)を\(\ \mathbf{a}\ \)の向きに投影した長さ(図の緑の部分)になります。
これは、次の反射や滑りの計算で使います。
外積
3次元のベクトルの外積はベクトルですが、平面のベクトルでは、次の1つの数として扱います。
定義 (平面の外積)
\[ \mathbf{a} \times \mathbf{b} = a_x b_y - a_y b_x \]
定理
\(\ \mathbf{a} \times \mathbf{b} = |\mathbf{a}|\,|\mathbf{b}|\sin\theta\ \)である(\(\ \theta\ \)は\(\ \mathbf{a}\ \)から\(\ \mathbf{b}\ \)へ反時計回りに測った角)。
その絶対値は、\(\ \mathbf{a}\ \)と\(\ \mathbf{b}\ \)が作る平行四辺形の面積に等しい。
外積の符号は、\(\ \mathbf{b}\ \)が\(\ \mathbf{a}\ \)の左右どちら側にあるかを表します。
| 外積の値 | \(\ y\ \)軸が上向きの座標 | \(\ y\ \)軸が下向きの座標(画面の座標) |
|---|---|---|
| 正 | \(\ \mathbf{b}\ \)は\(\ \mathbf{a}\ \)の左側 | \(\ \mathbf{b}\ \)は\(\ \mathbf{a}\ \)の右側 |
| 0 | 同じ直線の上 | 同じ直線の上 |
| 負 | \(\ \mathbf{b}\ \)は\(\ \mathbf{a}\ \)の右側 | \(\ \mathbf{b}\ \)は\(\ \mathbf{a}\ \)の左側 |
SNESの画面の座標は\(\ y\ \)が下に向かって増えるので、数学の教科書とは左右が逆になります。
符号の意味は、実際に1つ値を入れて確かめておくと間違えません。
線分が交わるかどうか
線分ABと線分CDが交わるのは、CとDが直線ABの反対側にあり、AとBが直線CDの反対側にあるときです。
「反対側にある」は、外積の符号が異なることで判定できます。
速い弾が1フレームで壁を通り抜けてしまうのを防ぐには、前のフレームの位置から今の位置への線分が、壁と交わるかどうかを調べます。
応用
狙い撃ち
敵から自機へのベクトル\(\ \mathbf{d}\ \)を正規化して速さを掛ければ、自機に向かう弾の速度になります。
\[ \mathbf{v} = s\,\frac{\mathbf{d}}{|\mathbf{d}|} \]割り算と平方根を避けたい場合は、\(\ \mathbf{d}\ \)の向きを角度(256方位)に直し、角度と三角関数の表から速度を求めます。
ベクトルから角度を求める関数は逆正接(\(\ \arctan\ \))です(三角関数の「逆三角関数」)。
ゲームでは、\(\ d_y/d_x\ \)の値と\(\ d_x,\ d_y\ \)の符号から、表を引いて角度を求めることが多いです。
向きを少しずつ変える(誘導弾)
弾の向きを一度に目標へ向けずに、毎フレーム決まった角度だけ目標のほうへ回すと、曲がりながら追いかける誘導弾になります。
どちらへ回すかは、弾の向きのベクトルと、弾から目標へのベクトルの外積の符号で決めます。

最初は真上を向いた弾が、右にある目標を追いかけたときの軌道です(\(\ y\ \)軸は上向き)。
1フレームに回せる角度が小さいほど、大回りしてから目標に近づきます。
回せる角度が小さすぎると、目標の周りを回り続けて当たらないことがあります。
角度の差を合同算術の方法で求めて、その符号で回す向きを決めることもできます。
反射と滑り
壁の向きを、壁に垂直な単位ベクトル\(\ \mathbf{n}\ \)(法線)で表します。
速度\(\ \mathbf{v}\ \)のうち、壁に垂直な成分は\(\ (\mathbf{v} \cdot \mathbf{n})\,\mathbf{n}\ \)です。

| 動き | 新しい速度 |
|---|---|
| 跳ね返る(反射) | \(\ \mathbf{v} - 2(\mathbf{v} \cdot \mathbf{n})\,\mathbf{n}\ \) |
| 壁に沿って滑る | \(\ \mathbf{v} - (\mathbf{v} \cdot \mathbf{n})\,\mathbf{n}\ \) |
壁が水平や垂直なら、\(\ \mathbf{n}\ \)は\(\ (0,\ \pm 1)\ \)や\(\ (\pm 1,\ 0)\ \)なので、反射は速度の片方の成分の符号を反転するだけ、滑りは片方の成分を0にするだけになります。
斜めの壁(坂など)では、この式をそのまま使います。