U
15 / 22 · 11 分钟
常见问题

旋转:欧拉角、四元数,以及哪个你能碰

简短回答

怎么旋转一个物体,Quaternion 是什么?

当你已经知道想要的角度时,用 Quaternion.Euler(x, y, z);要转向某个目标,就用 Quaternion.LookRotation 配合 RotateTowards 或 Slerp。绝不要直接往 transform.rotation 上做加法:它存的是四个并非角度的数。

位置和缩放都是可以往上加的三串数。旋转在 Inspector 里看着也是三个数,但它不是——每个新手都是这样发现这件事的:把 transform.rotation 抄进变量,给它的 y 加 1,再写回去,然后看着物体做出没法形容的动作。(C# 甚至不允许在一行里写 transform.rotation.y += 1——那个 property 交给你的只是一份拷贝。)

Inspector 显示给你的,不是实际存着的东西

Unity 把旋转存成 Quaternion:四个数——x、y、z 和 w——合起来描述一根轴和绕这根轴转了多少。这四个数里没有一个是按度算的角度。Inspector 为了显示把它们换算成三个好读的欧拉角,你敲进去的数再反向换算回去。

设置一个你已经知道的旋转

Quaternion.Euler — degrees in, Quaternion out
// Face 90° to the right, level, no roll.
transform.rotation = Quaternion.Euler(0f, 90f, 0f);

// Local rotation instead: relative to the parent, not to the world.
transform.localRotation = Quaternion.Euler(0f, 90f, 0f);

// Add 90° to whatever it is now. Quaternions multiply, they do not add.
transform.rotation *= Quaternion.Euler(0f, 90f, 0f);

转向某个目标

实战中真正常见的是这种情况:敌人要面向玩家,炮塔要咬住目标,角色要转向自己正在走的方向。它只有两步——算出你想要的那个旋转,然后每帧朝它挪一点。

FaceTarget.cs
using UnityEngine;

public class FaceTarget : MonoBehaviour
{
    public Transform target;
    public float turnSpeed = 360f;   // degrees per second

    void Update()
    {
        if (target == null) return;

        Vector3 to = target.position - transform.position;

        // Flatten it, or the enemy tips forward to look down at a short player.
        to.y = 0f;

        // LookRotation complains about a zero vector: standing exactly on the target.
        if (to.sqrMagnitude < 0.0001f) return;

        Quaternion wanted = Quaternion.LookRotation(to);
        transform.rotation = Quaternion.RotateTowards(
            transform.rotation, wanted, turnSpeed * Time.deltaTime);
    }
}

用 RotateTowards 还是 Slerp?

  • RotateTowards 按每秒固定的度数转动,而且会真的转到位。当转速本身就是设计的一部分时用它——比如一座不许瞬间甩过去的炮塔。

  • Slerp 越接近越慢、永远到不了终点,速度还取决于还剩多少角度。适合相机,或任何要柔和、不想像机械的东西。

  • 把 Slerp 写成 Slerp(a, b, speed * Time.deltaTime) 会随帧率变化。修法和位置那套完全一样:1f - Mathf.Exp(-speed * Time.deltaTime)。

让一个东西一直转

transform.Rotate — the one case where you do not touch rotation directly
// Around its own up axis: a coin spinning on the spot.
transform.Rotate(Vector3.up * 90f * Time.deltaTime, Space.Self);

// Around the world's up axis: orbiting behaviour, unaffected by tilt.
transform.Rotate(Vector3.up * 90f * Time.deltaTime, Space.World);

Gimbal lock,以及为什么要自己存一份角度

欧拉角把旋转描述成依次施加的三个转动。当中间那一转到达 90° 时,第一转和第三转就变成了绕同一根轴转,自由度少掉一个。这就是 gimbal lock——第一人称相机在你抬头看正上方时发疯,多半就是它干的。

The pattern that avoids all of it: keep the angle yourself
using UnityEngine;

public class MouseLook : MonoBehaviour
{
    public float sensitivity = 2f;

    // Your own numbers, in degrees, never read back from the transform.
    float yaw, pitch;

    void Update()
    {
        yaw   += Input.GetAxis("Mouse X") * sensitivity;
        pitch -= Input.GetAxis("Mouse Y") * sensitivity;

        // Clamping works because pitch is a plain float you control.
        pitch = Mathf.Clamp(pitch, -80f, 80f);

        transform.rotation = Quaternion.Euler(pitch, yaw, 0f);
    }
}

做出这个的那一课

第三人称环绕镜头

这篇指南单独成篇,是一份可以直接照着做的配方。在课程里,同样的东西会作为贯穿全部五个章的那个项目的一部分来搭建 —— 镜头与操作,第 02 课。