読者です 読者をやめる 読者になる 読者になる

お米 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ではこういうことが多々あるので、インスペクターを信じ切ってしまうことはあまりよろしくないのである。

では、これからこの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"という角度を向く、という処理を行っている。