unity 载具多视角相机的实现

       游戏开发中经常会有视角切换的需求,尤其是载具的行驶过程中,这篇博客实现了一个拥有多个视角和跟随目标方式的相机,不局限于使用在载具上,代码参考了汽车模拟插件Edy's Vehicle Physics的相机,附上官网地址https://www.edy.es/dev/vehicle-physics/

一.不同跟随方式的相机类

1.基类

基类包含了不同跟随方式的相机需要实现的方法,其实我更倾向于使用接口,但由于有些方法不是每个派生类都有具体实现,所以还用class了。

后面一共实现了四种不同的相机模式,固定镜头,平滑跟随镜头,鼠标控制方向的镜头和定点朝向目标的镜头

    public class BaseCameraMode
    {
        /// <summary>
        /// 初始化
        /// </summary>
        public virtual void Initialize(Transform self) { }

        /// <summary>
        /// 激活
        /// </summary>
        /// <param name="self">相机自身</param>
        /// <param name="target">目标单位</param>
        /// <param name="targetOffset">目标偏移量</param>
        public virtual void OnEnable(Transform self, Transform target, Vector3 targetOffset) { }

        /// <summary>
        /// 重置
        /// </summary>
        public virtual void Reset(Transform self, Transform target, Vector3 targetOffset) { }

        /// <summary>
        /// 相机控制,相机的Update在Mono的LateUpdate中执行
        /// </summary>
        public virtual void Update(Transform self, Transform target, Vector3 targetOffset) { }

        /// <summary>
        /// 禁用
        /// </summary>
        public virtual void OnDisable(Transform self, Transform target, Vector3 targetOffset) { }
    }

2.固定镜头

       这是最简单的镜头,位置和朝向和载具上的某个位置保持一致,最常用的是驾驶视角。

       注意类前加了[Serializable]标明可序列化,这是为了可以在面板中更改参数,每个具体的相机类在后面都会在一个相机控制器上声明。

    [Serializable]
    public class CameraAttachTo : BaseCameraMode
    {
        /// <summary>
        /// 被附加的目标
        /// </summary>
        public Transform attachTarget;

        public override void Update(Transform self, Transform target, Vector3 targetOffset)
        {
            if (attachTarget != null) target = attachTarget;
            if (target == null) return;

            self.position = target.position;
            self.rotation = target.rotation;
        }
    }

3.平滑跟随镜头

这个镜头跟目标保持一定距离,可以用作第三人称视角,相机与目标的位置差没有写成成员变量,而是作为参数传进来的targetOffset

    // 平滑跟随镜头
    [Serializable]
    public class CameraSmoothFollow : BaseCameraMode
    {
        public float distance = 10.0f;
        public float height = 5.0f;
        public float viewHeightRatio = 0.5f;      
        [Space(5)]
        public float heightDamping = 2.0f;
        public float rotationDamping = 3.0f;
        [Space(5)]
        public bool followVelocity = true;
        public float velocityDamping = 5.0f;

        private Vector3 smoothLastPos = Vector3.zero;
        private Vector3 smoothVelocity = Vector3.zero;
        private float smoothTargetAngle = 0.0f;

        public override void Reset(Transform self, Transform target, Vector3 targetOffset)
        {
            if (target == null) return;

            smoothLastPos = target.position + targetOffset;
            smoothVelocity = target.forward * 2.0f;
            smoothTargetAngle = target.eulerAngles.y;
        }

        public override void Update(Transform self, Transform target, Vector3 targetOffset)
        {
            if (target == null) return;

            Vector3 updatedVelocity = (target.position + targetOffset - smoothLastPos) / Time.deltaTime;
            smoothLastPos = target.position + targetOffset;

            updatedVelocity.y = 0.0f;  // 保证相机高度不变

            if (updatedVelocity.magnitude > 1.0f)
            {
                smoothVelocity = Vector3.Lerp(smoothVelocity, updatedVelocity, velocityDamping * Time.deltaTime);
                smoothTargetAngle = Mathf.Atan2(smoothVelocity.x, smoothVelocity.z) * Mathf.Rad2Deg;
            }

            if (!followVelocity)
                smoothTargetAngle = target.eulerAngles.y;

            float wantedHeight = target.position.y + targetOffset.y + height;
            float currentRotationAngle = self.eulerAngles.y;
            float currentHeight = self.position.y;

            currentRotationAngle = Mathf.LerpAngle(currentRotationAngle, smoothTargetAngle, rotationDamping * Time.deltaTime);
            currentHeight = Mathf.Lerp(currentHeight, wantedHeight, heightDamping * Time.deltaTime);

            Quaternion currentRotation = Quaternion.Euler(0, currentRotationAngle, 0);

            self.position = target.position + targetOffset;
            self.position -= currentRotation * Vector3.forward * distance;

            Vector3 t = self.position;
            t.y = currentHeight;
            self.position = t;

            self.LookAt(target.position + targetOffset + Vector3.up * height * viewHeightRatio);
        }
    }

3.鼠标控制方向的镜头

     和上一个相机一样与目标保持一定距离,并且可以拉远拉近镜头,并利用鼠标360度的查看目标物体

    [Serializable]
    public class CameraMouseOrbit : BaseCameraMode
    {
        public float distance = 10.0f;
        [Space(5)]
        public float minVerticalAngle = -20.0f;
        public float maxVerticalAngle = 80.0f;
        public float horizontalSpeed = 5f;
        public float verticalSpeed = 2.5f;
        public float orbitDamping = 4.0f;
        [Space(5)]
        public float minDistance = 5.0f;
        public float maxDistance = 50.0f;
        public float distanceSpeed = 10.0f;
        public float distanceDamping = 4.0f;
        [Space(5)]
        public string horizontalAxis = "Mouse X";
        public string verticalAxis = "Mouse Y";
        public string distanceAxis = "Mouse ScrollWheel";

        private float orbitX = 0.0f;
        private float orbitY = 0.0f;
        private float orbitDistance;

        public override void Initialize(Transform self)
        {
            orbitDistance = distance;

            orbitX = self.eulerAngles.y;
            orbitY = self.eulerAngles.x;
        }

        public override void Update(Transform self, Transform target, Vector3 targetOffset)
        {
            if (target == null) return;

            // 输入影响数据并限制数据在一定范围内
            orbitX += Input.GetAxis(horizontalAxis) * horizontalSpeed;
            orbitY -= Input.GetAxis(verticalAxis) * verticalSpeed;
            distance -= Input.GetAxis(distanceAxis) * distanceSpeed;

            orbitY = Mathf.Clamp(orbitY, minVerticalAngle, maxVerticalAngle);
            distance = Mathf.Clamp(distance, minDistance, maxDistance);

            // 计算相机位置和角度
            orbitDistance = Mathf.Lerp(orbitDistance, distance, distanceDamping * Time.deltaTime);
            self.rotation = Quaternion.Slerp(self.rotation, Quaternion.Euler(orbitY, orbitX, 0), orbitDamping * Time.deltaTime);
            self.position = target.position + targetOffset + self.rotation * new Vector3(0.0f, 0.0f, -orbitDistance);
        }
    }

5.定点朝向目标镜头

    这个相机不管目标在哪,相机位置不会自动变化,但可以通过设置按键来控制相机的位置以及拉远拉近镜头,并且相机一致朝向目标。注意,上一个相机的拉远拉近是利用距离实现的,这个相机是利用相机的视野大小实现的。

    [Serializable]
    public class CameraLookAt : BaseCameraMode
    {
        public float damping = 6.0f;
        [Space(5)]
        public float minFov = 10.0f;    // 最小视角 Field of view
        public float maxFov = 60.0f;
        public float fovSpeed = 20.0f;
        public float fovDamping = 4.0f;
        public bool autoFov = false;
        public string fovAxis = "Mouse ScrollWheel";
        [Space(5)]
        public bool enableMovement = false;  // 是否激活镜头移动
        public float movementSpeed = 2.0f;
        public float movementDamping = 5.0f;
        public string forwardAxis = "Vertical";
        public string sidewaysAxis = "Horizontal";
        public string verticalAxis = "Mouse Y";

        private Camera camera;
        private Vector3 position;
        private float fov = 0.0f;
        private float savedFov = 0.0f;

        public override void Initialize(Transform self)
        {
            camera = self.GetComponentInChildren<Camera>();
        }

        public override void OnEnable(Transform self, Transform target, Vector3 targetOffset)
        {
            position = self.position;

            if (camera != null)
            {
                fov = camera.fieldOfView;
                savedFov = camera.fieldOfView;
            }
        }

        public override void Update(Transform self, Transform target, Vector3 targetOffset)
        {
            // 位置
            if (enableMovement)
            {
                float stepSize = movementSpeed * Time.deltaTime;

                position += Input.GetAxis(forwardAxis) * stepSize * new Vector3(self.forward.x, 0.0f, self.forward.z).normalized;
                position += Input.GetAxis(sidewaysAxis) * stepSize * self.right;
                position += Input.GetAxis(verticalAxis) * stepSize * self.up;
            }

            self.position = Vector3.Lerp(self.position, position, movementDamping * Time.deltaTime);

            // 角度
            if (target != null)
            {
                Quaternion lookAtRotation = Quaternion.LookRotation(target.position + targetOffset - self.position);
                self.rotation = Quaternion.Slerp(self.rotation, lookAtRotation, damping * Time.deltaTime);
            }

            // 拉近拉远
            if (camera != null)
            {
                fov -= Input.GetAxis(fovAxis) * fovSpeed;
                fov = Mathf.Clamp(fov, minFov, maxFov);
                camera.fieldOfView = Mathf.Lerp(camera.fieldOfView, fov, fovDamping * Time.deltaTime);
            }
        }

        public override void OnDisable(Transform self, Transform target, Vector3 targetOffset)
        {
            if (camera != null)
                camera.fieldOfView = savedFov;
        }
    }

二、相机控制器

        上面实现了四种跟随方式的相机,可以根据自己的需要增减,下面是一个相机控制器,把它挂在场景中的相机上,并将跟随目标托到面板上去就可以了。按V切换视角

/// <summary>
/// 镜头控制
/// </summary>
public class VehicleCamera : MonoBehaviour
{    
    public enum Mode { AttachTo, SmoothFollow, MouseOrbit, LookAt };
    public Mode mode = Mode.SmoothFollow;

    public Transform target;
    public bool followCenterOfMass = true;
    public KeyCode changeCameraKey = KeyCode.V;

    // 这里把相机直接声明是为了在面板中调节参数
    public CameraAttachTo attachTo = new CameraAttachTo();
    public CameraSmoothFollow smoothFollow = new CameraSmoothFollow();
    public CameraMouseOrbit mouseOrbit = new CameraMouseOrbit();
    public CameraLookAt lookAt = new CameraLookAt();

    private Mode prevMode;
    private BaseCameraMode[] cameraModes = new BaseCameraMode[0];

    private Transform prevTarget;
    private Rigidbody targetRigidbody;
    private Vector3 localTargetOffset;
    private Vector3 targetOffset;

    void OnEnable()
    {
        cameraModes = new BaseCameraMode[]
        {
            attachTo, smoothFollow, mouseOrbit, lookAt
        };

        foreach (BaseCameraMode cam in cameraModes)
            cam.Initialize(transform);


        OnTargetChange();
        ComputeTargetOffset();
        prevTarget = target;

        cameraModes[(int)mode].OnEnable(transform, target, targetOffset);
        cameraModes[(int)mode].Reset(transform, target, targetOffset);
        prevMode = mode;
    }

    void OnDisable()
    {
        cameraModes[(int)mode].OnDisable(transform, target, targetOffset);
    }

    void LateUpdate()
    {
        // 跟随目标是否变化
        if (target != prevTarget)
        {
            OnTargetChange();
            prevTarget = target;
        }

        ComputeTargetOffset();

        if (Input.GetKeyDown(changeCameraKey))
            NextCameraMode();

        if (mode != prevMode)
        {
            cameraModes[(int)prevMode].OnDisable(transform, target, targetOffset);
            cameraModes[(int)mode].OnEnable(transform, target, targetOffset);
            cameraModes[(int)mode].Reset(transform, target, targetOffset);
            prevMode = mode;
        }

        cameraModes[(int)mode].Update(transform, target, targetOffset);
    }

    /// <summary>
    /// 计算目标偏移
    /// </summary>
    void ComputeTargetOffset()
    {
        if (followCenterOfMass && targetRigidbody != null)
        {
            // 重心不受大小影响
            targetOffset = target.TransformDirection(localTargetOffset);
        }
        else
        {
            targetOffset = Vector3.zero;
        }
    }

    /// <summary>
    /// 切换镜头类型
    /// </summary>
    public void NextCameraMode()
    {
        if (!enabled) return;

        mode++;
        if ((int)mode >= cameraModes.Length)
            mode = (Mode)0;
    }

    /// <summary>
    /// 目标变化
    /// </summary>
    void OnTargetChange()
    {
        // 如果目标是刚体的话计算重心
        if (followCenterOfMass && target != null)
        {
            targetRigidbody = target.GetComponent<Rigidbody>();
            localTargetOffset = targetRigidbody.centerOfMass;
        }
        else
        {
            targetRigidbody = null;
        }

        cameraModes[(int)mode].Reset(transform, target, targetOffset);
    }
}

 

  • 2
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值