home > programming > notes >

シューティングゲームと二次元ベクトル

1.自機を狙う弾

敵→自機のベクトル

2Dのシューティングゲームで敵e(ex, ey)から自機m(mx, my)に向かって弾を撃ちたい場合のことを考えます。
まず敵→自機のベクトルv(vx, vy)を求めます。

vx = mx - ex
vy = my - ey

自機の座標から、敵の座標をそれぞれ引いたものが敵→自機のベクトルとなります。

これをそのまま弾の速度として使うと、1フレームで自機に届く必殺弾になってしまうので、方向を保ったまま適当な長さに直す作業が必要です。
そのためにまずはvの現在の長さlを求めます。これには三平方の定理を用います。

l = sqrt (vx2 + vy2)
(ルート記号が使えないのでsqrtとしてます)

この値でvx, vyをそれぞれ割れば、長さ1に正規化できます。(lが0の場合は割れないことに注意)

vx' = vx / l
vy' = vy / l

さらに、弾速sを掛ければ、弾の速度ベクトルが求まります。

vx'' = vx' * s
vy'' = vy' * s

2.ベクトルの回転と扇状弾幕

ベクトルの回転

敵に扇状に広がる弾幕を吐かせたい場合、1.で求めたベクトルの大きさをそのままに、方向を回転させる必要があります。

この場合アークタンジェントを使ってベクトルの方向を求め、方向を変化させてからsin,cosを使ってベクトルに戻すこともできますが、この方法は計算量が大きいです。ベクトルの方向を求めずに回転させましょう。

X軸が右方向、Y軸が上方向の座標系でベクトル(x, y)を反時計回りに角度θ回転させたベクトル(x', y')を求める式は

[ x' y' ]  = [ x y ] [  cosθ sinθ ]
                     [ -sinθ cosθ ]

という行列の掛け算になります(文字の都合で見づらいですが……)。これを展開すると

x' = x * cosθ - y * sinθ
y' = x * sinθ + y * cosθ

「y' = x * cosθ + y * sinθ」と書いていましたが、誤りです。お詫びして訂正します。

となります。任意の回転角θからcosθ、sinθさえ求めればベクトルを回転させることができます。回転角が一定であればcosθ、sinθを先に計算しておき定数として使うことができるので、計算量はとても小さくなります。

扇状の弾幕を吐かせるには、1.で求めたベクトルを一定角でそれぞれ時計回り、反時計回りに回転させてやればオーケーです。sinθの符号を反転するだけで逆回転ができます。

この方法は回転全般に用いることができます。ただ、変数の精度が低いと回転を繰り返すうちにベクトルの長さが変わってしまうことが考えられますので、そのような場合は1.の終わりの方法でときどきベクトルの長さを調整してやる必要があります。

3.外積による追尾弾

発射された後、自機のいる方向をサーチして旋回しながら進むミサイルを出したいとします。

ミサイル→自機のベクトルから方向を求め、ミサイルの速度ベクトルから方向を求め、この二つを比較して回転方向を決めることができますが、たかだか右に回るのが近いか左に回るのが近いかを決めるためには大袈裟ですので、ここではベクトルの外積を使います。

[二次元ベクトルの外積]
二つのベクトルa(ax, ay), b(bx, by)のなす角をθとすると、a,bの外積
a×b = |a||b|sinθ = ax * by - ay * bx

ミサイルの速度ベクトルv(vx, vy)、ミサイル→自機のベクトルt(tx, ty)を用意するところまでは同じですが、この二つのベクトルの外積
vx * ty - vy * tx は、vの長さ、tの長さ、vとtのなす角θのsinを掛けたものなので、
vx * ty - vy * tx > 0なら自機はミサイルの進行方向の左側(反時計回り)、
vx * ty - vy * tx < 0なら自機はミサイルの進行方向の右側(時計回り)にいるということになります(Y軸が上向きの場合)。

それぞれの場合に応じて2.で説明した方法でミサイルの速度ベクトルを回転させれば、追尾ミサイルが実現できます。

外積による追尾ミサイル

4.内積と追尾弾

3.で追尾ミサイルが実現できましたが、ミサイルが自機に向いている場合、このままだと1フレームごとに左右に揺れてしまいます。回転角が小さい場合は気にならないかもしれませんが、そうでない場合やミサイルを画面に大きく表示する場合などは問題になるでしょう。

これを回避するためには、ミサイルの速度ベクトルとミサイル→自機のベクトルのなす角度を調べ、もしミサイルの1フレームの回転角以内であった場合には、1.で説明した方法でミサイルが自機をまっすぐ向くようにする、という処理をします。

外積を用いた場合、

vx * ty - vy * tx = |v||t|sinθ

でしたので、vx * ty - vy * txをv,tそれぞれのベクトルの長さで割ってやると、sinθが求まります。

sinθ = (vx * ty - vy * tx) / (sqrt(vx2 + vy2) * sqrt(tx2 + ty2))

ミサイルの1フレームに回転できる角度をα(最大90°)とすると、-α<θ<αであるとき-sinα<sinθ<sinαですので、|sinθ|<sinαであるか調べればよいです……が、ひとつ問題があります。ミサイルが自機と正反対の方向を向いているとき、つまりθが180°周辺のときも同様に|sinθ|<sinαとなってしまうため、これではミサイルが自機と逆方向を向いていたのに、突然こっちに向き直ってしまうという問題が起きてしまいます。

これを直すにはどうすればいいかというと、sinを使うのをやめ、cosを使います。3.のようにどちらの向きか知りたい場合にはsinが便利でしたが、今回のように符号が関係なく、角度だけ知りたい場合はcosのほうが便利です。で、sinθを求めるのには外積を使いましたが、cosθを求めるには内積を使います。

[二次元ベクトルの内積]
二つのベクトルa(ax, ay), b(bx, by)のなす角をθとすると、a,bの内積
a・b = |a||b|cosθ = ax * bx + ay * by

内積を用いると、

cosθ = (vx * tx + vy * ty) / (sqrt(vx2 + vy2) * sqrt(tx2 + ty2))

ミサイルの1フレームに回転できる角度をα(最大180°)とすると、-α<θ<αであるときcosθ>cosαです。sinを使ったときのような問題は発生しません。

内積と追尾ミサイル

ベクトルv,tの長さを求めるのに平方根を2回も使うのは処理速度的にどうかと思いますので、両辺を二乗してsqrtを外したり、あらかじめミサイルの速度がわかっていればsqrt(vx2 + vy2)の代わりにするなど、工夫してください。

この他にも、敵の前方何度以内に自機がいる場合のみ反応する、みたいな処理をさせる場合に内積を使うとよさそうです。距離で制限も付ければ、敵に扇形の視界を与えることが出来ます。