MRが楽しい

MRやVRについて学習したことを書き残す

カメラを現在の位置から特定のポイントまでスムーズに移動させる

本日は Unity の小ネタ枠です。
カメラを現在の位置から特定のポイントまでスムーズに移動させる方法を記事にします。
f:id:bluebirdofoz:20220201123319j:plain

位置と回転をスムーズに変化させる

今回は位置と回転をスムーズに変化させるため、UniRx と Lerp 関数、Ease-in と Ease-Out の計算式を利用します。
Lerp 関数と UniRx の利用手順は以下を参照ください。
docs.unity3d.com
bluebirdofoz.hatenablog.com

Ease-in と Ease-Out の計算式は徐々に加速して徐々に減速する動きを再現するために Lerp 関数と合わせて利用します。
以下の記事を参考にしました。
appleorbit.hatenablog.com

サンプルスクリプト

以下のサンプルスクリプトを作成しました。
ExecuteMove 関数を実行すると、メインカメラを指定のトランスフォームまでスムーズに移動させます。
・CameraMoveSmooth.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
using UniRx;

public class CameraMoveSmooth : MonoBehaviour
{
    /// <summary>
    /// カメラの移動先
    /// (ContextMenuItemによりInspectorからExecuteMoveを実行できる)
    /// </summary>
    [SerializeField, ContextMenuItem("Move", "ExecuteMove")]
    private Transform p_TargetTransform;

    /// <summary>
    /// カメラの移動開始地点
    /// </summary>
    private Vector3 p_StartPosition;
    private Quaternion p_StartRotation;

    /// <summary>
    /// 継続処理の参照
    /// </summary>
    private IDisposable p_Trigger;

    [SerializeField]
    private float p_LerpTime = 3.0f;

    public void ExecuteMove()
    {
        // 前回トリガーを終了する
        p_Trigger?.Dispose();

        // カメラの移動開始地点を保存する
        p_StartPosition = Camera.main.transform.position;
        p_StartRotation = Camera.main.transform.rotation;

        // 移動処理を開始する
        p_Trigger = Observable
            .IntervalFrame(1, FrameCountType.FixedUpdate)    // 1フレーム毎に呼び出す
            .TimeInterval()                                  // フレーム間の経過時間を取得する
            .Select(timeInterval => timeInterval.Interval)   // TimeSpan型のデータを抽出する
            .Scan((last, current) => last + current)         // 前回までの経過時間を加算する
            .TakeWhile(intervalTimeSpan => (float)intervalTimeSpan.TotalSeconds < p_LerpTime) // Lerp時間を超えるまで実行する
            .SubscribeOnMainThread()                         // メインスレッドで実行する
            .Subscribe(
            intervalTimeSpan =>
            {
                float totalInterval = (float)intervalTimeSpan.TotalSeconds;
                // Ease-in, Ease-Out の計算式で徐々に加速して徐々に減速する補間を行う
                float t = Mathf.Min(totalInterval / p_LerpTime, 1.0f);
                float lerpFactor = (t * t) * (3.0f - (2.0f * t));
                // Leap関数を使って徐々にターゲットに近づいていく
                Camera.main.transform.position = Vector3.Lerp(p_StartPosition, p_TargetTransform.position, lerpFactor);
                Camera.main.transform.rotation = Quaternion.Lerp(p_StartRotation, p_TargetTransform.rotation, lerpFactor);
            },
            () =>
            {
                // 最終的に指定のトランスフォームに到達させる
                Camera.main.transform.position = p_TargetTransform.position;
                Camera.main.transform.rotation = p_TargetTransform.rotation;
            }
            )
            .AddTo(this);
    }
}

動作確認

動作確認のため、サンプルスクリプトを組み込んだ以下のサンプルシーンを作成しました。
カメラを移動させたい位置に[TargetPosition]オブジェクトを配置し、移動時間を[3(秒)]に指定しました。
f:id:bluebirdofoz:20220201123444j:plain
f:id:bluebirdofoz:20220201123453j:plain

シーンを再生して動作を確認します。
サンプルスクリプトには ContextMenuItem を設定しているので Inspector の右クリックから Move を選択して実行可能です。
f:id:bluebirdofoz:20220201123502j:plain

関数の実行と共にカメラの視点が滑らかに移動すれば成功です。
f:id:bluebirdofoz:20220201123511j:plain