【Unity】Cinemachineカメラの俯瞰・アオリ角の最大値を制限する / CinemachineExtension のススメ

# 目的
皆さん以下のような状況にあったことありませんでしょうか？

- Cinemachine はすごく良い感じにカメラを設定してくれるけど、時々変な方向に向いて、どう調整すれば分からない


- できれば敵を追従して欲しいけど、真上や真下には向いてほしくない
  - つまり、俯瞰とアオリの角度の最大値を制限したい！

　
少なくとも自分にはめちゃめちゃありました。ですので、それを簡単に修正する方法を共有したいと思います。
加えて、似たような「痒いところに手が届かない」時に、**コードで Virtual Camera の結果を微調整する方法**も、合わせて共有します。

※ この記事は、すでにある程度 Cinemachine を使ったことのあるかた向けです。

# 改善前
下記は修正前のカメラ挙動です。
![修正前.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/295675/6398de79-ae53-749a-53b1-cc8d78422889.gif)
これはこれで綺麗で気持ちいいのですが、今回はこのカメラの俯瞰角を最大 25 度までと制限してみます。


# 要件
`Cinemachine Virtual Camera` の俯瞰・アオリ角（ = Pitch 角）を制限したい

# やり方
実は、Cinemachine には `CinemachineExtension` という、
各 Virtaul Camera の出力結果を簡単に修正する拡張コンポーネントがありました。

下の画像のように、出力結果を色々な方法で微調整するコンポーネントがたくさんあります。
![extension.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/295675/8ce076fb-774e-44ad-0662-42e2844baf1e.png)

今回は `CinemachineExtension` を自作して、俯瞰 / アオリの角度を制限してみます。

## コード

やり方はとても簡単です。
`MonoBehaviour` の代わりに、`CinemachineExtension` を継承して、`PostPipelineStageCallback()` を override するだけです。

``` CinemachineRestrictAngle.cs
using UnityEngine;

namespace Cinemachine
{
    [AddComponentMenu("")] // Hide in menu
    [ExecuteAlways]
    [SaveDuringPlay]
    public class CinemachineRestrictAngle : CinemachineExtension
    {
        /// <summary>
        /// field を public にすることで SaveDuringPlay が可能になる
        /// </summary>
        [Header("適用段階")]
        public CinemachineCore.Stage m_ApplyAfter = CinemachineCore.Stage.Aim;

        [Header("俯瞰角 閾値")]
        [Range(0f, 90f)]
        public float lowAngleThreshold = 80f;

        [Header("アオリ角 閾値")]
        [Range(0f, 90f)]
        public float highAngleThreshold = 80f;

        /// <summary>
        /// カメラパラメータ更新後に呼び出される Callback。ここで結果を微調整する。
        /// </summary>
        protected override void PostPipelineStageCallback(CinemachineVirtualCameraBase vcam, CinemachineCore.Stage stage, ref CameraState state, float deltaTime)
        {
            if (stage != m_ApplyAfter) return;

            // カメラの X 軸回転を制限する
            var eulerAngles = state.RawOrientation.eulerAngles;
            eulerAngles.x = Mathf.Clamp(eulerAngles.x, -highAngleThreshold, lowAngleThreshold);
            state.RawOrientation = Quaternion.Euler(eulerAngles);
        }
    }
}
```

そうすると、上のコードのように、引数に `CameraState state` というものが渡ってきますが、**この `CameraState` こそ、
`VirtualCamera` カメラの処理結果です**。

最終的に、`CinemachineBrain` は Priority の一番大きい `VirtualCamera` の `CameraState` を取得して、
それを本物の Camera に適用します。
（遷移中の場合、前後のカメラの `CameraState` を Lerp して、適用します）
　
今回は俯瞰角を制限したいですので、
上のコードでは `CameraState.RawOrientation` を取得して、角度を clamp してまた戻しています。
これで、バーチャルカメラの計算結果を微調整して、角度を制限することが出来ます。

## インスペクター設定
上のコードを `CinemachineRestrictAngle.cs` に保存すると、下記画像のように VirtualCamera の Extensions から選べるようになります。
![extensionSettings.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/295675/ab2df822-2034-cb88-f819-0aedae862c08.png)
`CinemachineRestrictAngle` を選び、俯瞰とアオリ角の閾値を 25 度に設定します。
これで、俯瞰角・アオリ角の最大値を自由に調整することができます。



# 改善後 結果
改善後の結果です。
![修正後.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/295675/6013095f-21c3-c5dc-56d2-3fd2dfd7b9ff.gif)
前半は改善前と同じ画角の運び方をしていますが、
25 度を超えていたところの画角の角度は制限され、それ以上に下を向かなくなりました。

# 結
この記事のように、`CinemachineExtension` を継承すれば、Virtual Camera の結果を色々微調整することができます。

現に、カメラを壁などに埋め込ませないようにできるコンポーネント `CinemachineCollider` も
公式で Extension として提供しています。

今回は俯瞰・アオリ角に最大値を付ける方法を説明しましたが、
ぜひ今度「こういう微調整の機能があったら良いな」と思った時、自作 `CinemachineExtension` を作ってみましょう！