【Unity步步升】各类旋转逻辑的区别,如欧拉旋转、插值旋转、矢量朝向等...及游戏视角案例

在 Unity 中为物体旋转提供了各种 API ,例如 Rotate、RotateAround、LookAt 等方法。但为了避免万向节死锁的问题,一般使用四元数来表示物体的旋转。

而接下来的旋转方法我将不再区分Vector和Transform实现的区别,重点来看方法本身。

目录

一、基础旋转

①Rotate()

②RotateAround()

③LookAt()

二、四元数旋转

①Euler()

②Lerp()

③LookRotation()

三、应用案例

①第一人称相机旋转

②第三人称相机环绕

③第三人称平滑旋转缩放与跟随


一、基础旋转

 图0-1 物体设置

①Rotate()

public void Rotate (Vector3 eulers, Space relativeTo= Space.Self);

应用一个围绕 Z 轴旋转 eulerAngles.z 度、围绕 X 轴旋转 eulerAngles.x 度、围绕 Y 轴旋转 eulerAngles.y 度(按此顺序)的旋转。

旋转采用欧拉角形式的 Vector3 参数。第二个参数是旋转轴,可以将其设置为本地轴 (Space.Self) 或全局轴 (Space.World)。旋转以欧拉角度数表示。

参数从左到右分别是,Vector类型的旋转角度和物体轴类设置。

知晓参数和使用方法后,我们将场景中的两个物体提前设置好,为了看出轴不同的设置带来的影响,把两个物体旋转一定的角度如图0-1。具体实现效果如图1-1~2,使用如下代码:

    [Header("旋转参数")]
    public float rotationSpeed;
    public GameObject aroundPoint; //围绕中心点

    [Header("旋转对象")]
    public GameObject rotateObj1;
    public GameObject rotateObj2;

 void Update()
    {
        //SelfRotate1();
        SelfRotate2();
    }

void SelfRotate1()
    {
        //rotateObj1.transform.Rotate(xAngle, yAngle, zAngle, Space.World); //直接赋值Transform变换
        //rotateObj2.transform.Rotate(xAngle, yAngle, zAngle, Space.Self);

        rotateObj1.transform.Rotate(Vector3.forward * rotationSpeed * Time.deltaTime, Space.World); //通过V3类特定某个轴
        rotateObj2.transform.Rotate(Vector3.forward * rotationSpeed * Time.deltaTime, Space.Self);
    }

 

 图1-1 Space.World

Rotate方法中的Space.World坐标轴设置,世界坐标的设置让物体是围绕着世界坐标轴的Z轴旋转。

图1-2 Space.Self 

Rotate方法中的Space.Self坐标轴设置,与前一种不同,可以看出来很明显的围绕自己的Z坐标轴旋转。


②RotateAround()

public void RotateAround (Vector3 point, Vector3 axis, float angle);

将变换围绕穿过世界坐标中的 point 的 axis 旋转 angle 度。

这会修改变换的位置和旋转。

参数从左到右分别为应围绕的点,围绕点的哪个轴,旋转多少度。

使用设置好的两个物体和一个围绕中心点看一下效果,见图2-1,及以下代码段:

public GameObject aroundPoint; //围绕中心点

void SelfRotate2()
    {
        rotateObj1.transform.RotateAround(aroundPoint.transform.position, Vector3.up, rotationSpeed * Time.deltaTime);
        rotateObj2.transform.RotateAround(aroundPoint.transform.position, Vector3.up, rotationSpeed * Time.deltaTime);
    }

图2-1 RotateAround()

可以看到,两个物体都围绕着设置好的中心点旋转类似星球环绕的效果。


③LookAt()

public void LookAt (Transform target);

public void LookAt (Transform target, Vector3 worldUp= Vector3.up);

旋转变换,使向前矢量指向 target 的当前位置。

然后,它旋转变换以将其向上方向矢量指向 worldUp 矢量暗示的方向。 如果省略 worldUp 参数,该函数将使用世界空间的 Y 轴。 worldUp 只是一个提示矢量。如果向前方向垂直于 worldUp,则旋转的向上矢量将仅匹配 worldUp 矢量。

参数从左到右分别为看向目标,看向轴向。

我们让物体1以Z轴始终看向物体2,物体2保持环绕旋转,效果见图3-1,及以下代码段。

 void SelfRotate3()
    {
        rotateObj1.transform.LookAt(rotateObj2.transform);
        //rotateObj1.transform.LookAt(rotateObj2.transform, rotateObj2.transform.up);
        rotateObj2.transform.RotateAround(aroundPoint.transform.position, Vector3.up, rotationSpeed * Time.deltaTime);
    }

图3-1 LookAt()

其中注意,无论如何都是物体的Z轴始终指向物体,而具体指向后物体想以怎么样的状态保持指向还需要看第二个参数,可以看到上图3-1中看向后是默认X轴向上的,但如果这里的worldUp设置为rotateObj2.transform.up,则意味着以物体Y轴作为世界Y轴朝向,其他轴向类似见下图3-2。

 图3-2 worldUp设置

二、四元数旋转

①Euler()

public static Quaternion Euler (float x, float y, float z);

返回一个围绕 Z 轴旋转 z 度、围绕 X 轴旋转 x 度、围绕 Y 轴旋转 y 度的旋转。

我们将前次课的行为树用上,定义一个物体来回运动。效果见图1-1,及以下代码段。

    /// <summary>
    /// Euler()
    /// </summary>
    void QuaternionRotate1() 
    {
        eulerRotate.y += rotationSpeed * Time.deltaTime;
        rotateObj1.transform.rotation = Quaternion.Euler(eulerRotate);
        rotateObj2.transform.rotation = Quaternion.Euler(eulerRotate);
    }

图1-1 Euler()


②Lerp()

public static Quaternion Lerp (Quaternion a, Quaternion b, float t); 

在 a 和 b 之间插入 t,然后对结果进行标准化处理。参数 t 被限制在 [0, 1] 范围内。

该方法比 Slerp 快,但如果旋转相距很远,其视觉效果也更糟糕。

参数从左到右分别为指定Z轴要对齐的物体,世界upwards方向的V3值。

效果见图2-1,及以下代码段。

    /// <summary>
    /// Lerp()
    /// </summary>
    void QuaternionRotate2()
    {
        rotateObj1.transform.rotation = Quaternion.Lerp(from.rotation, to.rotation, rotationSpeed * Time.time);
        rotateObj2.transform.rotation = Quaternion.Lerp(from.rotation, to.rotation, rotationSpeed * Time.time);
    }

图2-1 Lerp()


③LookRotation()

public static Quaternion LookRotation (Vector3 forward, Vector3 upwards= Vector3.up);

使用指定的 forward 和 upwards 方向创建旋转。

Z 轴将与forward对齐,X 轴与 forward 和 upwards 之间的差积对齐,Y 轴与 Z 和 X 之间的差积对齐。如果 forward 或 upwards 量值为零,则返回恒等。 如果 forward 和 upwards 共线,则返回恒等。

参数从左到右分别为指定Z轴要对齐的物体,世界upwards方向的V3值。

定义两个物体来回运动,将应对齐的Z轴改为两物体间的位置Z值,upwards就设置默认的Y轴up。效果见图3-1,及以下代码段。

    /// <summary>
    /// LookRotation()
    /// </summary>
    void QuaternionRotate3()
    {
        Vector3 relativePos = from.position - to.position; //相对位置
        rotateObj1.transform.rotation = Quaternion.LookRotation(relativePos, Vector3.up);
        rotateObj2.transform.rotation = Quaternion.LookRotation(relativePos, Vector3.up);
    }

图3-1  LookRotation()

三、应用案例

①第一人称相机旋转

   [Header("第一人称")]
    public Camera firstCamera;
    public float mouseSensitivityX = 5.0f;
    public float mouseSensitivityY = 5.0f;
    public float minCameraY = -30.0f;
    public float maxCameraY = 30.0f;

    private float currentRotationY;
    private float currentRotationX;

    /// <summary>
    /// 第一人称角色视角
    /// </summary>
    private void FirstPlayerCameraRotate() {
        currentRotationX += Input.GetAxis("Mouse X") * mouseSensitivityX;
        currentRotationY += Input.GetAxis("Mouse Y") * mouseSensitivityY;

        currentRotationY = AngleLimit(currentRotationY, minCameraY, maxCameraY);
        firstCamera.transform.eulerAngles = new Vector3(-currentRotationY, currentRotationX, 0);
    }

    /// <summary>
    /// 视角角度限制
    /// </summary>
    /// <param name="rotationLimited">应限制的旋转轴</param>
    /// <param name="min">最小值</param>
    /// <param name="max">最大值</param>
    /// <returns>可到达的角度范围</returns>
    private float AngleLimit(float rotationLimited, float min, float max) {
        if (currentRotationY < min) {
            return min;
        }
        if (currentRotationY > max) {
            return max;
        }
        return rotationLimited;
    }

代码段中可以看到分成了两个函数,第一人称视角主要控制函数FirstPlayerCameraRotate()及AngleLimit()视角角度限制函数。

FirstPlayerCameraRotate()控制函数就比较简单,通过对"Mouse X"和"Mouse Y"轴向的获取,乘以提前设置好的鼠标灵敏度赋值给最新旋转XY。如果好奇这里为什么是对-currentRotationY当作V3的x轴值,在Unity中去试一下Transfom Rotation的值看下效果就明白了。

AngleLimit()视角角度限制函数则是想实现对视角上下有限制,将当前最新旋转值currentRotationY与最大值和最小值比较,若大于最大值则不能继续旋转,小于最小值同样。具体实现效果如下图1

图1 第一人称视角

②第三人称相机环绕

    [Header("第三人称")]
    public Camera thirdCamera;
    public GameObject followObj;
    public float rotateSpeedX = 10.0f;
    public float rotateSpeedY = 10.0f;

    private Quaternion curRotation;
    private float currentMouseX = 0.0f;
    private float currentMouseY = 0.0f;


    void Start()
    {
        currentRotationX = thirdCamera.transform.eulerAngles.x;
        currentRotationY = thirdCamera.transform.eulerAngles.y;
    }    

    /// <summary>
    /// 第三人称角色视角
    /// </summary>
    private void ThirdPlayerCameraRotate()
    {
        try {
            if (followObj != null)
            {
                currentMouseX += Input.GetAxis("Mouse X") * rotateSpeedX;
                currentMouseY += Input.GetAxis("Mouse Y") * rotateSpeedY;
                curRotation = Quaternion.Euler(currentMouseY, currentMouseX, 0); //将V3转化为Quaternion
                thirdCamera.transform.RotateAround(followObj.transform.position, Vector3.up, currentMouseX);
            }
        }
        catch {
            Debug.Log("出了点问题,可能是追随目标没设置");
        }
        

    }

代码段中可以看到只有一个函数ThirdPlayerCameraRotate()第三人称控制函数。

首先函数需要对应的跟随物体followObj,其次同样是对"Mouse X"和"Mouse Y"轴向的获取,利用上述Euler函数将V3数值转化为四元素Quaternion数值,后就可以使用RotateAround这一类Transform四元素挂钩的函数了。具体效果见下图2

图2 第三人称环绕旋转 

③第三人称平滑旋转缩放与跟随

    [Header("相机")]
    public Camera thirdCamera;
    public GameObject target;

    [Header("相机缩放")]
    public float zoomSpeed = 2.0f;
    public float maxDistance = 15.0f;
    public float minDistance = 2.0f;
    private float cameraDistance;
    private Vector3 cameraPosition;

    [Header("相机旋转")]
    public float rotateSpeedX;
    public float rotateSpeedY;
    public float maxRotationY = 60.0f;
    public float minRotationY = -10.0f;
    private float rotateY;
    private float rotateX;
    private Quaternion cameraRotation;

    [Header("平滑设置")]
    public bool isDamping = false;
    public float damping = 5.0f;

    void Update()
    {
        ThirdCameraControll();
    }

    /// <summary>
    /// 第三相机控制
    /// </summary>
    void ThirdCameraControll() {
        //旋转
        rotateX += Input.GetAxis("Mouse X") * rotateSpeedX * Time.deltaTime;
        rotateY += Input.GetAxis("Mouse Y") * rotateSpeedY * Time.deltaTime;
        rotateY = AngleLimit(rotateY, minRotationY, maxRotationY);
        cameraRotation = Quaternion.Euler(rotateY, rotateX, 0);

        //缩放
        cameraDistance -= Input.GetAxis("Mouse ScrollWheel") * zoomSpeed;
        cameraDistance = Mathf.Clamp(cameraDistance, minDistance, maxDistance);

        //位置控制
        cameraPosition = cameraRotation * new Vector3(0.0f, 0.0f, -cameraDistance) + target.transform.position;

        //平滑处理
        if (isDamping)
        {
            thirdCamera.transform.position = Vector3.Lerp(thirdCamera.transform.position ,cameraPosition, damping * Time.deltaTime);
            thirdCamera.transform.rotation = Quaternion.Lerp(thirdCamera.transform.rotation, cameraRotation, damping * Time.deltaTime);
        }
        else {
            thirdCamera.transform.position = cameraPosition;
            thirdCamera.transform.rotation = cameraRotation;
        }
    }

    /// <summary>
    /// 角度限制
    /// </summary>
    /// <param name="LimitedValue"></param>
    /// <param name="min"></param>
    /// <param name="max"></param>
    /// <returns></returns>
    private float AngleLimit(float LimitedValue, float min, float max) {
        if (LimitedValue < -360) return LimitedValue += 360;
        if (LimitedValue > 360) return LimitedValue -= 360;

        return Mathf.Clamp(LimitedValue, min, max);
    }

 最终效果入下图3

 图3 线性插值和欧拉的处理

至于万向节死锁问题,我这不去讲述了感兴趣的小伙伴们可以自行学习。

喜欢的话还请关注B站账号:波仔羔。好了如果觉得对你有帮助的话不如给个点赞加关注,如果有问题也欢迎评论或私聊我解决哦。

猜你喜欢

转载自blog.csdn.net/PinaColadaONE/article/details/123941480