Unity 一种更为简洁明了的环绕相机方案

配置方法

创建一个空对象挂载相机控制脚本(在代码中让该对象与角色的位置重合),相机作为其子物体

将相机相对于父物体的距离设定为理想的 相机-人物 距离

指定环绕中心和相机

原理

想要实现环绕,首先就需要获取环绕点和相机的Transform属性。

想要表示相机相对于环绕点的方位,你可能会觉得使用相对位置描述更为方便,其实不然,实际上使用两个欧拉角来描述比使用位置描述更为方便,因为我们后面需要对相机的旋转过程插值,使用位置来描述显然是不便于插值的。

这样,我们就有了相机相对于环绕点的角度,加上距离就可以精确描述相机的位置了。

线性插值

如果要对一个变量进行插值,首先要明确插值的实现。什么是线性插值?

给定两个数,a、b,对a、b进行一次线性插值,再给一个参数t,代表插值的位置。

比如对0、1插值,参数为0.1,那么进行一次插值得到的结果就是0.1。

线性插值的公式是 a + (b-a) * t 。

那么插值有哪些实际应用呢?

- 平滑运动中的相机与角色的距离

- 平滑旋转中的相机与角色的角度,并实现一定时长的惯性旋转效果。

为什么插值会产生延时呢?

因为插值有两个数,一个是当前值,一个是目标值,另外还有一个参数是百分比。

当目标值不再变化之后,当前值逼近目标值需要一定的时间。

两个数之间的差距随着时间的变化曲线,类似于一个方向向下的抛物线。

周围障碍检测

对角色前后左右四个方向进行射线检测,获取返回的距离,取得一个最小值。

如果想要更精确的结果,可以将正交的方向向量分别两两相加,得到四个斜的方向向量,这样就可以做八个方向的距离检测。

相机被遮挡处理

如果相机被遮挡,就将相机距离调整到小于 距离角色水平方向最近的障碍距离 。

在手动调整距离时,记录偏好距离。

如果不再被遮挡,就恢复偏好距离。

代码

using UnityEngine;
using System.Collections;
using UnityEngine.Animations;

public class OrbitCamera : MonoBehaviour
{
    public Transform pivot;
    public Transform camera;
    private float distance;

    public bool distanceAdjustable = true;
    public bool rotationAdjustable = true;

    void Start()
    {
        targetSideRotation = transform.eulerAngles.y;
        currentSideRotation = transform.eulerAngles.y;
        targetUpRotation = transform.eulerAngles.x;
        currentUpRotation = transform.eulerAngles.x;
        distance = -camera.localPosition.z;             //相机局部坐标z值为-1.8,那么相机与距离人物为1.8
    }

    void LateUpdate()
    {
        if (!pivot) return;

        Follow();
        DragRotate();
        ScrollScale();
        OcclusionJudge();
    }

    void Follow()
    {
        if (!pivot.gameObject.GetComponentInParent<ParentConstraint>())
        {
            transform.position = Vector3.Lerp(transform.position, pivot.position, Time.deltaTime * 5);             //相机跟随角色的插值,相机当前帧的实际位置为它们中间10%的位置
            camera.localPosition = -Vector3.forward * distance;                                                     //仅z有值,并且方向为负
        }
        else       //高速运动下不再插值
        {
            transform.position = pivot.position;
            camera.localPosition = -Vector3.forward * distance;
        }
    }

    public float MinimumDegree = 0;
    public float MaximumDegree = 60;
    private float targetSideRotation;
    private float targetUpRotation;
    private float currentSideRotation;
    private float currentUpRotation;
    void DragRotate()
    {
        if (!rotationAdjustable) return;

        if (Input.GetMouseButton(0))
        {
            targetSideRotation += Input.GetAxis("Mouse X") * 5;
            targetUpRotation -= Input.GetAxis("Mouse Y") * 5;
        }

        targetUpRotation = Mathf.Clamp(targetUpRotation, MinimumDegree, MaximumDegree);

        currentSideRotation = Mathf.LerpAngle(currentSideRotation, targetSideRotation, Time.deltaTime * 5);
        currentUpRotation = Mathf.Lerp(currentUpRotation, targetUpRotation, Time.deltaTime * 5);
        transform.rotation = Quaternion.Euler(currentUpRotation, currentSideRotation, 0);
    }

    float MinimumDistance = 1;
    float MaximumDistance = 4;
    void ScrollScale()
    {
        if (!distanceAdjustable) return;

        distance *= (1 - Input.GetAxis("Mouse ScrollWheel") * 0.2f);        //在原值的基础上调整为原值的百分比
        distance = Mathf.Clamp(distance, MinimumDistance, MaximumDistance);
        if(Input.GetAxis("Mouse ScrollWheel")!=0)
            preferdDistance = distance;
    }

    float preferdDistance = 1;
    bool resumable = false;
    void OcclusionJudge()
    {
        if (Physics.Raycast(pivot.position, -camera.forward, distance))               
        {
            resumable = true;

            distance = NearestObstacleDistance(pivot);

            while (Physics.Raycast(pivot.position, -camera.forward, distance) && distance > MinimumDistance)
            {
                distance *= 0.99f;
                distance = Mathf.Clamp(distance, MinimumDistance, MaximumDistance);
                float dist = Mathf.Lerp(-camera.transform.localPosition.z, distance, 1f);
                camera.localPosition = -Vector3.forward * dist;
            }
        }

        if (!resumable) return;

        if (resumable && !Physics.Raycast(pivot.position, -camera.forward, preferdDistance))
        {
            distance = preferdDistance;
            float dist = Mathf.Lerp(-camera.transform.localPosition.z, distance, 1f);
            camera.localPosition = -Vector3.forward * distance;
            resumable = false;
        }
    }

    float NearestObstacleDistance(Transform start)
    {
        float dis = float.MaxValue;
        RaycastHit hit;
        Physics.Raycast(start.position, start.forward, out hit);
        if(hit.distance!=0) dis = Mathf.Min(dis, hit.distance);
        Physics.Raycast(start.position, -start.forward, out hit);
        if (hit.distance != 0) dis = Mathf.Min(dis, hit.distance);
        Physics.Raycast(start.position, start.right, out hit);
        if (hit.distance != 0) dis = Mathf.Min(dis, hit.distance);
        Physics.Raycast(start.position, -start.right, out hit);
        if (hit.distance != 0) dis = Mathf.Min(dis, hit.distance);
        return dis;
    }
}

效果就不演示了,比之前写的那个要好不少

猜你喜欢

转载自blog.csdn.net/weixin_43673589/article/details/123321294