【Unity】【Cinemachine】二つのVirtual Cameraをスクリプトを使ってブレンドする

CinemachineのVirtual Cameraをスクリプトを使ってブレンドする方法です。

Unity2018.4
Cinemachine2.2.9

やりたいこと - Playableが公開されてない!

CinemachineのVirtual Cameraのブレンドは非ランタイムではできません。
そこで、こんな感じで任意の二つのVirtual Cameraとブレンド率を指定してブレンドのシミュレーションができるコンポーネントを作りました。

f:id:halya_11:20190624135455g:plain

CinemachineのPlayableが公開されていれば手っ取り早かったのですが、
Timeline用のクラスとして設計されていてinternalになってしまっていたので半ば無理やりの実装です。
今後このあたりのPlayableはぜひ公開していただきたいものです…。

またTimelineで事足りるのであればそちらを使ったほうが良いかと思います。

ブレンド情報はCameraState.Lerp()を使って取得する

Cinemachineのソースコードを読んだ結果、下記のような処理で補間計算をできることがわかりました。

public static void ApplyCinemachineVirtualCameraBlending(Camera applyTo, CinemachineVirtualCamera camera1, CinemachineVirtualCamera camera2, Vector3 worldUp, float progress, float deltaTime)
{
    // Virtual CameraのStateを更新
    camera1.InternalUpdateCameraState(worldUp, deltaTime);
    camera2.InternalUpdateCameraState(worldUp, deltaTime);
    // 補間したStateを取得
    var state = CameraState.Lerp(camera1.State, camera2.State, progress);
    // Cameraに適用
    applyTo.transform.position = state.FinalPosition;
    applyTo.transform.rotation = state.FinalOrientation;
    applyTo.fieldOfView = state.Lens.FieldOfView;
    applyTo.nearClipPlane = state.Lens.NearClipPlane;
    applyTo.farClipPlane = state.Lens.FarClipPlane;
}

Virtual CameraはCameraStateの中に位置や回転の情報を持ちます。
これらを更新した後ブレンド率を指定して補間して、ブレンド後のCameraStateを得ます。

あとはこのCameraStateから位置や回転などを対象のカメラにセットすればOKです。

二つのVirtual Cameraをブレンドする

このメソッドを使ってみるために適当にクラスを定義してみます。

using Cinemachine;
using UnityEngine;

#if UNITY_EDITOR
[ExecuteAlways]
public class Example : MonoBehaviour
{
    [SerializeField]
    CinemachineVirtualCamera _camera1;
    [SerializeField]
    CinemachineVirtualCamera _camera2;
    [SerializeField]
    Camera _mainCamera;
    [SerializeField, Range(0.0f, 1.0f)]
    private float _rate;

    private void OnDisable()
    {
        if (_mainCamera == null) {
            return;
        }
        var brain = _mainCamera.GetComponent<CinemachineBrain>();
        if (brain != null) {
            brain.enabled = true;
        }
    }

    public void Update(){
        if (_mainCamera == null || _camera1 == null || _camera2 == null) {
            return;
        }
        // CinemachineBrainがアタッチされていると位置などがコントロールできないので非アクティブに
        var brain = _mainCamera.GetComponent<CinemachineBrain>();
        if (brain != null) {
            brain.enabled = false;
        }
        ApplyCinemachineVirtualCameraBlending(_mainCamera, _camera1, _camera2, Vector3.up, _rate, Time.deltaTime);
    }

    public static void ApplyCinemachineVirtualCameraBlending(Camera applyTo, CinemachineVirtualCamera camera1, CinemachineVirtualCamera camera2, Vector3 worldUp, float progress, float deltaTime)
    {
        // Virtual CameraのStateを更新
        camera1.InternalUpdateCameraState(worldUp, deltaTime);
        camera2.InternalUpdateCameraState(worldUp, deltaTime);
        // 補間したStateを取得
        var state = CameraState.Lerp(camera1.State, camera2.State, progress);
        // Cameraに適用
        applyTo.transform.position = state.FinalPosition;
        applyTo.transform.rotation = state.FinalOrientation;
        applyTo.fieldOfView = state.Lens.FieldOfView;
        applyTo.nearClipPlane = state.Lens.NearClipPlane;
        applyTo.farClipPlane = state.Lens.FarClipPlane;
    }
}
#endif

今回は非ランタイムで使いたかったのでUNITY_EDITOR限定にしています。
細かい部分の処理は適当ですが、これで下図のように補間処理を行うことができました。

f:id:halya_11:20190624135455g:plain