Unity基础:使用Transform控制物体旋转——Rotate与LookAt详解

Unity基础:使用Transform控制物体旋转------Rotate与LookAt详解

大家好,欢迎回到Unity教程系列。上一篇文章我们学习了Transform的移动操作,今天我们要深入旋转的主题。旋转在游戏中同样无处不在------角色转身、炮塔瞄准、摄像机环绕、物体自转------旋转操作比移动更加微妙,因为它涉及四元数(Quaternion)这种不同于直觉的数学工具。

这篇文章会从最直观的Rotate方法开始,逐步深入到LookAt、Slerp等高级旋转技巧,帮你建立起对Unity旋转系统的全面认知。

一、旋转的基本概念

1.1 旋转的表示方式

Unity中有三种表示旋转的方式,它们各有用途:

欧拉角(Euler Angles):三个角度值(绕X/Y/Z轴的旋转角度),单位是度。最直观------"绕Y轴旋转90度"人人都能理解。Inspector中显示的就是欧拉角。

四元数(Quaternion):Unity内部存储旋转的方式。四个分量(x, y, z, w),不直观但数学上完美------没有万向节死锁问题。

旋转矩阵(Rotation Matrix):3×3的变换矩阵,不常用于直接操作。

📝 日常开发中,我们用欧拉角来思考和设置旋转,但实际旋转计算(特别是插值)用四元数。Unity在中间做了大量转换工作。

1.2 什么是万向节死锁

💡 万向节死锁(Gimbal Lock)是使用欧拉角表示旋转时的一个固有问题:当第二个旋转轴旋转到±90度时,第一个和第三个旋转轴重合,导致一个旋转自由度丢失。

举个例子:在Unity编辑器中,将一个物体的Rotation的X设为90度,然后尝试分别绕Y轴和Z轴旋转它------你会发现它们产生了相同的旋转效果,你丢失了一个旋转维度。

四元数从根本上解决了这个问题,这也是Unity内部使用四元数的原因。

二、transform.Rotate------增量旋转

2.1 基本用法

csharp 复制代码
void Update()
{
    // 绕世界Y轴旋转(最常用的自转方式)
    transform.Rotate(Vector3.up * rotationSpeed * Time.deltaTime);
    
    // 绕自身X轴旋转
    transform.Rotate(Vector3.right * rotationSpeed * Time.deltaTime);
    
    // 绕世界Z轴旋转
    transform.Rotate(Vector3.forward * rotationSpeed * Time.deltaTime);
    
    // 组合旋转
    transform.Rotate(new Vector3(10, 20, 30) * Time.deltaTime);
}

2.2 Rotate的坐标系参数

与Translate类似,Rotate也支持坐标系选择:

csharp 复制代码
// 绕世界坐标系的Y轴旋转(无论物体当前朝向如何)
transform.Rotate(Vector3.up * speed * Time.deltaTime, Space.World);

// 绕物体自身坐标系的Y轴旋转(默认)
transform.Rotate(Vector3.up * speed * Time.deltaTime, Space.Self);

💡 大多数情况下,自转用Space.Self(默认),而"绕世界原点旋转"用Space.World。

2.3 Rotate的其他重载形式

csharp 复制代码
// 绕某个指定轴旋转(在世界空间中)
transform.Rotate(Vector3.up, angle);

// 绕某个指定点旋转(世界坐标系)
transform.RotateAround(pivotPoint.position, Vector3.up, speed * Time.deltaTime);

// 绕某个轴和角度旋转(欧拉角)
transform.Rotate(45f, 90f, 0f); // X=45, Y=90, Z=0

2.4 RotateAround------围绕其他物体旋转

csharp 复制代码
void Update()
{
    // 围绕目标物体旋转(如月球绕地球)
    transform.RotateAround(earth.position, Vector3.up, orbitSpeed * Time.deltaTime);
    
    // 同时自身旋转
    transform.Rotate(Vector3.up * selfRotationSpeed * Time.deltaTime);
}

RotateAround是实现公转效果最方便的方式。

三、transform.LookAt------朝向目标

3.1 基本用法

csharp 复制代码
void Update()
{
    // 让物体面向目标物体的位置
    transform.LookAt(target);
    
    // 面向世界坐标中的某个点
    transform.LookAt(new Vector3(0, 0, 10));
    
    // 只修改Y轴旋转(保持物体的X和Z旋转不变,用于2D或特定需求)
    Vector3 lookTarget = target.position;
    lookTarget.y = transform.position.y; // 保持水平朝向
    transform.LookAt(lookTarget);
}

3.2 LookAt的up参数

LookAt的第二个参数指定物体的"上方"方向。默认是Vector3.up(世界Y轴),这适用于大多数站立在地上的情况:

csharp 复制代码
// 标准用法(上方向是世界Y轴)
transform.LookAt(target);

// 物体倒挂在天花板上
transform.LookAt(target, -Vector3.up);

// 自定义上方向
transform.LookAt(target, customUpDirection);

3.3 平滑LookAt

直接用LookAt会让物体瞬间转向,通常不自然。配合Quaternion.RotateTowards或Slerp实现平滑转向:

csharp 复制代码
void Update()
{
    // 计算朝向目标的方向
    Vector3 direction = target.position - transform.position;
    direction.y = 0; // 只在水平面上转向
    
    if (direction != Vector3.zero)
    {
        // 计算目标旋转
        Quaternion targetRotation = Quaternion.LookRotation(direction);
        
        // 平滑转向
        transform.rotation = Quaternion.RotateTowards(
            transform.rotation,
            targetRotation,
            turnSpeed * Time.deltaTime
        );
    }
}

四、transform.rotation 与 transform.eulerAngles

4.1 transform.rotation------四元数

csharp 复制代码
// 获取当前旋转(四元数)
Quaternion currentRot = transform.rotation;

// 设置为无旋转(朝向世界坐标轴)
transform.rotation = Quaternion.identity;

// 从欧拉角创建四元数并赋值
transform.rotation = Quaternion.Euler(0, 90, 0);

4.2 transform.eulerAngles------欧拉角

csharp 复制代码
// 获取欧拉角(只读场景下获取)
Vector3 euler = transform.eulerAngles;
float yAngle = euler.y;

// 设置欧拉角
transform.eulerAngles = new Vector3(0, 45, 0);

// 单独设置某个轴
Vector3 currentEuler = transform.eulerAngles;
currentEuler.y = 90;
transform.eulerAngles = currentEuler;

⚠️ 注意:从eulerAngles读取值的范围是0-360度。负角度会被转换(如-90度变成270度)。这是因为欧拉角有多种等价表示,Unity使用了0-360的范围。

4.3 transform.localRotation

csharp 复制代码
// 相对于父物体的旋转
transform.localRotation = Quaternion.identity;
// 等同视角归零,物体的世界旋转将等于父物体的世界旋转

五、Quaternion常用操作

5.1 创建四元数

csharp 复制代码
// 无旋转(等同于Euler(0,0,0))
Quaternion identity = Quaternion.identity;

// 从欧拉角创建
Quaternion rot90Y = Quaternion.Euler(0, 90, 0);
Quaternion rot30X = Quaternion.Euler(30, 0, 0);

// 从方向向量创建(物体Z轴朝向direction方向)
Vector3 direction = target.position - transform.position;
Quaternion lookRotation = Quaternion.LookRotation(direction);

// 带自定义上方向的LookRotation
Quaternion lookRotWithUp = Quaternion.LookRotation(direction, Vector3.up);

5.2 四元数乘法------组合旋转

csharp 复制代码
// 四元数乘法组合旋转:先应用rotA再应用rotB
Quaternion combined = rotB * rotA;

// 示例:先绕Y轴转90°,再绕X轴转30°
Quaternion rotY = Quaternion.Euler(0, 90, 0);
Quaternion rotX = Quaternion.Euler(30, 0, 0);
Quaternion combinedRot = rotX * rotY; // 注意顺序!

⚠️ 四元数乘法不满足交换律a * b ≠ b * a。旋转的顺序非常重要。

5.3 四元数与向量

csharp 复制代码
// 用四元数旋转一个向量
Vector3 forward = Vector3.forward;
Vector3 rotatedForward = transform.rotation * forward;
// rotatedForward 等于 transform.forward

// 用四元数旋转一个方向
Vector3 direction = Quaternion.Euler(0, 45, 0) * Vector3.forward;
// direction 在世界空间中朝向东北方向

六、旋转的平滑过渡

6.1 Quaternion.Slerp------球面线性插值

💡 Slerp是旋转插值中最常用的方法。它在球面上做等速插值,保证旋转速度均匀:

csharp 复制代码
void Update()
{
    // 平滑旋转到目标旋转
    transform.rotation = Quaternion.Slerp(
        transform.rotation, 
        targetRotation, 
        rotationSpeed * Time.deltaTime
    );
}

6.2 Quaternion.Lerp------线性插值

Lerp是Slerp的更快但不太精确的版本:

csharp 复制代码
// 性能更好,但在大角度旋转时速度不均匀
transform.rotation = Quaternion.Lerp(
    transform.rotation, 
    targetRotation, 
    speed * Time.deltaTime
);

📝 对于大多数游戏场景,Slerp和Lerp的差异肉眼几乎看不出。在性能敏感的移动平台,可以考虑使用Lerp。

6.3 Quaternion.RotateTowards------匀速旋转

csharp 复制代码
void Update()
{
    // 以固定角速度向目标旋转(每帧最多旋转maxDegreesDelta度)
    float maxDegrees = turnSpeed * Time.deltaTime;
    transform.rotation = Quaternion.RotateTowards(
        transform.rotation,
        targetRotation,
        maxDegrees
    );
}

6.4 三种旋转插值方法对比

方法 速度 质量 适用场景
Slerp 指数衰减(越近越慢) 最佳(匀速球面) 需要高质量旋转过渡
Lerp 指数衰减 良好 性能敏感、小角度旋转
RotateTowards 恒定角速度 良好 需要固定转速的转向

七、实用旋转脚本

7.1 炮塔瞄准

csharp 复制代码
using UnityEngine;

public class TurretAiming : MonoBehaviour
{
    public Transform turretBase;  // Y轴旋转(水平转向)
    public Transform turretBarrel; // X轴旋转(垂直升降)
    public Transform target;
    
    public float horizontalSpeed = 90f;
    public float verticalSpeed = 45f;
    public float verticalMin = -10f;
    public float verticalMax = 45f;
    
    void Update()
    {
        if (target == null) return;
        
        Vector3 directionToTarget = target.position - turretBase.position;
        
        // 水平旋转(绕Y轴)
        Vector3 horizontalDirection = directionToTarget;
        horizontalDirection.y = 0;
        
        if (horizontalDirection != Vector3.zero)
        {
            Quaternion horizontalTarget = Quaternion.LookRotation(horizontalDirection);
            turretBase.rotation = Quaternion.RotateTowards(
                turretBase.rotation, 
                horizontalTarget, 
                horizontalSpeed * Time.deltaTime
            );
        }
        
        // 垂直旋转(限制角度)
        float verticalAngle = -Vector3.SignedAngle(
            turretBase.forward, 
            directionToTarget.normalized, 
            turretBase.right
        );
        verticalAngle = Mathf.Clamp(verticalAngle, verticalMin, verticalMax);
        turretBarrel.localRotation = Quaternion.RotateTowards(
            turretBarrel.localRotation,
            Quaternion.Euler(verticalAngle, 0, 0),
            verticalSpeed * Time.deltaTime
        );
    }
}

7.2 简单的Billboard效果

让物体始终面向摄像机(Sprite、血条等常用):

csharp 复制代码
using UnityEngine;

public class Billboard : MonoBehaviour
{
    void LateUpdate()
    {
        // 让物体的正面始终朝向摄像机
        transform.LookAt(transform.position + Camera.main.transform.forward);
        
        // 或者完全面向摄像机
        // transform.forward = Camera.main.transform.forward;
    }
}

八、常见问题与排查

8.1 旋转后物体抖动

可能原因:在Update中使用LookAt或Slerp时速度值设置过大,导致过度校正。

解决方案:降低旋转速度,或在到达足够接近的角度时停止旋转:

csharp 复制代码
float angle = Quaternion.Angle(transform.rotation, targetRotation);
if (angle < 0.1f)
{
    transform.rotation = targetRotation; // 精确设置,停止抖动
}

8.2 eulerAngles读取的值与Inspector不一致

这是正常的。eulerAngles返回的是0-360范围的值,而Inspector可能显示负数(如Inspector显示-90,eulerAngles返回270)。

8.3 旋转到一半就停了

检查是否遇到了万向节死锁。改用Quaternion操作可以避免。

九、本篇总结

✅ 本文核心知识点回顾:

  • transform.Rotate()做增量旋转,支持Space.World和Space.Self
  • transform.LookAt()让物体面向目标,用于快速朝向设置
  • transform.rotation存储四元数,transform.eulerAngles存储欧拉角
  • Quaternion.Slerp做球面线性插值,是最平滑的旋转过渡方式
  • Quaternion.RotateTowards以固定角速度转向目标
  • 四元数乘法组合旋转,注意顺序(不满足交换律)
  • Quaternion.LookRotation从方向向量创建四元数

📝 下一篇文章将学习使用Transform控制物体缩放------localScale与lossyScale的区别。

动手任务

① 让一个Cube绕Y轴自转,同时绕场景中的一个Point绕转

② 实现一个平滑LookAt效果(炮塔缓慢转向目标)

③ 对比Slerp和RotateTowards的行为差异


关于作者:拥有多年Unity开发经验的游戏开发者,专注Unity系统化教学与最佳实践分享。

相关推荐
平行云3 小时前
3D应用推流太贵太慢?ImmerShare Lite:利用本地算力实现极简一键分享
unity·ue5·实时云渲染·云桌面·像素流·云游戏·串流
淡海水4 小时前
11-04-Unity-Span-foreach-LINQ的分配与热路径边界
unity·c#·linq·foreach·span
灸木1 天前
Unity 多线程与并发:什么时候该用多线程?
unity
淡海水1 天前
11-02-Unity-Mono-vs-IL2CPP-两种脚本后端的数据结构行为差异
数据结构·unity·游戏引擎·il2cpp·mono
彧azz1 天前
初学Unity:编辑器
笔记·学习·unity·游戏引擎
小小数媒成员1 天前
GPU优化(1)
游戏·unity·游戏引擎
淡海水1 天前
11-03-Unity-List-T-和Dictionary-TKey-TValue-的性能调优实战
算法·unity·c#·list·dictionary
小小数媒成员2 天前
如何优化代码适应托管内存?
游戏·unity·游戏引擎