【Unity】Cinemachineでマウスホイールのドリーイン/アウトを実装する

こじゃら

Cinemachineでカメラのドリーイン/ドリーアウト(前後移動)を実現するにはどうすればいいの~?

このは

スクリプトからバーチャルカメラのオフセット値を変更すれば可能だわ。

Cinemachineで実装するカメラワークにおいて、マウスホイール操作で被写体との距離を変える方法の解説記事です。

結論を言うと、CinemachineバーチャルカメラのFollow Offsetをマウスホイール操作された時に直線的に移動させれば実現できます。

Follow Offsetは、追従対象に対するカメラの相対位置です。
この相対位置をスクリプトから動的に変更する事で実現できます。

本記事では、このようなマウスホイール操作でカメラをドリーイン/ドリーアウト(前後移動)させる方法について解説していきます。

この作品はユニティちゃんライセンス条項の元に提供されています

動作環境
  • Unity2021.1.12f1
  • Cinemachine2.7.4

前提条件

CinemachineVirtualCameraで対象を追従するようなカメラワークが設定されていることを前提とします。

Cinemachineによる追従カメラの設定方法については、以下記事をご覧ください。

【Unity】滑らかに(遅れて)追従するカメラを簡単に実装する方法
Cinemachineを用いてキャラクターを追従するカメラワークを実装する方法の解説記事です。 キャラクターを追従するカメラワークはスクリプトでも実装できますが、Cinemachineを使うことで次のメリットが得られます ...

オフセット値をスクリプトから変更する方法

Cinemachineのバーチャルカメラは、CinemachineVirtualCameraコンポーネントとしてスクリプトからアクセスできます。

var virtualCamera = GetComponent<CinemachineVirtualCamera>();

参考:Class CinemachineVirtualCamera | Package Manager UI website

そして、Follow Offsetへアクセスするためには、CinemachineVirtualCameraコンポーネント内にあるCinemachineTransposerコンポーネントのm_FollowOffsetフィールドを参照する必要があります。

CinemachineTransposerコンポーネントは、MonoBehaviour.GetComponent()メソッドからは取得できず、CinemachineVirtualCamera.GetCinemachineComponent()メソッドから取得する必要があります。

var transposer = virtualCamera.GetCinemachineComponent<CinemachineTransposer>();

ただし、CinemachineTransposerコンポーネントを取得するためには、CinemachineVirtualCameraのBodyにTransposerが設定されている必要があります。

CinemachineTransposer.m_FollowOffsetは、publicフィールドとしてアクセスできます。

var offset = transposer.m_FollowOffset;

マウスホイールの移動量取得

マウスホイールの移動量は、Input.mouseScrollDelta.yより取得できます。

var scrollDelta = Input.mouseScrollDelta.y;

本記事では、上記を使ってマウスホイールの移動量を取得することとします。2Input System等Input以外を使っている方は、適宜対応するものに置き換えてください。

オフセット位置計算

先述のCinemachineTransposer.m_FollowOffsetのスケールを変化させれば実現できます。
向きを変えず、スケールだけ変化させる処理は以下のようになります。

// オフセットの正規化された向き
var direction = transposer.m_FollowOffset.normalized;
// スケール変化
var distance = offset.magnitude - sensitivity * scrollDelta;
// スケールだけ更新
transposer.m_FollowOffset = distance * direction;

マウスホイールを使ったドリーイン/ドリーアウト

ここまで解説した内容を踏まえて、CinemachineVirtualCameraのオフセットをドリーイン/ドリーアウトで変化させるようにしてみます。

サンプルスクリプト

CinemachineVirtualCameraをマウスホイールによりドリーイン/ドリーアウトさせるスクリプトのサンプルです。

対象物とカメラとの距離に最小値・最大値を設け、移動範囲を制限できるようにしました。

CameraDollyController.cs
using Cinemachine;
using UnityEngine;

/// <summary>
/// CinemachineVirtualCameraのドリー操作
/// </summary>
public class CameraDollyController : MonoBehaviour
{
    // バーチャルカメラ
    [SerializeField] private CinemachineVirtualCamera _virtualCamera;

    // マウスホイールの操作感度
    [SerializeField] private float _sensitivity = 1;

    // 対象物との最小距離
    [SerializeField] private float _minDistance = 1;

    // 対象物との最大距離
    [SerializeField] private float _maxDistance = 20;

    private CinemachineTransposer _transposer;
    private Vector3 _direction;

    // 初期化
    private void Awake()
    {
        // Transposerコンポーネントを取得
        _transposer = _virtualCamera.GetCinemachineComponent<CinemachineTransposer>();

        // オフセットの方向保持
        // (この方向に沿ってオフセットを移動させる)
        if (_transposer != null)
            _direction = _transposer.m_FollowOffset.normalized;
    }

    // カメラワーク更新
    private void Update()
    {
        if (_transposer == null)
            return;

        // マウスホイールの移動量取得
        var scrollDelta = Input.mouseScrollDelta.y;
        if (Mathf.Approximately(scrollDelta, 0))
            return;

        // オフセット値取得
        var offset = _transposer.m_FollowOffset;

        // ドリーイン/ドリーアウトするように、オフセットを対象物から直線移動
        var distance = Mathf.Clamp(
            offset.magnitude - _sensitivity * scrollDelta,
            _minDistance,
            _maxDistance
        );

        // オフセット値更新
        _transposer.m_FollowOffset = _direction * distance;
    }
}

上記スクリプトをCameraDollyController.csとして保存し、適当なゲームオブジェクトにアタッチし、各種パラメータを設定します。

実行結果

対象物との距離を断続的に切り替えられるようになりました。

こじゃら

もっと滑らかに切り替えるにはどうすればいいの~?

このは

距離の変化にSmoothDampを用いればできるわ!

滑らかなドリーイン/ドリーアウト

先の例では、マウスホイールによる距離が断続的に変化していました。
これは、マウスホイール値は連続的には切り替わらないためです。
距離を滑らかに変化させられるように出来れば、より自然なカメラワークになるでしょう。

本記事では、滑らかな変化にMathf.SmoothDampメソッドを活用することにします。
Mathf.SmoothDampメソッドの使い方については、以下記事をご覧ください。

【Unity】SmoothDampで滑らかな追従を実装する
Unityには目標値に滑らかに向かう変化を実現するSmoothDamp系メソッドが用意されています。 これらのスムージングは、目標に近づくにつれて減速し、最終的に目標値になるような動きをします。 ほかによく用いられる方法 ...

サンプルスクリプト

Mathf.SmoothDampメソッドを使って、滑らかにドリーイン/ドリーアウトするスクリプトのサンプルです。

SmoothCameraDollyController.cs
using Cinemachine;
using UnityEngine;

/// <summary>
/// CinemachineVirtualCameraのドリー操作(滑らかな変化版)
/// </summary>
public class SmoothCameraDollyController : MonoBehaviour
{
    // バーチャルカメラ
    [SerializeField] private CinemachineVirtualCamera _virtualCamera;

    // マウスホイールの操作感度
    [SerializeField] private float _sensitivity = 1;

    // 対象物との最小距離
    [SerializeField] private float _minDistance = 1;
    
    // 対象物との最大距離
    [SerializeField] private float _maxDistance = 20;
    
    // 距離が切り替わるまでのおおよその時間
    [SerializeField] private float _smoothTime = 1;

    private CinemachineTransposer _transposer;
    private Vector3 _direction;

    private float _currentDistance;
    private float _targetDistance;
    private float _currentVelocity;

    // 初期化
    private void Awake()
    {
        // Transposerコンポーネントを取得
        _transposer = _virtualCamera.GetCinemachineComponent<CinemachineTransposer>();
        if (_transposer == null)
            return;

        // オフセットの方向保持
        // (この方向に沿ってオフセットを移動させる)
        var offset = _transposer.m_FollowOffset;
        _direction = offset.normalized;
        _currentDistance = _targetDistance = offset.magnitude;
    }

    // カメラワーク更新
    private void Update()
    {
        if (_transposer == null)
            return;

        // マウスホイールの移動量取得
        var scrollDelta = Input.mouseScrollDelta.y;
        if (!Mathf.Approximately(scrollDelta, 0))
        {
            _targetDistance = Mathf.Clamp(
                _targetDistance - _sensitivity * scrollDelta,
                _minDistance,
                _maxDistance
            );
        }

        // 滑らかに変化する距離の計算
        _currentDistance = Mathf.SmoothDamp(
            _currentDistance,
            _targetDistance,
            ref _currentVelocity,
            _smoothTime
        );

        // 向きと距離をもとに、次のオフセット計算・反映
        _transposer.m_FollowOffset = _direction * _currentDistance;
    }
}

上記スクリプトをSmoothCameraDollyController.csとして保存し、適当なゲームオブジェクトにアタッチし、パラメータを調整します。

注意点として、一つ目の例のCameraDollyControllerスクリプトが有効になっていると、処理が衝突して上手く動かないため、無効にするかゲームオブジェクトから削除しておく必要があります。

実行結果

滑らかな距離変化もしっかりと再現できました!

さいごに

本記事では、Cinemachineでマウスホイールによるドリーイン/ドリーアウトを実装する方法について解説しました。

Cinemachineの標準機能にはこのようなマウスホイール操作が存在しないため、スクリプトで自前で実装する必要がある点は面倒かもしれません。しかし、Cinemachineのカメラワークにそのまま拡張する形で実装できます。

Cinemachineのバージョンアップによって、このような操作が標準サポートされる可能性もありますので、最新機能については公式リファレンスをチェックする事をお勧めします。

参考サイト