[Unity][C# Script] 回転を自在にあやつろう。

11 min read読了の目安(約10500字

はじめに

その Slerp、使い方はあってますか?
はい、私は間違ってました(真顔

さて、GameObject(ゲームオブジェクト) の rotation はクォータニオン型なのですが、実際に使われるときはVector3型で行うことも少なくありません。
 そして、 Vector3型の使い方がベクトル(方向)と座標と角度の3つあるのですが、あまり説明されていないのでさらに混同しやすいという罠。
 そしてウェブのサンプルを動かしているとき、たまーに期待していない挙動をしめすものがあったので、首をひねりながら調べたてことをまとめてみました。さあこれでスッキリです。

Vector3型は、使いみちに応じてサンプルでは以下のように表記します。
・方向は Direction。単位ベクトルの長さがUnityの距離単位になる。
・座標は Position。単位はUnityの距離単位(m / 物理演算時)
・角度(または回転量)は Angle。X,Y,Zがそれぞれ角度。単位は°(度)
また、各サンプルの target はターゲットの GameObject を想定しています。

1.ゲームオブジェクトの向きの操作

1) ゲームオブジェクトの向きは、transform.rotation
 ゲームオブジェクトが向いている方向は、GameObject.transform.rotation であり、クォータニオン(Quaternion)型です。

Quaternion direction = transform.rotation;

2) 他のゲームオブジェクトとrotationを一致させる
 Quaternion 型なので直接代入して問題ありません。

transform.rotation = target.transform.rotation;
transform.rotation = Quaternion.identity; // ワールド座標の正面

他にも、Quaternion 型であれば直接代入できます。

3) ゲームオブジェクトの向きを、度数で指定する。
 transform.rotation の向きを度数で指定します。
 このときの Vector3型は、角度として扱われています。

// a)
transform.rotation = Quaternion.Euler( 0f, 0f, 10f); // Z軸に10°回転

// b)
transform.eulerAngles = new Vector3( 0f, 0f, 10f); // 同上

4) ゲームオブジェクトの向きをVector3型(角度)で取り出す。
 ゲームオブジェクトの向き(transform.rotation)はクォータニオン型のため、スクリプト内で角度を制御する場合はVector3型のほうが扱いやすいことが多いです。Inspectorの数値とも一致しますし。

// a)
Vector3 myAngle = transform.forword; // 結果はVector3型

// b)
Vevtor3 myAngle = transform.eulerAngle; // 同上

5) ゲームオブジェクトの向きをfloat型の角度(度数法)で取り出す。
 ゲームオブジェクトの向きの、xyzどれかひとつの軸の角度のみを取り出します。

float myAngleY = transform.eulerAngle.y; // y軸の回転量

6) ゲームオブジェクトを度数法(0~360°)でゆっくり回転させる。
 単純に回転させる場合はこれが便利ですね。サンプルは1秒ごとに120度回転します。

transform.Rotate( 0f, 120.0f * Time.deltaTime ,0f );

るGameObjectを円周軌道にそって移動させたい場合は、回転させたい中心に空の GameObject をおいてそれを親GameObjectに設定し、親のtransform.rotationを上のスクリプトで回転させるとよいでしょう。

targetをスクリプトから親にするとき。
transform.parent = target.transform;

7) Vector3型(座標)を使ってそちらを向かせる。
 target.transform.position は Vector3型。座標として扱われています。

transform.LookAt ( target.transform.position ); // target は GameObject型

8) ゆっくりとVector3型(座標)を向かせる。
 ホーミングの仕組みは、これで簡単に作れそうですね。
 サンプルは1秒間に120度の速度で回転します。

transform.rotation = Quaternion.RotateTowards ( transform.rotation, 
 Quaternion.LookRotation( target.transform.position - transform.position ),
 120.0f * Time.deltaTime );

(少し解説)
・Vector3 direction = target.transform.position - transform.position;
 targetのVector3型(方向)を算出
・Quaternion rotation = Quaternion.LookRotation( direction );
 Vector3型(方向)から Quaternion型(向き)に変換。
・transform.rotation = Quaternion.RotateTowards ( transform.rotation, rotation, 120.0f * Time.deltaTime );
 今の向きからtargetの向きに、1秒間に120度の速度で回転

9) 回転座標をy軸の回転のみに限定(アジャスト)する。

transform.rotation = Quaternion.Euler(new Vector3( 0f, transform.eulerAngle.y, 0f ));

相手と上下にずれていると、オブジェクトも上下を向くので補正したりするのに使います。

2. Quaternion型の操作

Quaternion型をVector3型に変換したり、またその逆の操作は、その仕組上計算コストが高いと考えています。なるべくQuaternion型のまま扱うことができれば、そのような心配がなくなるので、Quaternion型がどう扱えるか少々調べてみました。

Quaternion型は内部で 4つの数値 (Unity では x、y、z、w)を持ちます。この数値はどうやら専門的な知識なしにそれを直接操作しても期待した結果は得られないようで、実際には、Quaternion型はそのメソッドを使い操作することがメインとなるようです。
 ちょっと調べてみましたが、パターンはそれほど多くなさそうですね。

1) Quaternion型を 単位回転(identity)で初期化する。
 回転していないクォータニオンにて初期化します。つまり、worldの正面(哲学)を向きます。

Quaternion rotation = Quaternion.identity;

2) Quaternion型に Vector3型(角度)を代入する。
 Vector と Quaternion の相互変換を行う方法です。なお、Quaternionは0~360度に相当する数値しか保持しないようです。

// a)
Quaternion rotation = Quaternion.Euler(new Vector3(0, 30, 0));

// b)
Quaternion rotation = Quaternion.identity;
rotation.eulerAngles = new Vector3(0, 30, 0);

3) Quaternion型を Vector3型(角度)に変換。
 Vector と Quaternion の相互変換を行う方法です。2)とは逆方向の型に変換します。

// a
Vector3 angle = Quaternion.eulerAngles;

// b
Vector3 angle = transform.forward; // GameObject の場合はこっちがいいかな

4) Quaternion型をfloat(度数法 / 0~360°)で取り出す。
 Quaternion から、xyzのうち1つの軸について角度を取り出します。

float angleY = Quaternion.eulerAngles.y; // y軸の回転量

-180° ~ 180° にする場合は、以下で変換するとよいでしょう。
angleY = angleY <= 180f ? angleY : (360-angleY );

5) 2つのVector3型(方向)の差から回転量(Quaternion型)を得る。
 2つのベクトル(Vector3型)の差からは、ベクトル(Vector3型)が導けます。これを回転量(Quaternion型)で取り出します。
 回転速度を制御したいときなどに使うことになるでしょう。

// fromDirection = Vector3型
// targetDirection = Vector3型
Quaternion requiredRotation = Quaternion.FromToRotation(fromDirection,
                                               targetDirection);

6) Vector3型(方向)から回転量を得る。(上下の回転は抑止する)
 Quaternion.LookRotationは元となる位置とtargetの位置からtargetの方向を回転量(Quaternion型)として得ます。下のサンプルは第2パラメータが省略されているため、上下方向の回転量が抑止されています。
 ロックオンしたtargetを向かせたりするのに便利ですね。

transform.rotation = Quaternion.LookRotation( target.transform.position
                                              - transform.position );

public static Quaternion LookRotation (Vector3 forward, Vector3 upwards= Vector3.up);
・forward 向かせたい方向
・upwards 上方向を定義するベクトル。 Vector3.up は Vector3(0, 1, 0) と同意。
transform.LookAt とは、場合に応じた使い分けができそうですね。

7) 2つの角度の差を返す
 Quaternion型同士の差からQuaternion型の差を得るには、逆クォータニオンを掛けるとよいらしいです。すこし不思議。

// 必要な回転量を得る(Inverse したクォータニオンを乗算)
// targetRotation は Quaternion型
Quaternion requiredRotation = targetRotation
                             * Quaternion.Inverse(transform.rotation); 

8) 2つの角度の差を角度をfloat(度数法 / 0~360°)で返す。
 正面以外は無視するなどの判定に使えそうです。
 クォータニオンの差分で出力されるわけでないので、返した値を利用して物体を回転させる事は出来ないようです。

// targetRotation は Quaternion型
float angle = Quaternion.Angle(transform.rotation, targetRotation);  

9) ゆっくりと対象の方向を向ける(球面回転)
 1秒かけて回転するサンプルです。

// float timeCount = 0f; 別の場所で定義
// Quaternion fromRotation = transform.rotation;
// Quaternion targetRotation = Quaternion.LookRotation( target.transform.position - transform.position );

transform.rotation = Quaternion.Slerp ( fromRotation, targetRotation, timeCount  );
timeCount += Time.deltaTime;

※球面回転と非球面回転は以下と考えてください。
 球面回転  精度:高 処理速度:低
 非球面回転 精度:低 処理速度:高

10) ゆっくりと対象の方向を向ける(非球面回転)
 1秒かけて回転するサンプルです。

// float timeCount = 0f; 別の場所で定義
// Quaternion fromRotation = transform.rotation;
// Quaternion targetRotation = Quaternion.LookRotation( target.transform.position - transform.position );

transform.rotation = Quaternion.Leap ( fromRotation, targetRotation, timeCount  );
timeCount += Time.deltaTime;

11) ゆっくりと対象の方向を向ける(等速・回転量指定)
 自分がいま向いている方向から targetのある向きにむけて旋回します。
 下のサンプルでは、1秒間120°の割合で等速回転します。LookRotationを使っているので上下の回転は抑止されています。
 おそらくこれが一番使われることが多いのではないでしょうか。

Quaternion targetRotation = Quaternion.LookRotation( target.transform.position - transform.position );
  transform.rotation = Quaternion.RotateTowards( transform.rotation, targetRotation, 120f*Time.deltaTime );

12) ゆっくりと対象の方向を向ける(非等速・回転量指定)
 下のサンプルは、1秒間にかけて目的の方向に回転しようとします。角度が離れている場合は回転量が多く、近づくにつれ補正の割合が小さくなります。
 よって、指定された方向をそのものを向くことはありません(哲学)

Quaternion targetRotation = Quaternion.LookRotation( target.transform.position - transform.position );
  transform.rotation = Quaternion.Lerp( transform.rotation, targetRotation, Time.deltaTime );

サンプル等で回転にSlerpやLerpを使っているものを参考にする場合、意図した使いかたかどうかに留意してください。

13) クォータニオン同士の合成
 クォータニオン同士の合計はクオータニオン同士を*(掛け算)することで求められます。

Quaternion Rotation30 = Quaternion.Euler(new Vector3(0, 30, 0));
Quaternion Rotation60 = Quaternion.Euler(new Vector3(0, 60, 0));
Quaternion Rotation90 = Rotation30 * Rotation60;

3. Vector3型のベクトル(方向)、座標、角度の操作

1) 2つの位置間の角度を得る
 方向制御するために、2つの位置感の角度を得る。よく使う操作ですが、専用のメソッドが用意されているわけではないようです。

// a) Atan2をつかう(サンプルはy軸。高低差は考慮しない)
float angleY = Mathf.Atan2( target.transform.position.x - transform.position.x , target.transform.position.z - transform.position.z ) * Mathf.Rad2Deg;
   
// b) Vector3.Angle
// ベクトルの差分で出力されるわけでないので、返した値を利用して物体を回転させる事は出来ない
Vector3 targetDir = target.transform.position - transform.position;
float angle = Vector3.Angle(targetDir, transform.forward);

2) ベクトルの正規化
 ベクトルの大きさが単位ベクトルになるよう調整します。

Vector3 direction = target.transform.position - transform.position;
Vector3 normalizedDirection = direction.normalized;

3) ベクトルの長さ(読み取り専用)
 ベクトルの長さは、2点間の距離を求めるのに使われます。便利です。

Vector3 direction = target.transform.position - transform.position;
float distance = direction.magnitude;

ピュタゴラスの定理
Mathf.Sqrt( Mathf.Pow( target.transform.position.x - transform.position.x ,2) + Mathf.Pow( target.transform.position.z - transform.position.z ,2) );

4) ベクトルの長さの2乗(読み取り専用)
 ベクトルの長さは、2点間の距離を求めるのに使われます。便利ですがそれゆえに多用されます。ちょっとでも処理を軽くしたい場合はこちらを使うべきかもしれません。

Vector3 direction = target.transform.position - transform.position;
float sqrDistance = direction.sqrMagnitude;

特に問題がない場合、ベクトルの長さはできるだけ2乗同士のまま扱ったほうが処理が速いことが期待できる。平方根の演算をはさまないため。

5) じわじわと回転
 自分がいま向いている方向から targetのある向きにむけて旋回します。結果をQuaternionではなくVector3で得る必要がある場合は、こちらを使うことになります。

Vector3 targetDir = target.transform.position - transform.position;
Vector3 newDirection = Vector3.RotateTowards( transform.forward, targetDirection, 120.0f * time.deltaTime );

6) 直線上にある 2 つのベクトル間をゆっくりと補間する
 時間値をもとに、直線上のどこに位置するかを求める必要があれときは、これを使うのが便利そうです。

// timeCount = 0; 別の場所で定義

transform.position = Vector3.Lerp( startMarker.position, endMarker.position, timeCount );
timeCount += Time.deltaTime;

4.(解説)クォータ二オンとオイラー角について

Unityユーザーズマニュアル「Unity の回転と向き」より

3D アプリケーションの回転は、たいていクォータ二オンかオイラー角のどちらかによって表されています。
それぞれ、利点と欠点があります。Unity では内部でクォータ二オンを採用していますが、編集しやすいように、インスペクターでは同等のオイラー角で表しています。

オイラー角とクォータ二オンの違い

・オイラー角(Euler angles)
オイラー角は、3つの角度の値を X、Y、Z 軸に順に当てはめて回転を表す簡易な方法です。
オイラー角をあるオブジェクトに応用する場合は、オブジェクトを各軸に沿って与えられた角度で回転させます。

利点 - オイラー角は、3つの角度から構成される直感的で「人間が理解できる」フォーマットで表現されています。
利点 - オイラー角は、180度以上の回転を経て、1つの向きから別の向きへの回転を表現できます。
制限 - オイラー角はジンバルロック (Gimbal lock) として知られている制限があります。

ジンバルロックについて
対象をそれぞれの軸に沿って順に回転させるとき、第一、第二の回転によって、結果的に 3つ目の軸にそろってしまうことがあります。
つまり、第三の回転値を 3つ目の軸に反映できないために、「角度の自由」が失われてしまいます。

・クォータニオン(Quaternion)

クォータニオンは、オブジェクトの向きや回転を表すのに使用されます。
この表現法では、内部で 4つの数字 (Unity では x、y、z、w と呼びます) で構成されています。
ただし、この数字は、角度や軸を表現しているわけでなく、通常、直接アクセスすることはありません。
クォータ二オン (四元数)について特別に興味がない限り、クォータニオンが 3D 空間の回転を表しているのだということを知っているだけで十分で、
通常、x、y、z プロパティーを操作したり変更することはありません。

ベクトルが位置と方向 (方向は原点から測ります) を表現するのと同様に、クォータ二オンは向きと回転を表現できます。
回転は、回転「基準」か「単位」を基準に測られます。回転を、ある向きからもう一方の向きへの方向として計測するため、クォータ二オンでは、180度より大きな回転を表現することができません。

利点 - クォータ二オンの回転は、ジンバルロックの影響を受けません。
制限 - 単体のクォータ二オンでは、180度を超す回転を表すことができません。
制限 - クォータ二オンの数的表現は、直感的に理解できません。
Unity では、ゲームオブジェクトのすべての回転は、内部ではクォータ二オンで保存します。なぜなら、利点のほうが、制限を上回るためです。

しかし、Transform Inspector では、回転は、理解しやすく編集しやすいオイラー角で表示されます。
ゲームオブジェクトを回転させるために Inspector に新しい値を入力すると、目に見えない内部で、新しいクォータニオン回転値に変換されます。

2021.2.17 noteより転記
2021.2.19 一部修正。言葉遣いの統一など