今回はUnityの方向から角度を求めるメソッド群、角度から別の角度を求めるメソッド群をサンプルを使って視覚的にわかりやすくしたいと思います。
今回使用する角度計算のメソッドは
です。
今回作成するサンプルを作成すると、
上のような感じでシーンビューでゲームオブジェクトを動かすとそれに応じてすぐに結果を見ることが出来るので、メソッドの計算結果がどのようになるかを視覚的に確認出来ます。
シーンビューで角度を確認出来るサンプルの作成
シーンビューでゲームオブジェクトを動かして角度をどのように計算しているかがわかるサンプルを作成します。
ヒエラルキー上で右クリック→Create Emptyを選択し、名前をCalculateAngleにします。
ヒエラルキー上で右クリック→3D Object→Planeを選択します。
ヒエラルキー上で右クリック→3D Object→Cubeを選択します。
Cubeを選択しインスペクタのTransformのScaleのXYZを全て0.3にします。
Cubeを選択した状態でCtrl+Dキーを押して二つのCubeを複製し、名前をBaseObj、FromObj、ToObjとします。
BaseObjが元となる位置にするゲームオブジェクトで、FromObjが最初の方向を計算する位置、ToObjが次の方向を計算する位置です。
ゲームオブジェクトをシーンビュー上でわかりやすくアイコン表示したい場合はインスペクタの名前の横のアイコンを押して変更します。
作成したゲームオブジェクトは下のようになりました。
CalculateAngleゲームオブジェクトに新しくCalculateAngleスクリプトを作成し取り付けます。
全てのメソッドの計算で使うが下のようなスクリプトになり、OnDrawGizmosメソッド内を変更して確認出来ます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 | using System.Collections; using System.Collections.Generic; using UnityEditor; using UnityEngine; public class CalculateAngle : MonoBehaviour { public enum AngleMode { None, Vector3Angle, Vector3SignedAngle, QuaternionAngle, QuaternionAngleAxis, QuaternionFromToRotation, QuaternionLookRotation, Vector3Dot, Vector3Cross } [SerializeField] private Transform baseObj; [SerializeField] private Transform fromObj; [SerializeField] private Transform toObj; [SerializeField] private GUIStyle gUIStyle; [SerializeField] private Transform plane; [SerializeField] private AngleMode angleMode = AngleMode.None; #if UNITY_EDITOR void OnDrawGizmos() { var angle = 0f; var rot = Quaternion.identity; var a = Vector3.zero; var b = Vector3.zero; if (angleMode == AngleMode.Vector3Angle || angleMode == AngleMode.Vector3SignedAngle) { // Vector3.Angle用の表示 Handles.color = Color.red; Handles.DrawLine(baseObj.position, fromObj.position); Handles.DrawLine(baseObj.position, toObj.position); // fromObjの方向ベクトル a = fromObj.position - baseObj.position; // toObjの方向ベクトル b = toObj.position - baseObj.position; // fromObjからtoObjの角度 if (angleMode == AngleMode.Vector3Angle) { angle = Vector3.Angle(a, b); // Vector3.SignedAngle } else if (angleMode == AngleMode.Vector3SignedAngle) { angle = Vector3.SignedAngle(a, b, Vector3.up); } Handles.Label(baseObj.position + Vector3.back * 0.5f, "<color=#ffffff>" + angle.ToString() + "</color>", gUIStyle); } else if (angleMode == AngleMode.QuaternionAngle) { // Quaternion.Angle Handles.color = Color.red; Handles.DrawLine(fromObj.position, fromObj.position + fromObj.forward * 5f); Handles.DrawLine(toObj.position, toObj.position + toObj.forward * 5f); angle = Quaternion.Angle(fromObj.rotation, toObj.rotation); Handles.Label(baseObj.position + Vector3.back * 0.5f, "<color=#ffffff>" + angle.ToString() + "</color>", gUIStyle); Handles.DrawSolidArc(baseObj.position + Vector3.left * 5f, Vector3.up, Vector3.forward, angle, 3f); } else if (angleMode == AngleMode.QuaternionAngleAxis) { // Quaternion.AngleAxis rot = Quaternion.AngleAxis(fromObj.eulerAngles.y, Vector3.right); baseObj.rotation = rot; Handles.Label(baseObj.position + Vector3.back * 0.5f, "<color=#ffffff>" + rot.eulerAngles + "</color>", gUIStyle); } else if (angleMode == AngleMode.QuaternionFromToRotation) { // Quaternion.FromToRotation Handles.color = Color.red; Handles.DrawLine(baseObj.position, fromObj.position); Handles.DrawLine(baseObj.position, toObj.position); a = fromObj.position - baseObj.position; b = toObj.position - baseObj.position; rot = Quaternion.FromToRotation(a, b); Handles.Label(baseObj.position + Vector3.back * 0.5f, "<color=#ffffff>" + rot.eulerAngles + "</color>", gUIStyle); } else if (angleMode == AngleMode.QuaternionLookRotation) { // Quaternion.LookRotation Handles.color = Color.red; var forward = fromObj.position - baseObj.position; Handles.DrawLine(baseObj.position, fromObj.position); rot = Quaternion.LookRotation(forward, plane.up); baseObj.rotation = rot; Handles.Label(baseObj.position + Vector3.back * 0.5f, "<color=#ffffff>" + baseObj.eulerAngles + "</color>", gUIStyle); } else if (angleMode == AngleMode.Vector3Dot) { // Vector3.Dot Handles.color = Color.red; Handles.DrawLine(baseObj.position, fromObj.position); Handles.DrawLine(baseObj.position, toObj.position); // fromObjの方向ベクトル a = (fromObj.position - baseObj.position).normalized; // toObjの方向ベクトル b = (toObj.position - baseObj.position).normalized; var innerProduct = Vector3.Dot(a, b); Handles.Label(baseObj.position + Vector3.back * 0.5f, "<color=#ffffff>" + innerProduct + "</color>", gUIStyle); } else if (angleMode == AngleMode.Vector3Cross) { // Vector3.Cross Handles.color = Color.red; Handles.DrawLine(baseObj.position, fromObj.position); Handles.DrawLine(baseObj.position, toObj.position); // fromObjの方向ベクトル a = (fromObj.position - baseObj.position).normalized; // toObjの方向ベクトル b = (toObj.position - baseObj.position).normalized; var outerProduct = Vector3.Cross(a, b); Handles.DrawLine(baseObj.position, outerProduct); Handles.Label(baseObj.position + Vector3.back * 0.5f, "<color=#ffffff>" + outerProduct + "</color>", gUIStyle); } } #endif } |
CalculateAngleゲームオブジェクトのインスペクタでGUIStyleでテキストのフォントサイズ等も変更出来ます。
シーンビュー上でBaseObj、FromObj、ToObjを動かすとそれに応じて結果を確認出来ます(Unityのプレイボタンを押す必要はありません)。
CalculateAngleのインスペクタにBaseObj、FromObj、ToObj、Planeを設定しておきます。
インスペクタのAngleModeを変更することでメソッドを変更してサンプルを試すことが出来ます。
スクリプトの詳細は個々のメソッド毎に説明していきます。
Vector3.Angle
Vector3.Angleを見ていきます。
1 2 3 | Vector3.Angle (Vector3 from, Vector3 to); |
Vector3.Angleはfromとtoの間の角度を計算します。
得られる結果は0~180度の間のfloat値が得られます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | if (angleMode == AngleMode.Vector3Angle || angleMode == AngleMode.Vector3SignedAngle) { // Vector3.Angle用の表示 Handles.color = Color.red; Handles.DrawLine(baseObj.position, fromObj.position); Handles.DrawLine(baseObj.position, toObj.position); // fromObjの方向ベクトル a = fromObj.position - baseObj.position; // toObjの方向ベクトル b = toObj.position - baseObj.position; // fromObjからtoObjの角度 if (angleMode == AngleMode.Vector3Angle) { angle = Vector3.Angle(a, b); // Vector3.SignedAngle } else if (angleMode == AngleMode.Vector3SignedAngle) { angle = Vector3.SignedAngle(a, b, Vector3.up); } Handles.Label(baseObj.position + Vector3.back * 0.5f, "<color=#ffffff>" + angle.ToString() + "</color>", gUIStyle); |
BaseObjからFromObjの方向とBaseObjからToObjの方向を求めその間の角度を計算しています。
ラベルに渡す時にstring型にする必要がある為ToString()メソッドで変換しています。
Handles.Labelでは第1引数でラベルを表示する位置、第2引数で表示するテキスト、第3引数でテキストのスタイルを指定しています。
Vector3.SignedAngle
Vector3.SignedAngleを見ていきます。
1 2 3 | Vector3.SignedAngle (Vector3 from, Vector3 to, Vector3 axis); |
Vector3.SignedAngleはaxisを軸にしたfromとtoの角度を計算します。
得られる結果は-180~180度の間のfloat値が得られます。
axisをVector3.upにした場合はY軸を軸とした回転なのでわかりやすいですが、軸を変えるとすごくわかりにくくなりますね。
1 2 3 | angle = Vector3.SignedAngle(a, b, Vector3.up); |
角度計算部分以外はVector3.Angleと同じなので問題ないと思います。
Quaternion.Angle
Quaternion.Angleを見ていきます。
1 2 3 | Quatenrion.Angle (Quaternion a, Quaternion b); |
Quaternion.Angleはaの角度とbの角度の間の角度を計算します。
得られる結果は0~180度のfloat値です。
別の点CがあるとしてCからAに線を引き、CからBに線を引いたとしてそのCAとCBの線の間の角度が得られるようです。
1 2 3 4 5 6 7 8 9 10 | } else if (angleMode == AngleMode.QuaternionAngle) { // Quaternion.Angle Handles.color = Color.red; Handles.DrawLine(fromObj.position, fromObj.position + fromObj.forward * 5f); Handles.DrawLine(toObj.position, toObj.position + toObj.forward * 5f); angle = Quaternion.Angle(fromObj.rotation, toObj.rotation); Handles.Label(baseObj.position + Vector3.back * 0.5f, "<color=#ffffff>" + angle.ToString() + "</color>", gUIStyle); Handles.DrawSolidArc(baseObj.position + Vector3.left * 5f, Vector3.up, Vector3.forward, angle, 3f); |
スクリプトを流用する為にfromObjとtoObjという名前でそのまま使ってます。
Handles.DrawLineでfromObj、toObjの位置から前方5mの線を引きます。
fromObjとtoObjの角度を変更するとその間の角度が計算されます。
わかりやすくする為にFromObjとToObjの位置を同じにし、BaseObjゲームオブジェクトの左側には計算できた角度で扇形を作っています。
Quatenrion.AngleAxis
Quaternion.AngleAxisを見ていきます。
1 2 3 | Quaternion.AngleAxis (float angle, Vector3 axis); |
Quaternion.AngleAxisはaxisを軸にangle度回転させたQuaternionを計算します。
1 2 3 4 5 6 7 | } else if (angleMode == AngleMode.QuaternionAngleAxis) { // Quaternion.AngleAxis rot = Quaternion.AngleAxis(fromObj.eulerAngles.y, Vector3.right); baseObj.rotation = rot; Handles.Label(baseObj.position + Vector3.back * 0.5f, "<color=#ffffff>" + rot.eulerAngles + "</color>", gUIStyle); |
上のスクリプトではVector3.right(ワールドのX軸)を軸にしてFromObjのTransformのRotationのYの角度分回転させたQuaternionを取得し、BaseObjの角度に設定しています。
FromObjのTransformのRotationのYを60にした場合はBaseObjのTransformのRotationのXが60になります。
Quaternion.FromToRotation
Quaternion.FromToRotationを見ていきます。
Quaternion.FromToRotationに関しては
も参照してください。
1 2 3 | Quaternion.FromToRotation (Vector3 fromDirection, Vector3 toDirection); |
Quaternion.FromToRotationはfromDirectionの方向からtoDirectionの方向のQuaternionを計算します。
1 2 3 4 5 6 7 8 9 10 11 | } else if (angleMode == AngleMode.QuaternionFromToRotation) { // Quaternion.FromToRotation Handles.color = Color.red; Handles.DrawLine(baseObj.position, fromObj.position); Handles.DrawLine(baseObj.position, toObj.position); a = fromObj.position - baseObj.position; b = toObj.position - baseObj.position; rot = Quaternion.FromToRotation(a, b); Handles.Label(baseObj.position + Vector3.back * 0.5f, "<color=#ffffff>" + rot.eulerAngles + "</color>", gUIStyle); |
「BaseObjから見たFromObjの方向」から「BaseObjから見たToObjの方向」の角度を求められます。
BaseObjの位置のXYZを0、FromObjの位置をX:0、Y:0、Z:5、ToObjの位置をX:5、Y:-5、Z:0とした場合は以下のようになります。
Quaternion.LookRotation
Quatenrion.LookRotationを見ていきます。
1 2 3 | Quaternion.LookRotation (Vector3 forward, Vector3 upwards= Vector3.up); |
Quaternion.LookRotationはupwardsを上向き方向にしてforward方向に回転したQuaternionを計算します。
1 2 3 4 5 6 7 8 9 10 | } else if (angleMode == AngleMode.QuaternionLookRotation) { // Quaternion.LookRotation Handles.color = Color.red; var forward = fromObj.position - baseObj.position; Handles.DrawLine(baseObj.position, fromObj.position); rot = Quaternion.LookRotation(forward, plane.up); baseObj.rotation = rot; Handles.Label(baseObj.position + Vector3.back * 0.5f, "<color=#ffffff>" + baseObj.eulerAngles + "</color>", gUIStyle); |
Quaternion.LookRotationの第2引数は省略出来ますが省略するとVector3.upがデフォルト値になります。
上のスクリプトでは上向きをplaneのローカルの上方向を指定しているのでPlaneゲームオブジェクトの角度を変えると計算結果が変わります。
試しにPlaneのTransformのRotationのY軸の角度を315とすると、
上のようにFromObjの方向を向きつつBaseObjの上向きがPlaneの上向きと同じになります。
地べたを歩くゾンビ等で上向きを歩く床の向きと合わせたい時に便利です。
Vector3.Dot
Vector3.Dotを見ていきます。
1 2 3 | Vector3.Dot (Vector3 lhs, Vector3 rhs); |
Vector3.Dotはlhsのベクトルとrhsのベクトルの内積を計算します。
lhsとrhsベクトルが正規化されていた場合(長さ1の単位ベクトル)に得られる値は-1~1の値です。
2つのベクトルの内積を求めるとlhsベクトルの前方に対するrhsベクトルの位置を把握する事が出来ます。
得られた内積が1の場合は完全に同じ方向を向いていて、0の場合は垂直、-1の場合は完全に反対方向を向いています。
1 2 3 4 5 6 7 8 9 10 11 12 13 | } else if(angleMode == AngleMode.Vector3Dot) { // Vector3.Dot Handles.color = Color.red; Handles.DrawLine(baseObj.position, fromObj.position); Handles.DrawLine(baseObj.position, toObj.position); // fromObjの方向ベクトル a = (fromObj.position - baseObj.position).normalized; // toObjの方向ベクトル b = (toObj.position - baseObj.position).normalized; var innerProduct = Vector3.Dot(a, b); Handles.Label(baseObj.position + Vector3.back * 0.5f, "<color=#ffffff>" + innerProduct + "</color>", gUIStyle); |
今回はBaseObjからFromObjのベクトルとBaseObjからToObjへのベクトルの内積を計算しています。
ToObjをFromObjの前方から後ろの方に移動させると以下のようになります。
Vector3.Dotを使うと例えば敵キャラクターの前方にいる主人公キャラクターだけを追いかけさせるような機能を作る時に、敵の前方と敵から見た主人公の方向の内積を求めて主人公が敵の視覚内にいるかどうかを知らべる事が出来ます。
得られた内積が0より上の時は敵の横から前方、0より下の時は敵の横から後方という事になります。
Vector3.Cross
Vector3.Crossを見ていきます。
1 2 3 | Vector3.Cross (Vector3 lhs, Vector3 rhs); |
Vector3.Crossはlhsベクトルとrhsベクトルの外積を計算出来ます。
2つのベクトルの外積を計算すると2つのベクトルと垂直なベクトル(Vector3)が得られます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | } else if (angleMode == AngleMode.Vector3Cross) { // Vector3.Cross Handles.color = Color.red; Handles.DrawLine(baseObj.position, fromObj.position); Handles.DrawLine(baseObj.position, toObj.position); // fromObjの方向ベクトル a = (fromObj.position - baseObj.position).normalized; // toObjの方向ベクトル b = (toObj.position - baseObj.position).normalized; var outerProduct = Vector3.Cross(a, b); Handles.DrawLine(baseObj.position, outerProduct); Handles.Label(baseObj.position + Vector3.back * 0.5f, "<color=#ffffff>" + outerProduct + "</color>", gUIStyle); } |
上のスクリプトではBaseObjからFromObjの単位ベクトルとBaseObjからToObjの単位ベクトルの外積を求めBaseObjからそのベクトルへ線を引いています。
終わりに
方向と角度を使ったメソッドは種類が結構ある割にどれも似たような感じで使い分けが難しいですね。
実際にサンプルを使って操作をすると少しわかってくるかと思いましたが、回転軸を変更したり、きっちり90度単位で角度を変更しないとなかなか分かり辛いですね。(´Д`)
サンプルをグリグリ動かして試してみてください。
参考サイト
UnityスクリプトリファレンスーVector3.Angleー
UnityスクリプトリファレンスーVector3.SignedAngleー
UnityスクリプトリファレンスーQuaternion.Angleー
UnityスクリプトリファレンスーQuaternion.AngleAxisー
UnityスクリプトリファレンスーQuaternion.FromToRotationー