【Unity】【C#】符号(方向)付き角度を、2Dの外積を使って求める [2D] 
2017/07/08 Sat [edit]
はじめに断っておくと、外積というのは3Dでの定義なので、今回の「2Dの外積」というのは、あくまでもその計算方法を真似して、その性質を利用したものであるということだ。数学的に正しい定義でないことを承知しておいて欲しい。以下に参考URLを載せておこう。
(参考)外積の定義
また、Unity では Vector2.Angle() という便利な関数があるが、これも利用する。ただし、この関数が返す角度は大きさだけであって、その方向(正負符号)は含まれてない。あるオブジェクトから対象オブジェクトの角度を調べたとき、その方向(2Dなら右か左か等)も知りたいことは多いだろう。それを計算するために「なんちゃって外積」(2D外積)を利用しようというわけだ(笑)。
今回は2Dでの正負の方向や外積の定義などかなり詳細に記しているので、既に知っている、またはとりあえず今すぐ使いたいなどの場合は「■符号付きで角度を求める [2D]」まで読み飛ばした方が良いだろう。
(※) Unity 5.6.2p2 / Windows10(x64) で確認
■2Dでの回転方向と符号の対応
まずはじめに、Unity 上での回転方向の符号を調べておこう。数学的なものを用いるときに気をつけなければならないのは、Unity は左手系座標で数学は右手系座標で書かれているので、符号や見た目方向が異なる点だ。ちなみに「右手系」「左手系」というのは親指を X軸、人差し指を Y軸、中指を Z軸に見立てて、座標軸の正方向に合わせればわかる。腕をひっくり返しても良い。詳しくは以下の参考URLでも見て欲しいが、とりあえずここでは理屈は抜きにして、実際に試すことで正負を調べてみよう。
(参考)3D 座標系
試してみると、Rotation の Z を正方向に 45 度回転すると、見た目左側に傾くことがわかる。これもあくまで基準は上方向で Z軸を中心に回転した場合であって、例えば基準が下なら逆方向に見えるので気をつけよう。つまり、常に何かを基準に考えることを忘れないようにしよう。
ちなみに 2D画面の場合は、Z軸というのは見えないが、Unity の場合、自分から見て奥が Z軸の正方向、手前が負の方向となる。これは 3D画面にすればわかる。なので、スプライトの「Sorting Layer」「Order in Layer」が同じだった場合、Z軸の Position を正の方向へ動かせば、見た目重なり順が奥になる。
とにかく 2D画面では上方向を基準とした場合、Z軸回転で左側が正方向、右側が負方向ということがわかった。そしてこれを ▲ をキャラとした場合、transform.up に置き換えると、キャラを回転したときにも、進行方向から見て、符号で左右の向きが決まる。このことを踏まえた上で、次は「2Dの外積」を考えてみよう。
■2D外積の定義
冒頭に書いたように、外積の定義は本来 3Dのものである。その外積の成分表示は参考資料を見て欲しいが、ここでは 2D用に使うので、以下の式を使う。なお、Vector2 を利用したいので、成分[=座標]:(a1, a2), (b1, b2) → (ax, ay), (bx, by) に置き換えてある。式を見ただけではどういう計算をしているのかイマイチわからないので、図のように成分の外側と内側を掛け合わせて差を取るということを機会的に覚えてしまう方が良いだろう。理論より実践で使えることの方がよほど重要なことである(笑)。z 成分に0を代入したものと考えても良い。
(参考)外積の成分表示

●成分の外側と内側を掛け合わせて差を取る
●2D外積を求める関数
using UnityEngine;
/// <summary>
/// 2Dでの外積を求める(XY平面)
///・a×b = a1b2 - a2b1
///・2D 的に計算する。z軸を無視したものと考える。
///・外積 = 0 のとき、両ベクトルは平行(0または180度)。
///・外積 > 0 のとき、transform.up を基準にすると左側。
///・外積 < 0 のとき、transform.up を基準にすると右側。
/// </summary>
/// <param name="a">基準の方向ベクトル</param>
/// <param name="b">対象の方向ベクトル</param>
/// <returns>2Dの外積</returns>
public static float Cross2D(Vector2 a, Vector2 b)
{
return a.x * b.y - a.y * b.x;
}
コードにすると上記のようになる。図を見れば説明はいらないだろう。その対応はこれから調べることにする。
■2D外積と符号の対応
さて、2Dでの外積の計算も示したので、今度は具体的に座標を代入して計算してみよう。調べる座標をすべて1で構成すれば、暗算でも可能だろう。▲の進行方向を基準として、左右の2D外積を求めると、以下の図のようになる。同じ象限にいれば符号は変わらないので、これと前述の「2Dでの回転方向と符号の対応」と照らし合わせれば、符号が一致していることがわかる。ちなみに平行なベクトル(0または180度)の2D外積は0になる。
・(0, 1) が基準で、(1, 1) の 2D外積は、(0, 1)×(1, 1) = 0 * 1 - 1 * 1 = 0 - 1 = -1
・(1, 0) が基準で、(1, 1) の 2D外積は、(1, 0)×(1, 1) = 1 * 1 - 0 * 1 = 1 - 0 = +1
・(1, 0) が基準で、(1, -1) の 2D外積は、(1, 0)×(1, -1) = 1 * (-1) - 0 * 1 = (-1) - 0 = -1
・(0, -1) が基準で、(1, -1) の 2D外積は、(0, -1)×(1, -1) = 0 * (-1) - (-1) * 1 = 0 + 1 = +1
・(0, -1) が基準で、(-1, -1) の 2D外積は、(0, -1)×(-1, -1) = 0 * (-1) - (-1) * (-1) = 0 - 1 = -1
・(-1, 0) が基準で、(-1, -1) の 2D外積は、(-1, 0)×(-1, -1) = (-1) * (-1) - 0 * (-1) = 1 + 0 = +1
・(-1, 0) が基準で、(-1, 1) の 2D外積は、(-1, 0)×(-1, 1) = (-1) * 1 - 0 * (-1) = (-1) + 0 = -1
■符号付きで角度を求める [2D]
以上で、2Dでの回転方向と外積の符号が一致していることがわかった。つまり符号付きで角度を求めるには、
2Dの符号付き角度=角度の大きさ × 外積の符号
で良いことがわかる。注意して欲しいのは、これは Unity での2D座標系でのことであって、3Dでは基準方向(軸)によって符号が異なったりするので気をつけよう。数学的にすべて成り立つというわけではなく、この性質をゲームではよく利用されるというだけだ。また、冒頭に書いた「座標系」でも回転方向や符号は異なるので、少なくとも1度は調べておいた方が良い。ともかく、Unity での 2Dでの計算は以下のようになるだろう。
●2Dで符号付きで角度を返す
using UnityEngine;
/// <summary>
/// 2D(XY 平面)での方向ベクトル同士の角度を符号付きで返す(度)
///・from から to への角度を返す。
///・transform.up を基準とすると、左側(反時計回り)に位置するとき正、右側(時計回り)に位置するとき負となる。
///・値は Vector2.Angle() に 2Dの外積を掛けたものになる。
/// </summary>
/// <param name="from">基準の方向ベクトル</param>
/// <param name="to">対象の方向ベクトル</param>
/// <returns>-180 <= t <= 180 [degree]</returns>
public static float AngleWithSign(Vector2 from, Vector2 to)
{
float angle = Vector2.Angle(from, to); //符号なし大きさ(度)
float cross = Cross2D(from, to);
return (cross != 0) ? angle * Mathf.Sign(cross) : angle;
}
関数内部で使っている「Cross2D()」(2D外積)は前述のものを使う。解説も前述の図や計算式を見れば十分だろう。
これを使って、基準となる方向ベクトル(引数:from)に transform.up を、対象への方向ベクトル(引数:to)に target.transform.position - transform.position を代入すれば、常に進行方向に対して対象が左側(反時計回り)にいるか(正の値)、右側(時計回り)にいるか(負の値)とその角度がわかる。もちろん、引数の基準となる方向ベクトルと対象の方向ベクトルを逆にすると、符号が逆になる。
ちなみに以下3つの誘導弾(ホーミングミサイル)や火の鳥(?)は これら関数をそのまま使ったものだ。ただし、敵から自機への誘導弾の角度をきっちり計算してしまうと確実にやられてしまうので、一度に回転できる角度を制限し、かつ一定時間で追尾するのを停止することによって、ゲームとしてのバランスを調整している。それぞれ、角度の量と制限時間に加え、スピードと見た目を変えただけだが、敵の攻撃のバリエーションとしては十分効果があるだろう。色々なものに使えると思う。
|
|
|
なお、さんざん「2D外積」という用語を使っているが、正式な名称でも定義でもないため、数学に詳しい人に言ったら怒られるかも(笑)。私が便宜上使っているだけの言葉なので、他人に説明するときは何らか公共性のある言葉に置き換えた方が良いかも知れない(投げっぱなし)。
(関連記事)
【Unity】【C#】床面(Yaw)/YZ平面(Pitch)に射影された角度を、符号(方向)付きで求める [3D]
【Unity】【C#】n度ごとの角度を返す(一定角度ごとの切り捨て、切り上げ、四捨五入)
- 関連記事
トラックバック
トラックバックURL
→http://fantom1x.blog130.fc2.com/tb.php/253-fdf125bf
この記事にトラックバックする(FC2ブログユーザー)
| h o m e |