33
20

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

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

Last updated at Posted at 2022-07-02

目的

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

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

Cinemachine_header.png

Cinemachine とは

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

Cinemachine の仕組み

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

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

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

CameraStateToCamera.drawio.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);

    ... ...
}

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

CameraState.drawio.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 BlendCylindrical Blend (円を描くような遷移経路)の処理もここに入っています。
Lerp() の出力結果もまた CameraState ですので、必要に応じて更に Lerp することも出来ます。
BlendHint.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

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


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

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

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

CameraStateBlending.drawio.png

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


 

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

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

CinemachineVirtualCamera

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

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

VirtualCamera.png

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

CinemachineBlendListCamera

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

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

BlendListCamera.png

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

実際に 実装 (Github) を見てみると、BlendListCamera は自身で複雑な処理をせず、子 VirtualCamera の CameraState を取得して、そのまま自分の CameraState として提供しています。
遷移時は遷移元と遷移先の子カメラの CameraState をブレンドして提供しています。
 

次のステップへ

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

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

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

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

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

extension.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 のススメ
実際に CinemachineExtension を継承して、俯瞰・アオリ角を制限する拡張を作りました。
興味のある方は是非見てみてください。
 

備考:CinemachineCore.Stage について

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

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

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

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

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

  • CinemachineVirtualCameraBase
  • CameraState
  • CinemachineBrain

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

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

参考

33
20
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
33
20

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?