🙌

Unity Cinemachineを使わないカメラワーク設計

2024/03/20に公開

SimpleCameraControl

https://github.com/fluncle/SimpleCameraControl/wiki

https://qiita.com/flankids/items/0a4f70c9bfb6d20f20eb

CameraManager.cs
/*
被写体の座標	CameraParentの座標
被写体からの距離	CameraChildのローカルZ座標(負数)
画角	Main CameraのField Of View
被写体への回り込み角度	CameraParentの角度
視界オフセット座標	Main Cameraのローカル座標
視界オフセット角度	Main Cameraのローカル角度
*/

using System;
using UnityEngine;

// シーンを実行しなくてもカメラワークが反映されるよう、ExecuteInEditModeを付与
[ExecuteInEditMode]
public class CameraManager : MonoBehaviour
{
    /// <summary> カメラのパラメータ </summary>
    [Serializable]
    public class Parameter
    {
        public Vector3 position;
        public Vector3 angles = new Vector3(10f, 0f, 0f);
        public float distance = 7f;
        public float fieldOfView = 45f;
        public Vector3 offsetPosition = new Vector3(0f, 1f, 0f);
        public Vector3 offsetAngles;
    }

    [SerializeField]
    private Transform _parent;

    [SerializeField]
    private Transform _child;

    [SerializeField]
    private Camera _camera;

    [SerializeField]
    private Parameter _parameter;

    // 被写体などの移動更新が済んだ後にカメラを更新したいので、LateUpdateを使う
    private void LateUpdate()
    {
        if(_parent == null || _child == null || _camera == null)
        {
            return;
        }

        // パラメータを各種オブジェクトに反映
        _parent.position = _parameter.position;
        _parent.eulerAngles = _parameter.angles;

        var childPos = _child.localPosition;
        childPos.z = -_parameter.distance;
        _child.localPosition = childPos;

        _camera.fieldOfView = _parameter.fieldOfView;
        _camera.transform.localPosition = _parameter.offsetPosition;
        _camera.transform.localEulerAngles = _parameter.offsetAngles;
    }
}

被写体を追いかけるには、Parameter.positionに被写体の座標を代入し続ける必要があります。
多くの場合、被写体はTransformを持つと思うのでCamera.Parameterクラスに追従対象(trackTarget)をTransformで指定できるように拡張してみます

CameraManager.cs
/// <summary> カメラのパラメータ </summary>
[Serializable]
public class Parameter
{
    public Transform trackTarget; // ← 追加
    public Vector3 position;
    public Vector3 angles = new Vector3(10f, 0f, 0f);
    public float distance = 7f;
    public float fieldOfView = 45f;
    public Vector3 offsetPosition = new Vector3(0f, 1f, 0f);
    public Vector3 offsetAngles;
}
private void LateUpdate()
{
    /* ↓↓↓ここから追加↓↓↓ */
    if(_parameter.trackTarget != null)
    {
        // 被写体がTransformで指定されている場合、positionパラメータに座標を上書き
        _parameter.position = _parameter.trackTarget.position;
    }
    /* ↑↑↑追加ここまで↑↑↑ */

    // パラメータを各種オブジェクトに反映
    _parent.position = _parameter.position;
}

被写体の動きに対してちょっと遅れてついてくる
Lerpを使った簡易的なイージングを入れて、座標更新の変化をなめらかなものにします。
ピッタリカメラがついてくるよりは自然な印象のカメラワークになりました。

CameraManager.cs
private void LateUpdate()
{
    if(_parameter.trackTarget != null)
    {
        // がTransformで指定されている場合、positionパラメータに座標を上書き
        _parameter.position = Vector3.Lerp(
            a: _parameter.position,
            b: _parameter.trackTarget.position,
            t: Time.deltaTime * 4f
        );
    }
}

マウスでカメラを回転させるFPS/TPSっぽい動き

CameraManager.csに実装するよりは、操作用のクラスを作って外部からカメラのParameterを変更するほうが設計としては良い

CameraManager.cs
public class CameraManager : MonoBehaviour
{
    private void Update()
    {
        // マウスの動きの差分をカメラの回り込み角度に反映
        Vector3 diffAngles = new Vector3(
            x: -Input.GetAxis("Mouse Y"),
            y: Input.GetAxis("Mouse X")
        ) * 10f;
        _parameter.angles += diffAngles;
    }
}

Lerpメソッドを作って2つのカメラワークをブレンド遷移

次に行くべき場所を示したり、仕掛けを操作したことで動いたオブジェクトを一度見せたりする

CameraManager.cs
public class CameraManager : MonoBehaviour
{
     /// <summary> カメラのパラメータ </summary>
    [Serializable]
    public class Parameter
    {
        public static Parameter Lerp(Parameter a, Parameter b, float t, Parameter ret)
        {
            ret.position = Vector3.Lerp(a.position, b.position, t);
            ret.angles = LerpAngles(a.angles, b.angles, t);
            ret.distance = Mathf.Lerp(a.distance, b.distance, t);
            ret.fieldOfView = Mathf.Lerp(a.fieldOfView, b.fieldOfView, t);
            ret.offsetPosition = Vector3.Lerp(a.offsetPosition, b.offsetPosition, t);
            ret.offsetAngles = LerpAngles(a.offsetAngles, b.offsetAngles, t);

            return ret;
        }

        private static Vector3 LerpAngles(Vector3 a, Vector3 b, float t)
        {
            Vector3 ret = Vector3.zero;
            ret.x = Mathf.LerpAngle(a.x, b.x, t);
            ret.y = Mathf.LerpAngle(a.y, b.y, t);
            ret.z = Mathf.LerpAngle(a.z, b.z, t);
            return ret;
        }
    }
}

TPSゲームで通常のビューから銃を構えたビューに切り替える

ズーム時のビューはdistance(被写体からの距離)を小さくするだけでなく、fieldOfView(画角)も小さくすることで、位置的な近さ+遠近感を薄めて遠くのものが見えやすい

offsetPosition(Main Cameraのローカル座標)の x の値をズラして、被写体であるプレイヤーが画面中央の照準に被らないようにしている

とりあえずイイカンジのカメラ構造

https://qiita.com/flankids/items/21a8e900e8e9f6329767

3D空間上に表示する距離・角度の影響を受けないUI

https://qiita.com/flankids/items/2d92d1ed66752205fcae



Discussion