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系统化教学与最佳实践分享。