お米 is ライス

C#やらUnityやらを勉強していて、これはメモっといたほうがええやろ、ということを書くつもりです

UnityでRotation(Quaternion)をうまく使いたい

前置き

Unityは3Dのゲームをゴリゴリ作ることができるように設計されたアレだ。
当然、三次元空間上での操作ができるようにいろいろ用意されている。
その中で根幹を担っているのが、すべてのゲームオブジェクトにくっついてくる"Transform"コンポーネントである。

uGUIが導入された最近では"RectTransform"コンポーネントというものもあるのだが、これとてTransformから継承したクラスに過ぎない。

そんな重要なものなのであるが、それゆえに(私を含む)Unity初心者がつまづきやすいところでもある。

Transformは「Position, Rotation, Size」という3つの項目で物体の3D空間上の状態を保持しているのだが、
なにが分かりにくいかというと、"Rotation"という項目なのである。

Rotationの難点

Rotationという項目は読んで字のごとく物体の回転状態を表している。
インスペクター上で見ると以下のようになっていて、回転が度数法(360度で一周するもの)を使って表示されている。
http://i.gyazo.com/5e91ca312cba46036a85bac95bd47be3.png

うむ、じゃあこの物体はX軸に90度、Y軸に30度、Z軸に10度回転しているのだな、と理解しそうになるのだがちょっと待ってほしい。
というかこのページはRotationで困っている人しか見ていないと思うのでもうわかっているとは思うが、そういう理解ではうまく動かないのである。
なぜなら(自分も詳しくないのだが)三次元空間上では物体を回転させるのに3つの情報だけでは無理なのである。
すなわち回転の軸を決めるベクトル(3次元ベクトル)と、どれだけ回転させるか(ただの数字)の情報が必要なのだ。

じゃあどないすんねん・・・、とここまで読んだ人々は嘆息するかもしれない、実際私もそうである。
敵の攻略にはまず敵を知ることから始めねばなるまい。

結論から言うと、Rotationは"Quaternion"という構造体であらわされている。
どういうことかというと以下の通りだ。

transform.rotation = new Vector3(x, y, z); //これは間違い

transform.rotation = new Quaternion(x, y, z, w); //これが正しい

これは四元数というもので、ごく簡単に言ってしまうと複素数を拡張したものである。
すなわち実数部分と虚数部分×3という4つのパラメータから成っている数なのだ。

(少し勉強したことのある人なら知っていると思うが、複素数というのは二次元上の回転を表すのに便利なことが知られている。
そして四元数複素数の拡張なのである。
すなわち三次元上の回転を表すのに便利だ。)

数学的な知識は皆無に等しいので突っ込んだ話はできないが、とにかく回転は4つのパラメータで表さないといけない。それなのにインスペクター上では3つのパラメータしか見えない。
これこそが、そもそもの混乱の原因なのである。
先述のRectTransformにも、見えているパラメータと実際に使われているパラメータが違うことが原因で生じる混乱があり、Unityではこういうことが多々あるので、インスペクターを信じ切ってしまうことはあまりよろしくないのである。

※追記
指摘があったので少し言い直しておくと、回転は一応3次元ベクトルで表すことができる。しかしこのときに注意しないといけないのが3次元ベクトルに対する回転は一意ではないということなのである。どの成分から回転させるかによって結果が変わってしまうのである。したがってインスペクタ上でX軸に何度、Y軸に何度、Z軸に何度と順番に指定した場合と、Z軸に何度、Y軸に何度、X軸に何度と指定した場合では異なる状態となってしまうのだ。


では、これからこのQuaternionとの付き合い方について見ていこう。

理解するな、関数を使え

正直言って人間は三次元空間上にへばりついた下等な生き物にすぎないので3Dのものを3Dとしてそのままとらえることはできない。二つのお目々でできることといえば、せいぜい二次元画像を取得し、それを脳みその中で頑張って処理し、3Dなものとして錯覚させることぐらいなのである。

だから三次元の回転を理解しようとしてはいけない。そもそも無理なのである。
ただ、数学や物理といったすさまじい学問をやっている人たちに限ってはそれを理解していて、数字に落とし込むことに成功している。我々のなすべきことはそういった賢人たちの知恵を借りることなのである。

そしてそういった賢人たちの知恵は、Unityでも「関数」として我々愚人たちが簡単にアクセスできるように用意されている。

Quaternionの使い方

それでも度数法で回転させたい

やっぱりQuaternionなんかめんどくさい、という人はこの項目を見るといい。

Quaternion.Euler

という関数がある。
使い方は至って簡単で、

transform.rotation = Quaternion.Euler(90, 30, 10);

という風に、度数法で表した回転の三次元ベクトルを突っ込んであげればいいのである。
思った通りに動いてくれるとは限らないが、たいていの場合はこれで事足りるだろう。

難しいことは考えずに、とにかくある方向を向かせたい

そういう人はこの項目を見てくれると助かる。

transform.LookAt

という関数がある。
たとえば(10, 20, 30)という座標に対して物体を向かせたいなら

transform.LookAt(10, 20, 30);

としてやればいい。
これで回転を全くしていない初めの状態で、Z軸正の方向を向いていたところが(10, 20, 30)という座標を向いてくれる(伝われ)。

ある軸の周りにいくらか回転させたい

このあたりからQuaternionを使わないとどうしようもなくなってくる。

transform.Rotate

という関数がある
これを使って、例えば(0, 1, 0)というベクトル(すなわちY軸)を軸にして90度回転させたい!と思うなら、

transform.Rotate(new Vector3(0, 1, 0), 90);

としてやればいい。Y軸などだけではなく、(0, 30, 60)といった変なベクトルでも同じことだ。
特にTransformは物体が向いている方向などを

transform.forward  //物体が向いている方向
transform.right  //物体から見て右側
transform.up       //物体から見て上側

という風にVector3で取得することができるので、たとえば戦闘機なんかがスピンするとき(伝われ)には

float angle = 1;
transform.Rotate(transform.forward, angle);

とでもしてあげればよいのである。

あるベクトルをある軸で回転させたい

たとえばキャラクターを動かすスクリプトを書きたいとき、InputうんたらとしてHorizontalの入力とVerticalの入力を受け取った時にどうすればよいのか。
キャラクターが常に正面を向いていればそのままpositionにそれらを足してやればいいのだが、そうでない場合そのままでは「キャラクターが向いている方向に対して右」などといったベクトルを取得するにはめんどくさい処理を書かないといけなさそうだ。

そこでQuaternionとVector3との掛け算を使う。
すなわち、(horizontal, vertical)という入力を受け取った時、Y軸に対してangle度回転しているキャラクターの正面に向かってvertical、右側に向かってhorizontal分進ませたいときは、

float horizontal = Input.GetAxis("Horizontal");
float vertical = Input.GetAxis("Vertical");

transform.Translate(Quaternion.AngleAxis(angle, Vector3.up) * new Vector3(horizontal, 0, vertical));

ということができる。
何をしているのかというと「Quaternion.AngleAxis(angle, Vector3.up)」という関数を使って、Y軸にangle度だけ回転させるQuaternionを取得し、それを「new Vector3(horizontal, 0, vertical)」というベクトルにかけることによってこのベクトルを回転させているのだ。
これが本来のQuaternionの使い方である。
順番は必ず「Quaternion × Vector3」の順でなければいけない。

ここではキャラクターの移動という目的に対して変な手段を用いて解決したが、この場合は先ほど紹介した"transform.forward"などを用いて簡単に、

transform.Translate(transform.forward * vertical + transform.right * horizontal);

という風に書ける、ということも追記しておく。

オブジェクトを連続的に回転させたい

Quaternion.Slerp

想像するならば、物音に気付いてこちらを振り返る敵や、戦闘機がスピンするとき。
一瞬でこちらを向くのではなく、ゆっくりと連続的に向いてほしいときは、

Quaternion from;
Quaternion to;
float t = 0;
public void Update()
{
    if(t < 1)
        t += Time.deltaTime;
    transform.rotation = Quaternion.Slerp(from, to, t);
}

などというふうにしてあげればよい。
そうすると"from"という角度から"to"という角度へ、"t"という割合だけ寄った角度を計算してくれる。ここで"t"は0から1までの少数である。
このサンプルでは、初め"from"という角度を向いていたオブジェクトが、1秒かけてゆっくりと"to"という角度を向く、という処理を行っている。