【Unity】Cinemachine 内部処理 完全解説

# 目的
この記事では、内側から Cinemachine を見て解説します。
内側が分かると、Cinemachine の動かせ方が自然と分かるようになり、ゲームのバトルとシナリオのカメラワークをより格好良く調整出来ます。
また、Cinemachine の基本機能では足らなくなった時に、拡張クラスも書けるようになります。

是非この記事を見て、素晴らしいカメラワークを作ってみてください！

<img alt="Cinemachine_header.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/295675/d7515b05-765e-f2d1-5ed4-25e111eb2c12.png">

# Cinemachine とは
Cinemachine は Unity が映像制作のために作った公式ツールです。
Cinemachine を使えば、カメラの位置・回転や FOV などが簡単に操作・切り替えができて、イベントシーンや必殺技のカメラワークが簡単にできます。
本記事では基本的な使い方の説明をせず、内部処理のほうにフォーカスします。
　

# Cinemachine の仕組み

Cinemachine は様々なカメラワークを実現するために、色々なクラスがあります。
しかし、処理の中身を探求すると、理解するのに本当に重要なクラスは以下の３つのみです。


 - `Cinemachine Virtual Camera Base`
   - 全ての**バーチャルカメラの基底クラス**
   - 次の CameraState を提供する
 - `Camera State (struct)`
   - バーチャルカメラの **計算結果**
   - バーチャルカメラはどの時でも必ず CameraState を提供しなければならない
 - `Cinemachine Brain`
   - Priority の一番高いバーチャルカメラの **計算結果をカメラに適用する管理クラス**
　

Cinemachine を使うと、毎フレーム、Cinemachine によってカメラが適宜な位置に配置されますが、
その時、上記３クラスの処理と関係は以下のようになります：

>![CameraStateToCamera.drawio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/295675/1ab5013d-313f-adf6-8311-2aebd476a9be.png)

1. CinemachineBrain が一番 Priority の高いバーチャルカメラを取得します
2. 該当バーチャルカメラの CameraState を更新して・取得します
3. CameraState をそのままカメラに適用します

上のように、Cinemachine は `VirtualCamera -> CameraState -> CinemachineBrain -> Camera` の処理の流れで Camera を更新しています。

下節で、各コンポーネントの役割を説明します。 

## CinemachineVirtualCameraBase

CinemachineVirtualCameraBase は、全てのバーチャルカメラの基底クラスです。

基底クラスゆえに、カメラ操作用の処理はあまり入っていませんが、
継承クラスのルールとして、「**必ず CameraState を提供しなければならならない**」があります。

関連の定義は下記になります：

``` CinemachineVirtualCameraBase.cs
public abstract class CinemachineVirtualCameraBase : MonoBehaviour, ICinemachineCamera
{
    public int m_Priority = 10;
    public abstract CameraState State { get; }

    public abstract void InternalUpdateCameraState(Vector3 worldUp, float deltaTime);

    ... ...
}
```

図解として、下記のようになります：

> <img width="400" alt="CameraState.drawio.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/295675/af9deff9-c3b7-c460-946c-3727dc1f013c.png">

各バーチャルカメラは、`InternalUpdateCameraState` を override して、独自の CameraState の更新処理の記述をしています。
バーチャルカメラに提供された CameraState で、カメラのこのフレームのパラメータが決まります。


## CameraState

バーチャルカメラによって計算された CameraState ですが、
その役割としては、Camera を操作するためのパラメータのデータ構造です。
中には position・rotation や FOV などの情報が入っていて、Camera に直接適用するのに適しています。

関連の定義は下記になります：

``` CameraState.cs
    public struct CameraState
    {
        // ノイズ適用前の位置
        public Vector3 RawPosition;

        // ノイズ適用前の回転
        public Quaternion RawOrientation;

        // 最終位置。FinalPosition はそのまま Camera の Transform に適用される
        public Vector3 FinalPosition => RawPosition + PositionCorrection;

        // FOV, perspective/orthographic などの設定
        public LensSettings Lens;

        ... ...
        // ほか、細かい調整用のパラメータ
    }
```

このように、馴染み深い Vector3 と Quaternion が多く入っており、それがカメラの Transform 更新に使用されます。

また、複数のバーチャルカメラ間の遷移を実現するために、CameraState は `Lerp()` の関数も実装しています。
単純な Vector3 の Lerp のみならず、**Spherical Blend** や **Cylindrical Blend** （円を描くような遷移経路）の処理もここに入っています。
`Lerp()` の出力結果もまた CameraState ですので、必要に応じて更に Lerp することも出来ます。
![BlendHint.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/295675/4e6576d4-1c06-f95c-e3ef-3e8abf32a3bf.png)

## CinemachineBrain

各バーチャルカメラはそれぞれ CameraState を提供していますが、
実際に Camera に適用されるのは Priority の一番高いバーチャルカメラの CameraState のみです。

Priority を見て、適切なバーチャルカメラの CameraState を Camera に適用する管理クラスは、ズバリ CinemachineBrain です。

幸い、CameraState はすでに適した形で定義されていますので、
以下のように、適用時はほぼ state の情報そのまま適用できます。

``` CinemachineBrain.cs
    public class CinemachineBrain : MonoBehaviour
    {
        ... ...

        // CameraState を Camera に適用する関数
        private void PushStateToUnityCamera(ref CameraState state)
        {
            transform.position = state.FinalPosition;
            transform.rotation = state.FinalOrientation;

            Camera cam = OutputCamera;
            if (cam != null) {
                cam.nearClipPlane = state.Lens.NearClipPlane;
                cam.farClipPlane = state.Lens.FarClipPlane;
                ... ...
            }
        }
    }
```
　

図解は下記のようになります：
![CameraStateToCamera.drawio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/295675/1ab5013d-313f-adf6-8311-2aebd476a9be.png)

また、最高 Priority のバーチャルカメラが変わった時に、スムーズな遷移をするための処理もしています。

---

### バーチャルカメラ間の遷移

Priority 変動によって適用するバーチャルカメラが変わった時、急激なカメラの画角変化を防ぐために、
CinemachineBrain は緩やかに遷移元から遷移先へブレンドしています。

具体的には、CameraState の節でも説明した通り、
遷移元のカメラと遷移先のカメラの CameraState を Lerp させ、結果を Camera に適用しています。

> ![CameraStateBlending.drawio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/295675/9915844c-5f9a-1c27-07d6-c96a345cdeea.png)

こうすることで、スムーズなカメラ遷移が出来て、気持ちいいカメラワークが出来ます。

---
　

# 上記の知識を持って、VirtualCamera を見てみる

実際に上記の知識を持って、CinemachineVirtualCameraBase の継承クラスを分析してみましょう。
今回は **CinemachineVirtualCamera** と **CinemachineBlendListCamera** を紹介します。

## CinemachineVirtualCamera

CinemachineVirtualCamera は一番操作種類が多く、とても強力なバーチャルカメラです。
様々な追従・注視方法で、キャラをフォーカスするカメラが簡単に作れます。

CinemachineVirtualCamera のインスペクター上のパラメータは下記のようになります。

> ![VirtualCamera.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/295675/2e95c0f4-9f0f-46da-4bb6-8ce9d2283c85.png)

他のバーチャルカメラと比べて、**Body** と **Aim**、**Noise** の項目があります。
ここから推察すると、おそらく VirtualCamera はアップデート時、**Body** と **Aim** の方法で CameraState を作り、更にちょっとした Noise を追加して、それを最終結果として出力しています。

## CinemachineBlendListCamera
CinemachineBlendListCamera は複数の子バーチャルカメラを持って、
各子カメラの映す時間や遷移時間が設定出来るバーチャルカメラです。

CinemachineBlendListCamera のインスペクター上のパラメータは下記のようになります。

>![BlendListCamera.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/295675/19ab5924-f12a-16de-09f5-b4df1215ca29.png)

VirtualCamera と比べて、Body・Aimt・Noise が無く、代わりに複数の子 VirtualCamera を持っています。

実際に [実装 (Github)](https://github.com/Unity-Technologies/com.unity.cinemachine/blob/dd407bdc275acf7b7a4bb313c354a3aa2b189fbe/com.unity.cinemachine/Runtime/Behaviours/CinemachineBlendListCamera.cs#L181-L259) を見てみると、BlendListCamera は自身で複雑な処理をせず、子 VirtualCamera の CameraState を取得して、そのまま自分の CameraState として提供しています。
遷移時は遷移元と遷移先の子カメラの CameraState をブレンドして提供しています。
　

# 次のステップへ

上記のように、内部処理が分かると、各バーチャルカメラをインスペクターを見ただけで、何をやっているかが何となく分かるようになります。

しかし、それだけではありません。
Cinemachine の内部知識があると、**新たなバーチャルカメラを作ったり、既存のバーチャルカメラの拡張処理クラス（CinemachineExtension）を書くことができます。**

実際に自分で一から VirtualCamera を書くのは大変ですが、既存のバーチャルカメラの微調整ならとても簡単です。
以下で拡張クラスの作り方を紹介します。

## Cinemachine の拡張処理クラス (CinemachineExtension) とは

CinemachineVirtualCamera のインスペクターの一番下に、Add Extension という項目があります。
プルダウンリストを選択すると、新たな CinemachineExtension がアタッチされ、様々なカスタマイズができます。

> ![extension.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/295675/ce467f58-1b1c-d6ea-8486-d339f4b38bc9.png)

例えば、爆発などをシミュレートする ImpulseListener、カメラをめり込まないようにする Collider なども `CinemachineExtension` です。

## 拡張処理クラスの作り方
`CinemachineExtension` を継承すれば、独自の処理を書くこともできます。
新たな `CinemachineExtension` クラスを作ると、自動的に Add Extension のプルダウンリストに追加されます。（めっちゃ便利です！）

以下はもっともシンプルな Extension 定義です。

``` CinemachineTestExtension.cs
public class CinemachineTestExtension : CinemachineExtension
{
    // 自動的に呼ばれる、abstract な callback 関数。 VirtualCamera の state を更にカスタマイズできる
    protected override void PostPipelineStageCallback(
        CinemachineVirtualCameraBase vcam,
        CinemachineCore.Stage stage,
        ref CameraState state,
        float deltaTime)
    {
        // 独自のカメラ操作の記述
    }
}
```

abstract 関数の `PostPipelineStageCallback` を override すれば、ref 引数として CameraState が渡されます。

前章で説明した通り、CameraState は Camera の調整に直結していますので、
ここでカスタムな調整記述をすると、結果はそのまま Camera に反映されます。

別記事の [Cinemachineカメラの俯瞰・アオリ角の最大値を制限する / CinemachineExtension のススメ](https://qiita.com/tsukimi_neko/items/7bf90ef69b199aed699b#%E3%82%B3%E3%83%BC%E3%83%89) で
実際に CinemachineExtension を継承して、俯瞰・アオリ角を制限する拡張を作りました。
興味のある方は是非見てみてください。
　

### 備考：CinemachineCore.Stage について

`CinemachineExtension.PostPipelineStageCallback()` 関数に、見慣れない引数の **CinemachineCore.Stage** があります。

これはズバリ 「今、`PostPipelineStageCallback()` が呼ばれたのは何の処理の後か」を伝える enum です。
この **stage** をチェックすることで、正しいタイミングで自分の調整を適用することができます。

例えば、最終調整後の CameraState にのみ手を入れたい場合は、`if (stage != CinemachineCore.Stage.Finalize) return;` を関数の先頭に追加しましょう。

> ``` C#
> // CinemachineCore.Stage の定義
> public enum Stage
> {
>     Body,     // 位置調整ステージ
>     Aim,      // 回転調整ステージ
>     Noise,    // ノイズ（手ブレなど）ステージ
>     Finalize  // 最終調整ステージ
> };
> ```

# 結

この記事で、Cinemachine の仕組みと、Cinemachine を支える３つのクラス、

- `CinemachineVirtualCameraBase`
- `CameraState`
- `CinemachineBrain`

を紹介しました。
また、CinemachineExtension を使って CameraState の調整のやり方も紹介しました。

これらを使って、素晴らしいカメラワークを是非作ってみてください。

# 参考
- [CinemachineVirtualCameraBase ソースコード (Github)](https://github.com/Unity-Technologies/com.unity.cinemachine/blob/master/com.unity.cinemachine/Runtime/Core/CinemachineVirtualCameraBase.cs)
- [CameraState ソースコード (Github)](https://github.com/Unity-Technologies/com.unity.cinemachine/blob/master/com.unity.cinemachine/Runtime/Core/CameraState.cs)
- [CinemachineBrain ソースコード (Github)](https://github.com/Unity-Technologies/com.unity.cinemachine/blob/master/com.unity.cinemachine/Runtime/Behaviours/CinemachineBrain.cs)