[Unity][C# Script] 回転を自在にあやつろう。
はじめに
その Slerp、使い方はあってますか?
はい、私は間違ってました(真顔
さて、GameObject(ゲームオブジェクト) の rotation はクォータニオン型なのですが、実際に使われるときはVector3型で行うことも少なくありません。
そして、 Vector3型の使い方がベクトル(方向)・座標・角度の3つあるのですが、これをあまり区別して説明されてなかったり、操作する組み合わせもそのぶんあり混同しやすいという罠。
そしてウェブのサンプルを動かしているとき、たまーに期待していない挙動をしめすものがあったので、首をひねりながら調べたてことをまとめてみました。さあこれでスッキリです。
Vector3型は、使いみちに応じてサンプルでは以下のように表記します。
・方向(移動量)は Direction。単位ベクトルの長さがUnityの距離単位になる。
・座標は Position。単位はUnityの距離単位(m / 物理演算時)
・角度(オイラー角・回転量)は Angle。X,Y,Zがそれぞれ角度。単位は°(度)
・クォータニオンは Rotation で表記します。
また、各サンプルの target はターゲットの GameObject を想定しています。
1.ゲームオブジェクトの向きの操作
1) ゲームオブジェクトの向きは、transform.rotation
ゲームオブジェクトが向いている方向は、GameObject.transform.rotation であり、クォータニオン(Quaternion)型です。
// trQ → Q
Quaternion myRotation = transform.rotation;
2) 他のゲームオブジェクトとrotationを一致させる
Quaternion 型なので直接代入して問題ありません。
// Q → trQ
transform.rotation = target.transform.rotation;
transform.rotation = Quaternion.identity; // ワールド座標の正面
他にも、Quaternion 型であれば直接代入できます。
3) ゲームオブジェクトの向きを、度数で指定する。
transform.rotation の向きを度数で指定します。
このときの Vector3型は、角度として扱われています。
// a) 回転(v3Angle → trQ (加算))
transform.Rotate ( 0f, 0f, 10f ); // Z軸を10°回転
// b) 角度指定(v3Angle → trQ)
transform.rotation = Quaternion.Euler( 0f, 0f, 10f); // Z軸を10°に設定
// c) 角度指定(v3Angle → trQ)
transform.eulerAngles = new Vector3( 0f, 0f, 10f); // 同上
4) ゲームオブジェクトの向きをVector3型(角度)で取り出す。
ゲームオブジェクトの向き(transform.rotation)はクォータニオン型のため、スクリプト内で角度を制御する場合はオイラー角(Vector3型)のほうが扱いやすいことが多いです。Inspectorの数値とも一致しますし。
// trQ → v3Angle
Vevtor3 myAngle = transform.eulerAngles; // 結果はVector3(Angle)型
5) ゲームオブジェクトの向きをVector3型(方向)で取り出す。
ゲームオブジェクトの向きをベクトル量で取り出します。たとえばゲームオブジェクトを前に進めるときの移動量を求めるときや、ゲームオブジェクトの前方の座標を得たいときなどは、これを使います。
// trQ → v3Direction
Vector3 myDirection = transform.forword; // 結果はVector3(Direction)型
射出するオブジェクトの位置をオフセットするような場合は、以下のようにするとよいでしょう。
// v3Direction Offset(前に1.0、上に0.1、左に0.1オフセット)
transform.position += (transform.forword *1.0f)
+ (transform.up *0.1f)
+ (transform.right * -0.1f);
6) ゲームオブジェクトの向きをfloat型の角度(度数法)で取り出す。
ゲームオブジェクトの向きの、xyzどれかひとつの軸の角度のみを取り出します。
// trQ → v3Angle
float myAngleY = transform.eulerAngle.y; // y軸の回転量
// 実質、オブジェクトの向いている角度になります。
7) ゲームオブジェクトを度数法(0~360°)でゆっくり回転させる。
単純に回転させる場合はこれが便利ですね。サンプルは、1秒ごとにY軸(xz平面)にそって120度回転します。
// 回転(v3Angle → trQ)
transform.Rotate( 0f, 120.0f * Time.deltaTime ,0f );
GameObjectを円周軌道にそって移動させたい場合は、回転させたい中心に空の GameObject をおいてそれを親GameObjectに設定し、親のtransform.rotationを上のスクリプトで回転させるとよいでしょう。このように、オブジェクトを回転させたり傾けたりする場合は、オブジェクトの角度を直接操作するよりは、回転させたり傾けたりするパラメータの数に応じた親オブジェクトを作り、それを操作したほうが単純になることが多いと思います。
targetをスクリプトから親にするとき。
transform.parent = target.transform;
8) Vector3型(座標)を使ってそちらを向かせる。
target.transform.position は Vector3型。座標として扱われています。
// v3Position → trQ
transform.LookAt ( target.transform.position ); // target は GameObject型
9) ゆっくりとVector3型(座標)を向かせる。
ホーミングの仕組みは、これで簡単に作れそうですね。
サンプルは1秒間に120度の速度で回転します。
// trQ と v3Position → trQ
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度の速度で回転
10) 回転座標をy軸の回転のみに限定(アジャスト)する。
// v3Angle → Q
transform.rotation = Quaternion.Euler(new Vector3( 0f, transform.eulerAngles.y, 0f ));
相手と上下にずれていると、オブジェクトも上下を向くので補正したりするのに使います。
2. Quaternion型の操作
Quaternion型をVector3型に変換したり、またその逆の操作は、その仕組上計算コストが高いと考えています。なるべくQuaternion型のまま扱うことができれば、そのような心配がなくなるので、Quaternion型がどう扱えるか少々調べてみました。
Quaternion型は内部で 4つの数値 (Unity では x、y、z、w)を持ちます。この数値はどうやら専門的な知識なしにそれを直接操作しても期待した結果は得られないようで、実際には、Quaternion型はそのメソッドを使い操作することがメインとなるようです。
ちょっと調べてみましたが、パターンはそれほど多くなさそうですね。
1) Quaternion型を 単位回転(identity)で初期化する。
回転していないクォータニオンにて初期化します。つまり、worldの正面(哲学)を向きます。
// Q → Q
Quaternion rotation = Quaternion.identity;
2) クォータニオン同士の合成
クォータニオン同士の合計はクオータニオン同士を*(掛け算記号による演算)することで求められます。
// Q → Q
Quaternion Rotation30 = Quaternion.Euler(0f, 30f, 0f);
Quaternion Rotation60 = Quaternion.Euler(0f, 60f, 0f);
Quaternion Rotation90 = Rotation30 * Rotation60;
3) 2つの角度の差を返す
Quaternion型同士の差からQuaternion型の差を得るには、逆クォータニオンを掛けるとよいらしいです。すこし不思議。
// Q → Q
// 必要な回転量を得る(Inverse したクォータニオンを乗算)
// targetRotation は Quaternion型
Quaternion requiredRotation = targetRotation
* Quaternion.Inverse(transform.rotation);
4) Quaternion型に Vector3型(角度)を代入する。
Vector(オイラー角) と Quaternion の相互変換を行う方法です。なお、Quaternionは0~360°に相当する数値しか保持しないようです。
// a) v3Angle → Q
Quaternion rotation = Quaternion.Euler(new Vector3(0f, 30f, 0f));
// b) v3Angle → Q
Quaternion rotation = Quaternion.identity;
rotation.eulerAngles = new Vector3(0f, 30f, 0f);
5) Quaternion型を Vector3型(角度)に変換。
Quaternion と Vector3(オイラー角) の相互変換を行う方法です。2)とは逆方向の型に変換します。取り出される範囲はそれぞれ0~360°になります。
// Q → v3Angle
Vector3 angle = Quaternion.eulerAngles;
6) Quaternion型を Vector3型(方向)に変換。
GameObjectであれば、transform.forwardで向きをベクトル量に変換できます。Quatanion型でこれと同等の操作をしたいときは、Vector3.forwardを*(掛け算記号による演算)します。
// a) Q(rotationQ) → v3Direction
Vector3 direction = rotationQ * Vector3.forward;
7) Quaternion型をfloat(度数法 / 0~360°)で取り出す。
Quaternion から、xyzのうち1つの軸について角度を取り出します。
float angleY = Quaternion.eulerAngles.y; // y軸の回転量
-180° ~ 180° にする場合は、以下で変換するとよいでしょう。
angleY = angleY <= 180f ? angleY : (360f - angleY );
8) Vector3型(座標)から回転量(Quaternion型)を得る。
元となる位置とtargetの位置からtargetの方向を回転量(Quaternion型)として得ます。これをtransform.rotatonに代入することも可能ですが、その場合はおそらく通常はtansform.LookAtを使ったほうがスッキリすることが多いはずです。
// a v3Position → Q
transform.rotation = Quaternion.FromToRotation(transform.position,
target.transform.position );
9) Vector3型(方向)から回転量を得る。(上下の回転は抑止する)
Quaternion.LookRotationは方向(移動量)から回転量を得ます。下のサンプルは第2パラメータが省略されているため、上下方向の回転量が抑止されています。
// v3Direction → Q
transform.rotation = Quaternion.LookRotation( target.transform.position
- transform.position );
public static Quaternion LookRotation (Vector3 forward, Vector3 upwards= Vector3.up);
・forward 向かせたい方向
・upwards 上方向を定義するベクトル。 Vector3.up は Vector3(0f, 1f, 0f) と同義。
transform.LookAt とは、場合に応じた使い分けができそうですね。
10) 回転量(Quaternion型)から回転量(Quaternion型)へ制限つき回転を行う。
transfom のホーミングの仕組みで利用したQuaternion.RotateTowardsです。
// Q → Q
// toRotation = 目標のRotation
// step = 回転量制限(サンプルは120°/秒)
transform.rotation = Quaternion.RotateTowards( ransform.rotation,
toRotation, 120.0f * Time.deltaTime );
11) 2つの角度の差を角度をfloat(度数法 / 0~360°)で返す。
正面以外は無視するなどの判定に使えそうです。
クォータニオンの差分で出力されるわけでないので、得た値を利用して物体を回転させる事は出来ないようです。
// targetRotation は Quaternion型
float angle = Quaternion.Angle(transform.rotation, targetRotation);
12) ゆっくりと対象の方向を向ける(球面回転)
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;
※球面回転と非球面回転は以下と考えてください。
球面回転 精度:高 処理速度:低
非球面回転 精度:低 処理速度:高
13) ゆっくりと対象の方向を向ける(非球面回転)
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;
14) ゆっくりと対象の方向を向ける(非等速・回転量指定)
下のサンプルは、1秒間にかけて目的の方向に回転しようとします。角度が離れている場合は回転量が多く、近づくにつれ補正の割合が小さくなります。
よって、指定された方向をそのものを向くことはありません(哲学)
Quaternion targetRotation = Quaternion.LookRotation( target.transform.position
- transform.position );
transform.rotation = Quaternion.Lerp( transform.rotation, targetRotation,
Time.deltaTime );
サンプル等で回転にSlerpやLerpを使っているものを参考にする場合、意図した使いかたかどうかに留意してください。
15) ドリフトするホーミングミサイル
ホーミングミサイルで、ミサイルの向きに加速する表現です。
自分がいま向いている方向から targetのある向きにむけて旋回し、その方向に加速しますが、旋回速度によっては進行方向とミサイルの向きがアンマッチになり、ドリフトしているように見えます。
下のサンプルでは、1秒間に120°の割合で等速回転します。LookRotationを使っているので上下の回転は抑止されています。
// Vector3 moveSpeed = new Vector3(0f,0f,0f); 別の場所で定義
// float accSpeed = 3.0f; 別の場所で定義
// float maxSpeed = 3.0f; 別の場所で定義
Quaternion targetRotation = Quaternion.LookRotation( target.transform.position
- transform.position );
transform.rotation = Quaternion.RotateTowards( transform.rotation,
targetRotation, 120f*Time.deltaTime );
moveSpeed += transform.rotation * Vector3.forward * accSpeed * Time.deltaTime;
if( moveSpeed.magnitude >= maxSpeed )
{
moveSpeed = speed.normalized * maxSpeed;
}
3. Vector3型のベクトル(方向)、座標、角度の操作
1) 2つの位置間の角度を得る
方向制御するために、2つの位置間の角度を得るのはよく使う操作ですが、専用のメソッドが用意されているわけではないようです。(ただ、unityでは実際には操作対象はtransformである場合が多いでしょうから、これを使う機会はあまりないかもしれません)
// v3Position → v3Angle
// a) Atan2をつかう(サンプルはy軸。高低差は考慮しない)
float angleY = Mathf.Atan2( target.transform.position.x - transform.position.x , target.transform.position.z - transform.position.z ) * Mathf.Rad2Deg;
// -180~180°となる模様
// b) Quaternion.FromToRotationを使う(サンプルはy軸。高低差は考慮しない)
Quaternion rotation = Quaternion.FromToRotation(fromDirection,targetDirection);
float angleY = rotation.eulerAngles.y;
// 0~360°となる模様
// v3Position → floatAngle
// c) Vector3.Angle(サンプルは対象の位置への方向と、向いている方向との差)
// ベクトルの差分で出力されるわけでないので、返値を利用して物体を回転させる事は出来ない
Vector3 targetDir = target.transform.position - transform.position;
float angle = Vector3.Angle(targetDir, transform.forward);
2) ベクトルの正規化
ベクトルの大きさが単位ベクトルになるよう調整します。
// v3Direction → v3Direction
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で得る必要がある場合は、こちらを使うことになります。
// v3Position → v3Direction
Vector3 targetDir = target.transform.position - transform.position;
Vector3 newDirection = Vector3.RotateTowards( transform.forward, targetDirection, 120.0f * time.deltaTime );
6) 直線上にある 2 つのベクトル間をゆっくりと補間する
時間値をもとに、直線上のどこに位置するかを求める必要があれときは、これを使うのが便利そうです。
// v3Position
// float timeCount = 0f; 別の場所で定義
transform.position = Vector3.Lerp( startMarker.position, endMarker.position, timeCount );
timeCount += Time.deltaTime;
7) 方向(移動量)を、回転量を指定して回転
方向(移動量)を回転する場合、三角関数を使用して回転することもできますが、回転量をQuaternionで表現できる場合は、クォータニオンにVector3を*(掛け算)するとすっきりする場合があります。
「Quaternion * Vector3」と、演算の順番は決まっていることに注意してください。
// v3Direction と Q → v3Direction
Vector3 targetDir = target.transform.position - transform.position;
Quaternion Rotation30 = Quaternion.Euler(new Vector3(0f, 30f, 0f));
Vector3 newDirection = Rotation30 * targetDir; // 必ずQuaternion * Vector3
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 に新しい値を入力すると、目に見えない内部で、新しいクォータニオン回転値に変換されます。
ex.関係ありそうな記事
2021.2.17 noteより転記
2021.2.19 一部修正。言葉遣いの統一など
2022.9.6、2022.10.9 2022.11.24 transform回転にまつわる記述・挙動ついての誤記を修正。
Discussion