【Unity】Quaternionでゲームで使えそうな回転を作ってみた

現在グレンジでUnityを用いたゲーム開発を行っているみくりやと申します。
[グレンジ Advent Calendar 2018](https://qiita.com/advent-calendar/2018/grenge) 3日目の記事になります。
去年も3日目だったな（[ZenjectでDIの真理を悟る](https://qiita.com/mikuri8/items/bb1867b0321b058796c5)）

QuaternionはUnityを使う人なら一度は通る道だと思います。Quaternionについての記事はたくさんありますが、今回はもうちょっと噛み砕いてゲームなどに応用しやすい初歩的な回転サンプルを作ってみたいと思います。

#Quaternionって？
 **細けぇことは一旦どうでもいいんだ、こいつは回転させてくれる** 

#注視する回転
指定オブジェクトを注視する回転です。

```
using UnityEngine;

public class QuaternionTest : MonoBehaviour {
    [SerializeField]
    private Transform _target;
    [SerializeField]
    private Transform _player;
	
	void Update () {
        LookTarget();
	}

    private void LookTarget() {
        _player.rotation = Quaternion.LookRotation(_target.position - _player.position);
    }
}
```
`_player`(Unityちゃん)が`_target`(とうふ)を見つめています。
`transform.LookAt`を使っているパターンもよく見かけますが、

![Dec-01-2018 15-38-51.gif](https://qiita-image-store.s3.amazonaws.com/0/20374/da77c64b-9c22-b9c3-c1d8-e446d27d598d.gif)


#指定方向に回転
指定キーが押されている間、指定方向に回転する処理です。

```
public class QuaternionTest : MonoBehaviour {
    [SerializeField]
    private Transform _player;

	void Update () {
        if (Input.GetKey(KeyCode.LeftArrow)) {
            OnKeyPressRotate(1);
        } else if (Input.GetKey(KeyCode.RightArrow)) {
            OnKeyPressRotate(-1);
        }

	}

    private void OnKeyPressRotate(int direction) {
        _player.rotation *= Quaternion.AngleAxis(1f * direction, _player.up);
    }
}
```
`Quaternion.AngleAxis`は **指定軸に対する単位回転** を表すので、`_player`の現在の回転にかけることで角度が加算されてくるくると回っていきます。
コードの例だとプレイヤーのy軸に対して1度回転します。

![Dec-01-2018 16-07-10.gif](https://qiita-image-store.s3.amazonaws.com/0/20374/c6e289e3-6c39-3209-1dee-cceba76caa15.gif)


今度は指定キーを押したときに一定角度回転する動きを作ってみます。

```
public class QuaternionTest : MonoBehaviour {
    [SerializeField]
    private Transform _player;

    private Quaternion _startRotation;
    private Quaternion _endRotation;
    private float _countTime;
    private bool _startRotate;

	void Update () {
        if (Input.GetKeyDown(KeyCode.LeftArrow)) {
            OnKeyDownRotate(1);
        } else if (Input.GetKeyDown(KeyCode.RightArrow)) {
            OnKeyDownRotate(-1);
        }

        UpdateKeyDownRotate();
	}

    private void OnKeyDownRotate(int direction) {
        _countTime = 0f;
        _startRotation = _player.rotation;
        _endRotation = _player.rotation * Quaternion.AngleAxis(45f * direction, _player.up);
        _startRotate = true;
    }

    private void UpdateKeyDownRotate() {
        if (_startRotate == false) {
            return;
        }
        _countTime = Mathf.Clamp(_countTime + Time.deltaTime, 0f, 0.5f);
        float rate = _countTime / 0.5f;
        _player.rotation = Quaternion.Lerp(_startRotation, _endRotation, rate);

        if (rate >= 1f) {
            _startRotate = false;
        }
    }
}
```
![Dec-01-2018 16-41-01.gif](https://qiita-image-store.s3.amazonaws.com/0/20374/cb90be03-738f-ff2f-901b-8c799a583718.gif)

指定キーを押したときに0.5秒かけて45度回転させています。
`Quaternion.Lerp`を用いて、`_startRotation`から`_endRotation`まで回転させていきます。
`_endRotation`の値は最初の例と同じ方法で指定しています。Quaternion自体は値なので動いているように見せるには今回のような方法が必要です。
rateの値を調節するとイージングをかけたりできますね。

#指定オブジェクト周りの回転
指定したオブジェクトの周りを回転させます

```
public class QuaternionTest : MonoBehaviour {
    [SerializeField]
    private Transform _target;
    [SerializeField]
    private Transform _player;
	
	void Update () {
        RotateArownd();
        LookTarget();
	}

    private void LookTarget() {
        _player.rotation = Quaternion.LookRotation(_target.position - _player.position);
    }

    private void RotateArownd() {
        _player.position = Quaternion.AngleAxis(1f, _target.up) * _player.position;
    }
}
```
![Dec-01-2018 17-25-45.gif](https://qiita-image-store.s3.amazonaws.com/0/20374/273e6ed6-7976-b832-6d14-9496c989e1e8.gif)


プレイヤーの座標に回転をかけることでいい感じにオブジェクトの周りを回転してくれます。
しかし、このままだと`_target`を動かしたときにおかしなことになります。

![Dec-01-2018 17-28-53.gif](https://qiita-image-store.s3.amazonaws.com/0/20374/02e762be-8d53-fc30-33f2-dbe45cdda4ba.gif)

Quaternionは **原点を中心とした回転** を表すのでその点を考慮しなければなりません。（今回は`_target`が原点にいたのでいい感じに見えてしまっていました）
`RotateArownd()`を以下のように改修します。

```
private void RotateArownd() {
    _player.position = _target.position + Quaternion.AngleAxis(1f, _target.up) * (_player.position - _target.position).normalized * 2f;
}
```
`_target`から`_player`に向いたベクトルを`Quaternion.AngleAxis`で回転させ`_target`の座標に加算することで位置を算出します。こうすることで`_target`起点の回転を作ることができました。今回は半径2の位置に移動するようにnormalizedしたものに2をかけています。

![Dec-01-2018 17-48-33.gif](https://qiita-image-store.s3.amazonaws.com/0/20374/491a4e4f-8f9c-9972-f4d7-ed78eb6c47b3.gif)

「指定キーを押したときに一定角度回転する動き」で作ったものを応用することで「指定キーを押したときに指定オブジェクト周りを一定角度回転する」ということもできるでしょう。

#まとめ
今回作成したサンプルでは水平方向にしか回転させていませんが、Quaternionに指定する軸を変えることで回転方向を楽に変えることができるというのも便利なところです。
本記事であげた例以外にもQuaternionの使い方は様々あるので自身のプロジェクトにあった使い方を是非探してみてください。とっかかりに役立てれば幸いです。良きQuaternionライフを！