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

本記事の最新版はzennに移行しております。

その 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;
Transform Inspector では、回転は、理解しやすく編集しやすいオイラー角で表示されます。

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型(角度)で取り出す。

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

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

5) ゲームオブジェクトの向きをfloat型の角度(度数法)で取り出す。

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.FromToRotation ( transform.rotation, 
 Quaternion.LookRotation( target.transform.position - transform.position ),
 120.0f * Time.deltaTime );

(少し解説)
・Vector3 direction = target.transform.position - transform.position;
 Vector3型(方向)を算出
・Quaternion rotation = Quaternion.LookRotation( direction );
 Vector3型(方向)を Quaternion型に変換。
・transform.rotation = Quaternion.FromToRotation ( transform.rotation, rotation, 120.0f * Time.deltaTime );
 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 の相互変換。

// a
Vector3 angle = Quaternion.eulerAngles;

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

4) Quaternion型をfloat(度数法 / 0~360°)で取り出す。

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

-180° ~ 180° にする場合は、
angleY = angleY <= 180f ? angleY : (360-angleY );

5) 2つのVector3型(方向)の差から回転量(Quaternion型)を得る。
 2つのベクトル(Vector3型)の差から回転(Quaternion型)を算出

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

6) Vector3型(方向)から回転量を得る。(上下の回転は抑止する)
 サンプルは 自分の位置 と 相手の位置 から方向を算出し、それをもとに回転量(Quaternion型)を得ている。
 Quaternion.LookRotation の第2パラメータを省略すると、上下方向の回転が抑止される。ターゲットした的を向いたりするのに便利。

transform.rotation = Quaternion.LookRotation( target.transform.position
                                              - transform.position );
public static Quaternion LookRotation (Vector3 forward, Vector3 upwards= Vector3.up);
・forward 向かせたい方向
・upwards 上方向を定義するベクトル。 Vector3(0, 1, 0) と同意。

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) ゆっくりと対象の方向を向ける(非球面回転)

// 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;
ゲームではこちら(非球面回転)が推奨と考えて良い。

10) ゆっくりと対象の方向を向ける(等速・回転量指定)
 1秒間120°の割合で等速回転する。おそらくこれを一番使う。

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

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

Quaternion targetRotation = Quaternion.LookRotation( target.transform.position - transform.position );
  transform.rotation = Quaternion.Lerp( transform.rotation, targetRotation, Time.deltaTime );
サンプル等で回転にSlerpやLerpを使っているものを参考にする場合、意図した使いかたかどうかに留意する。

12) クォータニオン同士の合成
 
クォータニオン同士の合計は掛け算となる。

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つの位置間の角度を得る

// 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) ベクトルの長さ(読み取り専用)

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乗(読み取り専用)

Vector3 direction = target.transform.position - transform.position;
float sqrDistance = direction.sqrMagnitude;
 特に問題がない場合、ベクトルの長さはできるだけ2乗同士のまま扱ったほうが処理が速い。平方根の演算をはさまないため。

5) 直線上にある 2 つのベクトル間をゆっくりと補間する

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

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

6) じわじわと回転

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

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

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 に新しい値を入力すると、目に見えない内部で、新しいクォータニオン回転値に変換されます。

良い記事をみつけたのでメモ。

2020.05.31 大幅改定

この記事が気に入ったらサポートをしてみませんか?