第4章 Rigidbody API
在前面的章节中,我们已经学习了 Rigidbody 的基本概念、常用属性以及各种力的使用方法。从这一章开始,我们将深入学习 Rigidbody 提供的各种 API,掌握如何通过代码准确控制物体的运动、旋转、休眠状态以及物理属性。
Unity 的 Rigidbody 不仅能够响应物理引擎的模拟,还提供了大量用于控制物体行为的接口。例如,可以直接修改物体的速度,实现瞬间移动,控制角速度,设置重心,调整惯性张量等。这些 API 广泛应用于角色控制、载具系统、投射物、AI、动画与物理结合等各种游戏开发场景。
需要注意的是,不同 API 的工作方式并不一样。有些 API 会参与物理模拟,有些则会直接修改物体状态;有些适合在FixedUpdate中调用,有些则更适合初始化或特殊场景使用。如果使用不当,可能会导致穿模、抖动、物理异常甚至网络同步错误。
因此,本章不仅会介绍每个 API 的作用和使用方法,还会结合实际开发中的案例,分析它们之间的区别、适用场景以及常见错误,协助读者真正理解 Rigidbody 的工作机制,而不仅仅是记住几个函数名称。
4.1 velocity
velocity 是 Rigidbody 中最常用的属性之一,用于表明刚体当前的线速度(Linear Velocity)。它决定了物体每秒沿各个方向移动的速度,也是 Unity 物理系统计算物体运动的重大依据。
当物体受到重力、碰撞、力(Force)等物理作用时,实际上都是通过改变 Rigidbody 的 velocity 来影响物体的运动。因此,理解 velocity 的工作原理,对于掌握 Unity 物理开发至关重大。
velocity 的类型为 Vector3,分别表明物体在 X、Y、Z 三个坐标轴方向上的速度。
public Vector3 velocity;
例如:
using UnityEngine;
public class VelocityDemo : MonoBehaviour
{
private Rigidbody rb;
private void Start()
{
rb = GetComponent<Rigidbody>();
Debug.Log(rb.velocity);
}
}
运行后,如果物体静止,输出结果一般为:
(0.0, 0.0, 0.0)
如果物体正在下落,则可能输出:
(0.0, -5.6, 0.0)
表明:
- X 方向速度为 0。
- Y 方向速度为 -5.6,即每秒向下移动 5.6 米。
- Z 方向速度为 0。
需要注意的是,velocity 表明的是世界坐标系(World Space)中的速度,而不是物体自身坐标系(Local Space)中的速度。这意味着即使物体发生旋转,velocity 的方向也不会自动跟随物体朝向变化,而始终以世界坐标作为参考。
4.1.1 velocity 的单位
许多初学者认为 velocity 的单位是“每帧移动多少距离”,实际上这是一个超级常见的误区。
velocity 的单位是:
米/秒(Units Per Second)
在 Unity 中,一般约定:
1 Unit ≈ 1 Meter
因此:
rb.velocity = new Vector3(5, 0, 0);
表明:
物体每秒向 X 正方向移动 5 米。
而不是每一帧移动 5 米。
例如,假设游戏运行在 60 FPS。
速度为:
5 m/s
那么每一帧实际移动距离约为:
5 ÷ 60 ≈ 0.0833 米
如果运行在 120 FPS:
5 ÷ 120 ≈ 0.0417 米
可以发现,无论帧率如何变化,只要使用 Rigidbody 的物理模拟,最终每秒移动的距离始终保持约 5 米,这正是物理系统与直接使用 transform.Translate最大的区别之一。
4.1.2 读取物体当前速度
读取当前速度是 velocity 最常见的用途之一。
例如:
using UnityEngine;
public class VelocityReader : MonoBehaviour
{
public Rigidbody rb;
private void FixedUpdate()
{
Debug.Log(rb.velocity);
}
}
控制台可能输出:
(2.1, -3.4, 0.0)
(2.1, -3.6, 0.0)
(2.1, -3.8, 0.0)
从输出可以看出:
- X 方向速度保持不变。
- Y 方向速度不断减小,这是重力持续加速下落造成的。
- Z 方向没有运动。
许多游戏都会根据当前速度实现不同的逻辑,例如:
判断角色是否正在移动:
if (rb.velocity.magnitude > 0.1f)
{
Debug.Log("角色正在移动");
}
判断角色是否已经停止:
if (rb.velocity.sqrMagnitude < 0.01f)
{
Debug.Log("角色已经停止");
}
这里使用 sqrMagnitude 可以避免开平方运算,性能比 magnitude 更高,因此在频繁判断速度大小时更推荐使用。
4.1.3 修改 velocity
除了读取速度外,还可以直接修改 velocity。
例如,让物体一直向前运动:
using UnityEngine;
public class VelocityMove : MonoBehaviour
{
public Rigidbody rb;
private void Start()
{
rb.velocity = Vector3.forward * 5f;
}
}
运行后,物体会一直以每秒 5 米的速度向世界 Z 正方向移动。
如果希望向右运动:
rb.velocity = Vector3.right * 8f;
向左运动:
rb.velocity = Vector3.left * 3f;
向上运动:
rb.velocity = Vector3.up * 10f;
向下运动:
rb.velocity = Vector3.down * 5f;
由于直接修改的是速度,因此物体不会逐渐加速,而是立即获得新的速度。
例如:
rb.velocity = new Vector3(0, 20, 0);
物体会瞬间获得一个向上的速度,随后受到重力影响逐渐减速,最终开始下落。
这种方式常用于:
- 跳跃。
- 子弹发射。
- 冲刺。
- 爆炸后的弹飞。
- 瞬间改变运动方向。
4.1.4 velocity 与 AddForce 的区别
许多初学者容易混淆 velocity 和 AddForce,它们虽然都能让物体运动,但工作方式完全不同。
直接修改 velocity:
rb.velocity = Vector3.forward * 10f;
特点:
- 立即修改速度。
- 不会产生加速过程。
- 会覆盖之前的速度。
- 效果立即生效。
使用 AddForce
rb.AddForce(Vector3.forward * 10f);
特点:
- 给刚体施加一个力。
- 是否立即产生明显速度变化取决于质量、阻力和力的大小。
- 会产生加速过程。
- 更符合真实物理规律。
例如,一个正在向左移动的物体:
速度 = (-5, 0, 0)
执行:
rb.velocity = Vector3.right * 5;
结果立即变为:
(5, 0, 0)
原来的速度完全被替换。
而如果执行:
rb.AddForce(Vector3.right * 50);
则物体不会立刻变成每秒 5 米,而是逐渐减速、停止,再逐渐向右加速,具体效果受到质量和阻力等因素影响。
因此,两者适用于不同场景:
一般来说,如果希望立即得到一个确定的速度,使用 velocity;如果希望模拟真实受力过程,则使用 AddForce()。
4.1.5 在 FixedUpdate 中修改 velocity
由于 velocity 属于物理系统的一部分,因此提议在 FixedUpdate 中进行修改,而不是 Update。
推荐写法:
using UnityEngine;
public class PlayerMove : MonoBehaviour
{
public Rigidbody rb;
public float speed = 5f;
private void FixedUpdate()
{
float h = Input.GetAxis("Horizontal");
float v = Input.GetAxis("Vertical");
Vector3 dir = new Vector3(h, 0, v).normalized;
rb.velocity = dir * speed;
}
}
这样可以保证物理更新与 Rigidbody 的模拟保持同步,避免不同帧率下产生不一致的运动表现。
如果在 Update 中频繁修改 velocity,由于 Update 的调用频率受帧率影响,而物理模拟运行在固定时间步长中,可能导致运动不稳定或出现轻微抖动。因此,涉及 Rigidbody 的速度控制,应优先放在 FixedUpdate 中执行。
4.1.6 使用 velocity 的注意事项
虽然 velocity 使用简单,但仍有一些容易踩坑的地方。
不要每帧无条件覆盖所有速度。
例如:
rb.velocity = Vector3.forward * 5;
如果持续这样赋值,会把重力产生的 Y 方向速度也覆盖掉,导致物体无法正常下落。
正确的做法一般是保留不需要修改的轴向速度,例如:
Vector3 velocity = rb.velocity;
velocity.x = 5f;
rb.velocity = velocity;
或者角色移动时保留竖直方向速度:
Vector3 velocity = rb.velocity;
velocity.x = moveX;
velocity.z = moveZ;
rb.velocity = velocity;
这样重力依旧能够正常作用于 Y 方向。
此外,不提议将 velocity 与直接修改 transform.position 混合使用,否则容易绕过物理系统,引起碰撞检测异常、穿模或抖动等问题。
掌握 velocity 的正确使用方法,是实现角色控制、射击游戏、平台跳跃、物理交互等功能的重大基础,也是后续学习 MovePosition、MoveRotation 以及客户端预测移动等高级内容的前提。
4.2 angularVelocity
除了线速度(velocity)之外,Rigidbody 还提供了用于控制旋转速度的属性——angularVelocity。
如果说 velocity 决定的是物体移动得有多快,那么 angularVelocity 决定的就是物体旋转得有多快。
例如:
- 风扇不断旋转。
- 车轮滚动。
- 陀螺旋转。
- 子弹击中木箱后木箱翻滚。
- 骰子被抛出后不断旋转。
这些旋转效果,本质上都是由角速度控制的。
angularVelocity 的类型同样是 Vector3。
public Vector3 angularVelocity;
其中:
- X 表明绕世界 X 轴旋转速度。
- Y 表明绕世界 Y 轴旋转速度。
- Z 表明绕世界 Z 轴旋转速度。
和 velocity 一样,它也是 Rigidbody 当前状态的一部分,物理引擎会根据各种力矩(Torque)、碰撞以及阻力不断修改它的值。
4.2.1 angularVelocity 的单位
许多初学者看到 angularVelocity 是一个 Vector3,容易误以为它表明的是每秒旋转多少度。
实际上并不是。
angularVelocity 的单位是:
弧度/秒(Radians Per Second)
而不是:
度/秒(Degrees Per Second)
这一点超级重大。
数学中:
180° = π rad
因此:
1 rad ≈ 57.2958°
例如:
rb.angularVelocity = Vector3.up;
表明:
绕 Y 轴每秒旋转 1 弧度。
换算成角度约为:
57.3°/秒
如果希望每秒旋转 360°,不能直接写:
rb.angularVelocity = Vector3.up * 360;
由于这里的单位不是度,而是弧度。
正确写法应该进行单位转换:
float rad = Mathf.Deg2Rad * 360f;
rb.angularVelocity = Vector3.up * rad;
或者:
rb.angularVelocity = Vector3.up * Mathf.PI * 2f;
由于:
360° = 2π rad
4.2.2 读取当前角速度
读取角速度的方法与读取线速度完全一样。
例如:
using UnityEngine;
public class ReadAngularVelocity : MonoBehaviour
{
public Rigidbody rb;
private void FixedUpdate()
{
Debug.Log(rb.angularVelocity);
}
}
如果物体正在绕 Y 轴旋转,控制台可能输出:
(0.0, 3.2, 0.0)
表明:
- X 轴没有旋转。
- Y 轴角速度为 3.2 rad/s。
- Z 轴没有旋转。
如果物体发生翻滚,则可能输出:
(2.5, 0.8, 1.6)
说明三个方向都存在旋转速度。
同样,可以通过:
rb.angularVelocity.magnitude
获取整体旋转速度大小。
或者:
rb.angularVelocity.sqrMagnitude
判断是否仍在旋转。
例如:
if (rb.angularVelocity.sqrMagnitude < 0.01f)
{
Debug.Log("物体几乎停止旋转");
}
4.2.3 修改 angularVelocity
和 velocity 一样,可以直接修改角速度。
例如:
using UnityEngine;
public class Spin : MonoBehaviour
{
public Rigidbody rb;
private void Start()
{
rb.angularVelocity = Vector3.up * Mathf.PI;
}
}
表明:
物体绕 Y 轴每秒旋转 180°。
如果希望绕 X 轴旋转:
rb.angularVelocity = Vector3.right * Mathf.PI;
绕 Z 轴旋转:
rb.angularVelocity = Vector3.forward * Mathf.PI;
同时绕多个轴旋转:
rb.angularVelocity = new Vector3(2, 4, 1);
这样物体会产生更加复杂的翻滚效果。
需要注意的是,直接修改 angularVelocity 会立即改变物体当前的旋转速度,而不会经历逐渐加速的过程。
4.2.4 angularVelocity 与 AddTorque 的区别
许多开发者第一次接触旋转时,会疑惑到底应该使用 angularVelocity,还是使用 AddTorque。
实际上,它们的关系与 velocity 和 AddForce十分类似。
直接修改角速度:
rb.angularVelocity = Vector3.up * Mathf.PI;
特点:
- 立即获得目标旋转速度。
- 不思考加速过程。
- 会覆盖当前角速度。
- 效果立即生效。
施加力矩:
rb.AddTorque(Vector3.up * 20f);
特点:
- 给物体施加旋转力矩。
- 会逐渐增加旋转速度。
- 会受到质量、惯性张量、角阻力等因素影响。
- 更符合真实物理规律。
例如,一个静止的木箱:
执行:
rb.angularVelocity = Vector3.up * Mathf.PI;
木箱会立刻开始匀速旋转。
而执行:
rb.AddTorque(Vector3.up * 20);
木箱会慢慢开始旋转,并不断增加角速度。
因此:
如果需要一个确定的旋转速度,直接修改 angularVelocity 更简单;如果需要真实的物理旋转效果,则应使用 AddTorque。
4.2.5 停止旋转
停止物体旋转最简单的方法就是将角速度设置为零。
例如:
rb.angularVelocity = Vector3.zero;
这样物体会立即停止旋转。
如果只是停止某一个方向,例如停止绕 Y 轴旋转:
Vector3 angular = rb.angularVelocity;
angular.y = 0;
rb.angularVelocity = angular;
另外,也可以通过增加角阻力(angularDrag)让物体逐渐停止旋转,而不是瞬间停止。
例如:
rb.angularDrag = 5f;
角阻力越大,旋转停止得越快。
4.2.6 在 FixedUpdate 中修改 angularVelocity
由于 angularVelocity 属于物理系统,因此提议在 FixedUpdate中进行修改。
例如:
using UnityEngine;
public class RotateObject : MonoBehaviour
{
public Rigidbody rb;
private void FixedUpdate()
{
rb.angularVelocity = Vector3.up * Mathf.PI;
}
}
这样能够保证物理更新与 Rigidbody 保持同步。
如果在 Update中不断修改角速度,在不同帧率下可能产生轻微的不一致,因此涉及 Rigidbody 的旋转控制,应优先使用 FixedUpdate。
4.2.7 使用 angularVelocity 的注意事项
虽然 angularVelocity 使用方式与 velocity 十分类似,但仍有几个需要特别注意的地方。
第一,它的单位是弧度而不是角度,这是最容易出错的地方。如果直接将角度数值赋给 angularVelocity,物体一般会旋转得远超预期。
其次,不提议每一帧都无条件覆盖角速度。例如:
rb.angularVelocity = Vector3.up * Mathf.PI;
如果持续执行这段代码,任何由碰撞、爆炸或力矩产生的旋转都会被立即覆盖,物理模拟效果将失去意义。
此外,不要同时使用 transform.Rotate() 与 angularVelocity 控制同一个 Rigidbody。transform.Rotate() 会直接修改 Transform,而 angularVelocity 属于物理系统,两者混合使用容易导致旋转抖动、碰撞异常以及物理模拟不一致等问题。
最后,如果物体的 isKinematic 被设置为 true,或者通过约束(Constraints)冻结了某些旋转轴,那么对应方向上的 angularVelocity 将不会产生实际效果。
熟练掌握 angularVelocity 后,就能够更加灵活地控制各种旋转运动,为后续学习 MoveRotation、力矩(Torque)以及刚体约束等内容打下基础。
4.3 MovePosition
在开发游戏时,我们常常需要通过代码移动物体。例如角色行走、平台移动、电梯升降、NPC 巡逻等。
对于普通 GameObject,可以直接修改 transform.position;而对于带有 Rigidbody 的物体,Unity 更推荐使用 MovePosition() 来进行移动。
MovePosition() 是 Rigidbody 提供的一个专门用于移动刚体的方法,它能够在移动物体的同时保持物理系统正常工作,避免直接修改 Transform 带来的各种物理问题。
函数原型如下:
public void MovePosition(Vector3 position);
参数说明:
- position:目标世界坐标位置。
例如:
using UnityEngine;
public class MovePositionDemo : MonoBehaviour
{
public Rigidbody rb;
private void FixedUpdate()
{
rb.MovePosition(new Vector3(5, 0, 0));
}
}
运行后,物体会朝目标位置移动,而不是像修改 transform.position 一样直接跳过去。
需要注意的是,MovePosition() 主要用于运动学刚体(Kinematic Rigidbody),对于普通动态刚体虽然也可以调用,但一般不能达到预期效果,因此在实际开发中更多用于 isKinematic = true 的物体。
4.3.1 MovePosition 的工作原理
许多初学者认为 MovePosition() 就是:
transform.position = targetPosition;
实际上,两者完全不同。
当调用:
rb.MovePosition(targetPosition);
Unity 并不会立即修改 Transform,而是通知物理系统:
在下一次物理更新时,将刚体移动到指定位置。
因此,它依旧属于物理模拟的一部分。
整个流程如下:
调用 MovePosition()
│
▼
记录目标位置
│
▼
等待下一次 FixedUpdate
│
▼
Physics 更新刚体位置
│
▼
同步 Transform
正由于经过了物理系统,所以:
- 碰撞检测正常。
- 插值(Interpolation)正常。
- Rigidbody 状态保持一致。
- 不容易产生穿模。
这也是 Unity 官方推荐使用 MovePosition() 控制刚体移动的重大缘由。
4.3.2 MovePosition 与 position 的区别
许多开发者容易混淆:
rb.position = target;
和:
rb.MovePosition(target);
虽然都能改变物体位置,但工作方式完全不同。
直接修改位置:
rb.position = target;
特点:
- 立即修改位置。
- 不经过移动过程。
- 更像瞬移。
- 常用于初始化。
使用 MovePosition():
rb.MovePosition(target);
特点:
- 参与物理模拟。
- 保持碰撞连续性。
- 插值更加平滑。
- 更适合持续移动。
例如,一个移动平台:
错误写法:
rb.position += Vector3.right * Time.fixedDeltaTime;
推荐写法:
rb.MovePosition(rb.position + Vector3.right * Time.fixedDeltaTime);
这样平台上的角色能够更稳定地跟随平台运动。
4.3.3 MovePosition 的正确使用方式
MovePosition() 一般应放在 FixedUpdate() 中调用。
例如,实现一个持续向前移动的平台:
using UnityEngine;
public class MovingPlatform : MonoBehaviour
{
public Rigidbody rb;
public float speed = 2f;
private void FixedUpdate()
{
Vector3 nextPos = rb.position + Vector3.forward * speed * Time.fixedDeltaTime;
rb.MovePosition(nextPos);
}
}
这里需要特别注意:
Time.fixedDeltaTime
而不是:
Time.deltaTime
由于:
- FixedUpdate() 使用固定时间步长。
- Update() 使用每帧时间。
在物理更新中,应始终使用:
Time.fixedDeltaTime
这样才能保证移动速度稳定。
4.3.4 MovePosition 实现角色移动
许多第三人称角色控制器都会使用 MovePosition()。
例如:
using UnityEngine;
public class PlayerMove : MonoBehaviour
{
public Rigidbody rb;
public float speed = 5f;
private void FixedUpdate()
{
float h = Input.GetAxis("Horizontal");
float v = Input.GetAxis("Vertical");
Vector3 dir = new Vector3(h, 0, v).normalized;
Vector3 nextPos =
rb.position +
dir * speed * Time.fixedDeltaTime;
rb.MovePosition(nextPos);
}
}
这样具有几个优点:
- 碰撞检测正常。
- 可以推动其他刚体。
- 不容易穿模。
- 插值更加平滑。
相比直接修改 Transform,更适合基于 Rigidbody 的角色控制。
4.3.5 MovePosition 与 transform.position 的区别
这是 Unity 初学者最容易犯错的地方。
例如:
transform.position += Vector3.forward;
虽然物体移动了,但是:
- 完全绕过物理系统。
- Rigidbody 不知道发生了什么。
- 容易导致碰撞异常。
- 高速移动可能直接穿过墙体。
而:
rb.MovePosition(nextPos);
则会:
- 通知物理系统。
- 正常进行碰撞检测。
- 支持连续碰撞检测(Continuous Collision Detection)。
- 与 Rigidbody 保持同步。
因此,如果物体带有 Rigidbody,应尽量避免直接修改:
transform.position
而优先使用:
MovePosition()
4.3.6 MovePosition 与 velocity 的区别
虽然两者都能够让物体移动,但控制方式完全不同。
使用速度:
rb.velocity = Vector3.forward * 5;
表明:
给物体一个速度。
之后:
- 重力继续作用。
- 阻力继续作用。
- 碰撞可能改变速度。
- 物体具有惯性。
而:
rb.MovePosition(nextPos);
表明:
本次物理更新移动到指定位置。
不会依赖当前速度。
例如:
角色一直向前移动。
使用:
velocity
角色会有惯性。
使用:
MovePosition
角色按照代码指定的位置移动,更容易准确控制。
因此:
一般来说,需要准确控制位置时使用 MovePosition();需要模拟真实物理运动时使用 velocity 或 AddForce()。
4.3.7 MovePosition 与插值(Interpolation)
如果 Rigidbody 开启了:
Interpolation
例如:
Interpolation = Interpolate
那么:
rb.MovePosition(...)
产生的移动效果会更加平滑。
缘由是:
物理系统依旧按照固定时间更新。
而渲染帧率可能远高于物理帧率。
插值会自动计算:
上一帧位置
↓
当前帧位置
↓
中间位置
从而让画面看起来更加流畅。
如果直接修改:
transform.position
则无法正确利用 Rigidbody 的插值机制。
因此,对于需要平滑移动的平台、角色以及 NPC,提议开启 Rigidbody 的 Interpolation,并配合 MovePosition() 使用。
4.3.8 使用 MovePosition 的注意事项
MovePosition() 虽然超级实用,但仍有几个需要注意的地方。
第一,它应当放在 FixedUpdate() 中调用,而不是 Update()。由于它属于物理系统的一部分,只有在固定时间步长中调用,才能保证物理模拟稳定。
其次,MovePosition() 更适合运动学刚体(isKinematic = true)。如果用于动态刚体,物体依旧会受到重力、碰撞和其他物理因素影响,最终运动结果可能与预期不一致。
另外,不要同时使用:
transform.position
和:
MovePosition()
共同控制同一个 Rigidbody,否则容易导致位置抖动、碰撞异常以及物理模拟不一致。
最后,虽然 MovePosition() 能够正常参与物理系统,但它并不是万能的。如果物体每次移动距离过大,例如一次跨越数十米,即使开启连续碰撞检测,也依旧有可能发生穿透。因此,在高速运动场景下,应合理控制每次移动距离,或者根据实际需求使用速度、力或射线检测等其他方案。
合理使用 MovePosition(),能够在保持物理模拟稳定的同时,实现准确、平滑且可靠的刚体移动,是开发角色控制器、移动平台、机关、电梯等系统时最常使用的 Rigidbody API 之一。
4.4 MoveRotation
在上一节中,我们学习了 MovePosition() 用于平滑移动刚体的位置,而与之对应的,Rigidbody 还提供了用于控制旋转的 MoveRotation()。
MoveRotation() 可以在保持物理系统正常工作的情况下旋转刚体,是控制运动学刚体旋转时最推荐使用的方法。
例如:
- 自动旋转的平台。
- 缓慢打开的房门。
- 持续旋转的机关。
- 自动转向的炮台。
- 朝向目标旋转的 NPC。
这些都可以通过 MoveRotation() 来实现。
函数原型如下:
public void MoveRotation(Quaternion rot);
参数说明:
- rot:目标旋转(世界旋转),类型为 Quaternion。
例如:
using UnityEngine;
public class MoveRotationDemo : MonoBehaviour
{
public Rigidbody rb;
private void FixedUpdate()
{
rb.MoveRotation(Quaternion.Euler(0, 90, 0));
}
}
运行后,刚体会朝目标角度旋转,而不是直接修改 Transform 的旋转。
需要注意的是,MoveRotation() 与 MovePosition() 类似,主要用于运动学刚体(Kinematic Rigidbody),能够在保持物理系统一致性的前提下完成旋转操作。
4.4.1 MoveRotation 的工作原理
许多开发者第一次使用时,会认为:
rb.MoveRotation(rotation);
等价于:
transform.rotation = rotation;
实际上,它们的工作方式完全不同。
调用:
rb.MoveRotation(rotation);
后,并不会立即修改 Transform,而是通知物理系统:
在下一次物理更新时,将刚体旋转到指定姿态。
整个过程如下:
调用 MoveRotation()
│
▼
记录目标旋转
│
▼
等待下一次 FixedUpdate
│
▼
Physics 更新刚体旋转
│
▼
同步 Transform
因此,它依旧属于物理模拟的一部分,可以正确参与碰撞检测、插值以及 Rigidbody 的状态同步。
4.4.2 MoveRotation 与 rotation 的区别
许多初学者容易混淆:
rb.rotation = rotation;
和:
rb.MoveRotation(rotation);
虽然最终都会改变物体朝向,但它们的用途不同。
直接修改:
rb.rotation = rotation;
特点:
- 立即修改旋转。
- 更像瞬间转向。
- 常用于初始化位置。
- 不会产生平滑旋转过程。
使用:
rb.MoveRotation(rotation);
特点:
- 参与物理模拟。
- 支持插值。
- 旋转更加平滑。
- 更适合持续旋转。
例如,一个自动旋转的平台。
不推荐:
rb.rotation = Quaternion.Euler(0, angle, 0);
推荐:
rb.MoveRotation(Quaternion.Euler(0, angle, 0));
这样平台的旋转会更加自然,同时保持 Rigidbody 与物理系统同步。
4.4.3 使用 MoveRotation 实现持续旋转
实际开发中,很少每次直接旋转到固定角度,而是不断在当前旋转基础上增加一个旋转量。
例如,让物体持续绕 Y 轴旋转。
using UnityEngine;
public class RotatePlatform : MonoBehaviour
{
public Rigidbody rb;
public float speed = 90f;
private void FixedUpdate()
{
Quaternion delta = Quaternion.Euler(0, speed * Time.fixedDeltaTime, 0);
rb.MoveRotation(rb.rotation * delta);
}
}
这里:
rb.rotation
表明当前旋转。
而:
Quaternion.Euler(...)
表明本次新增旋转。
最终:
rb.rotation * delta
得到新的目标旋转。
这样物体便能够以每秒 90° 的速度持续旋转。
4.4.4 使用 MoveRotation 朝向目标
除了持续旋转之外,更常见的是让物体朝向某个目标。
例如,一个炮台始终朝向玩家。
using UnityEngine;
public class LookTarget : MonoBehaviour
{
public Rigidbody rb;
public Transform target;
private void FixedUpdate()
{
Vector3 dir = target.position - rb.position;
Quaternion targetRot = Quaternion.LookRotation(dir);
rb.MoveRotation(targetRot);
}
}
这样炮台会不断调整自身方向,始终朝向目标。
如果希望旋转更加平滑,可以结合:
Quaternion.RotateTowards()
例如:
Quaternion target =
Quaternion.LookRotation(dir);
Quaternion next =
Quaternion.RotateTowards(
rb.rotation,
target,
90f * Time.fixedDeltaTime);
rb.MoveRotation(next);
这样炮台不会瞬间转向,而是逐渐旋转过去。
4.4.5 MoveRotation 与 transform.rotation 的区别
这是初学者最容易犯错的地方。
例如:
transform.rotation = Quaternion.Euler(0, 90, 0);
虽然物体旋转了,但是:
- 绕过了物理系统。
- Rigidbody 无法正确参与物理模拟。
- 插值可能失效。
- 容易造成碰撞异常。
而:
rb.MoveRotation(rotation);
则:
- 通知物理系统。
- 保持 Rigidbody 状态一致。
- 支持连续物理更新。
- 插值效果更加平滑。
因此,只要物体带有 Rigidbody,就尽量不要直接修改:
transform.rotation
而应优先使用:
MoveRotation()
4.4.6 MoveRotation 与 angularVelocity 的区别
许多开发者都会遇到这样的问题:
什么时候使用:
angularVelocity
什么时候使用:
MoveRotation()
二者虽然都能让物体旋转,但控制方式完全不同。
使用:
rb.angularVelocity = Vector3.up * Mathf.PI;
表明:
给物体一个旋转速度。
之后:
- 受到角阻力影响。
- 碰撞可能改变旋转速度。
- 具有旋转惯性。
- 更符合真实物理。
而:
rb.MoveRotation(nextRotation);
表明:
本次物理更新旋转到指定姿态。
不会依赖当前角速度。
例如:
角色转身。
使用:
angularVelocity
可能出现惯性,停止时继续旋转。
使用:
MoveRotation
则能够准确控制角色朝向。
因此:
一般来说,需要准确控制旋转角度时,应使用 MoveRotation();需要模拟真实物理旋转时,则使用 angularVelocity 或 AddTorque()。
4.4.7 MoveRotation 与插值(Interpolation)
如果 Rigidbody 开启了:
Interpolation = Interpolate
那么:
rb.MoveRotation(...)
能够充分利用插值机制。
例如:
物理更新频率为:
50Hz
而游戏渲染频率可能达到:
120FPS
如果没有插值,旋转看起来可能会一顿一顿。
开启插值后,Unity 会自动计算:
上一帧旋转
↓
当前旋转
↓
中间旋转
从而让旋转更加平滑。
如果直接修改:
transform.rotation
则无法利用 Rigidbody 的插值系统。
因此,对于角色、平台、机关等需要平滑旋转的物体,提议开启 Rigidbody 的 Interpolation,并配合 MoveRotation() 使用。
4.4.8 使用 MoveRotation 的注意事项
MoveRotation() 的使用方式与 MovePosition() 十分类似,在实际开发中也有一些需要注意的地方。
第一,它应当放在 FixedUpdate() 中调用,而不是 Update()。由于它属于物理系统的一部分,只有在固定时间步长中执行,才能保证物理模拟稳定且与 Rigidbody 同步。
其次,MoveRotation() 更适合运动学刚体(isKinematic = true)。如果用于动态刚体,旋转仍可能受到碰撞、力矩、角阻力等物理因素影响,因此最终结果未必完全按照代码设定执行。
另外,不要同时使用:
transform.rotation
和:
rb.MoveRotation(...)
共同控制同一个 Rigidbody,否则容易导致旋转抖动、插值失效以及物理模拟异常。
最后,由于 MoveRotation() 的参数类型是 Quaternion,提议尽量使用 Quaternion.Euler()、Quaternion.LookRotation()、Quaternion.RotateTowards() 或 Quaternion.Slerp() 等 API 来生成目标旋转,而不要直接修改四元数的 x、y、z、w 分量。这样既能避免计算错误,也能保证旋转结果符合预期。
熟练掌握 MoveRotation() 后,你就可以轻松实现角色转向、自动门开关、旋转平台、炮台瞄准等各种常见功能,并能够在保持物理系统稳定的同时获得平滑自然的旋转效果。
4.5 Sleep 与 WakeUp
在 Unity 的物理系统中,并不是所有 Rigidbody 都会一直参与物理计算。
例如,一个木箱掉落到地面后,如果长时间保持静止,那么继续对它进行碰撞检测、重力计算以及速度计算已经没有意义,只会增加 CPU 的计算负担。
为了提高物理系统的运行效率,Unity 引入了休眠(Sleep)机制。
当 Rigidbody 长时间保持静止,并且没有受到任何外力影响时,Unity 会自动让它进入休眠状态(Sleeping)。进入休眠后,该刚体将暂时停止参与物理模拟,从而减少物理引擎的计算开销。
当物体再次受到碰撞、施加力或者其他物理影响时,Unity 又会自动将其唤醒(Wake Up),重新参与物理模拟。
除了自动休眠和自动唤醒之外,Unity 还提供了两个 API,让开发者能够手动控制刚体的休眠状态:
public void Sleep();
public void WakeUp();
其中:
- Sleep():立即让刚体进入休眠状态。
- WakeUp():立即唤醒刚体,重新参与物理模拟。
虽然这两个 API 在日常开发中使用频率不如 velocity 或 MovePosition() 高,但在优化大量物理物体、控制机关以及网络同步等场景中十分有用。
4.5.1 什么是休眠(Sleep)
假设场景中有一个木箱。
开始时:
木箱掉落
↓
碰撞地面
↓
不断弹跳
↓
速度越来越小
↓
最终静止
如果木箱已经静止一分钟,它的:
- 位置没有变化。
- 速度接近 0。
- 角速度接近 0。
- 没有受到新的碰撞。
此时 Unity 会自动判断:
该刚体已经没有继续计算的必要。
于是将其设置为:
Sleeping
进入休眠后:
- 不再进行速度计算。
- 不再进行重力积分。
- 不再更新物理状态。
- 节省 CPU 开销。
对于拥有大量静态物体的场景,这一机制能够显著提高物理模拟性能。
4.5.2 手动让刚体休眠
除了自动休眠,还可以主动调用:
rb.Sleep();
例如:
using UnityEngine;
public class SleepDemo : MonoBehaviour
{
public Rigidbody rb;
private void Start()
{
rb.Sleep();
}
}
运行后,刚体立即进入休眠状态。
此时:
- 不再响应重力。
- 不再更新速度。
- 不再进行物理计算。
如果此时查看:
rb.IsSleeping()
会返回:
true
表明刚体已经休眠。
4.5.3 判断刚体是否休眠
Unity 提供了:
public bool IsSleeping();
用于判断当前刚体是否处于休眠状态。
例如:
using UnityEngine;
public class CheckSleep : MonoBehaviour
{
public Rigidbody rb;
private void Update()
{
if (rb.IsSleeping())
{
Debug.Log("刚体正在休眠");
}
}
}
返回结果:
true
表明:
当前刚体已经停止参与物理模拟。
如果:
false
则说明:
刚体依旧处于活动状态。
4.5.4 唤醒刚体
让刚体重新参与物理模拟的方法超级简单:
rb.WakeUp();
例如:
using UnityEngine;
public class WakeUpDemo : MonoBehaviour
{
public Rigidbody rb;
private void Start()
{
rb.Sleep();
}
private void Update()
{
if (Input.GetKeyDown(KeyCode.Space))
{
rb.WakeUp();
}
}
}
运行后:
开始:
Sleeping
按下空格:
Wake Up
刚体重新开始参与物理模拟。
此时:
- 重力恢复。
- 碰撞恢复。
- 力恢复。
- 速度重新更新。
4.5.5 哪些操作会自动唤醒刚体
实际上,大多数情况下,并不需要主动调用:
WakeUp()
由于 Unity 会自动完成这项工作。
例如:
施加力:
rb.AddForce(Vector3.up * 10);
自动唤醒。
施加力矩:
rb.AddTorque(Vector3.up * 10);
自动唤醒。
发生碰撞:
碰撞
自动唤醒。
修改速度:
rb.velocity = Vector3.up * 5;
自动唤醒。
修改角速度:
rb.angularVelocity = Vector3.right;
自动唤醒。
因此,大多数游戏几乎不会主动调用:
WakeUp()
只有某些特殊逻辑才需要手动控制。
4.5.6 Sleep 与 isKinematic 的区别
许多初学者容易把:
Sleep
和:
isKinematic
混为一谈。
实际上,它们完全不是同一个概念。
休眠:
rb.Sleep();
特点:
- 只是暂时停止物理计算。
- 受到碰撞后会自动恢复。
- Unity 自动管理。
- 动态刚体依旧是动态刚体。
运动学:
rb.isKinematic = true;
特点:
- 完全退出动力学模拟。
- 不受重力影响。
- 不响应 AddForce。
- 需要代码控制移动。
因此:
两者适用于完全不同的场景。
4.5.7 Sleep 的实际应用场景
虽然许多项目中几乎不会主动调用 Sleep(),但它在一些特殊场景下能够有效提升性能。
例如,大量箱子生成后:
1000 个木箱
如果它们已经摆放完成,不会再发生运动,可以主动:
rb.Sleep();
这样可以减少物理系统的计算压力。
又例如:
大型地图中的装饰物。
生成完成后:
花盆
桶
石头
桌子
如果它们不会移动,可以直接休眠。
当玩家推动其中某个物体时,再:
WakeUp();
恢复物理模拟。
网络游戏中也常常利用休眠机制优化同步。
例如:
服务器发现某个物体:
- 已静止。
- 长时间未移动。
即可停止同步位置。
直到:
WakeUp
再次发生。
这样可以减少网络带宽占用。
4.5.8 使用 Sleep 与 WakeUp 的注意事项
虽然 Unity 会自动管理刚体的休眠状态,但在手动调用 Sleep() 和 WakeUp() 时,依旧需要注意一些问题。
第一,不提议频繁调用 Sleep()。如果一个刚体刚刚进入休眠,又立刻被代码唤醒,随后再次休眠,如此反复,会增加额外的状态切换开销,反而影响性能。
其次,不要把 Sleep() 当作“冻结物体”的方法。休眠只是暂时停止物理计算,当物体受到碰撞或其他物理影响时,Unity 依旧可能自动将其唤醒。如果希望物体完全停止响应物理,可以思考冻结约束(Constraints)或将 isKinematic 设置为 true。
另外,对于已经休眠的刚体,如果只是修改其 Transform,可能导致物理状态与显示状态不同步。一般应优先使用 Rigidbody 提供的 API 来操作物体,并在需要时调用 WakeUp(),确保物理系统能够正确更新。
最后,在绝大多数情况下,开发者并不需要主动管理刚体的休眠。Unity 的自动休眠机制已经能够满足绝大多数游戏场景的需求。只有在需要优化大量刚体性能、实现特殊逻辑控制或进行网络同步优化时,才提议手动使用 Sleep() 与 WakeUp()。
理解休眠机制不仅有助于编写更加高效的物理代码,也能够协助开发者在大型场景和复杂项目中更好地优化物理性能,为后续学习 Rigidbody 的高级属性和物理优化技巧打下基础。
4.6 position 与 rotation
除了 MovePosition() 和 MoveRotation() 之外,Rigidbody 还提供了 position 和 rotation 两个属性,用于直接获取或修改刚体的位置与旋转。
许多初学者会认为:
transform.position
和:
rb.position
完全一样。
实际上,两者虽然表明的位置一样,但工作对象并不一样。
transform.position 属于 Transform 组件,而 Rigidbody.position 属于物理系统。当物体带有 Rigidbody 时,Unity 官方更推荐优先使用 Rigidbody 提供的接口,而不是直接操作 Transform。
对应属性如下:
public Vector3 position;
public Quaternion rotation;
其中:
- position 表明刚体当前的世界坐标位置。
- rotation 表明刚体当前的世界旋转。
这两个属性既可以读取,也可以直接赋值。
4.6.1 读取 position
读取当前位置超级简单。
例如:
using UnityEngine;
public class ReadPosition : MonoBehaviour
{
public Rigidbody rb;
private void Update()
{
Debug.Log(rb.position);
}
}
运行后可能输出:
(2.35, 1.00, -4.62)
表明当前刚体位于:
- X = 2.35
- Y = 1.00
- Z = -4.62
读取的位置与:
transform.position
得到的结果一般是一致的。
例如:
Debug.Log(rb.position);
Debug.Log(transform.position);
输出基本一样。
但是,对于带 Rigidbody 的物体,提议统一使用:
rb.position
保持代码风格一致,也能够避免绕过物理系统。
4.6.2 修改 position
除了读取位置,也可以直接修改:
rb.position = new Vector3(5, 1, 0);
完整示例:
using UnityEngine;
public class SetPosition : MonoBehaviour
{
public Rigidbody rb;
private void Start()
{
rb.position = new Vector3(5, 1, 0);
}
}
运行后:
物体会立即出目前:
(5, 1, 0)
这里需要注意的是:
position 属于直接修改位置。
因此:
- 不会产生移动过程。
- 不会经过插值。
- 更像瞬移。
因此,它更适合:
- 初始化角色位置。
- 场景加载。
- 传送门。
- 存档读取。
而不是用于持续移动。
4.6.3 读取 rotation
读取旋转的方法与读取位置类似。
例如:
using UnityEngine;
public class ReadRotation : MonoBehaviour
{
public Rigidbody rb;
private void Update()
{
Debug.Log(rb.rotation);
}
}
输出类似:
(0.0, 0.7, 0.0, 0.7)
这里输出的是:
Quaternion(四元数)
而不是:
欧拉角
如果希望查看更直观的角度,可以使用:
Debug.Log(rb.rotation.eulerAngles);
例如输出:
(0.0, 90.0, 0.0)
这样更方便阅读。
4.6.4 修改 rotation
修改旋转的方法同样超级简单。
例如:
rb.rotation = Quaternion.Euler(0, 90, 0);
完整示例:
using UnityEngine;
public class SetRotation : MonoBehaviour
{
public Rigidbody rb;
private void Start()
{
rb.rotation = Quaternion.Euler(0, 90, 0);
}
}
运行后:
物体立即旋转到:
Y = 90°
同样需要注意:
这属于:
立即修改
不会产生旋转过程。
因此,更适合:
- 初始化方向。
- 角色出生朝向。
- 场景切换。
- 瞬间转向。
而不是持续旋转。
4.6.5 position 与 MovePosition 的区别
许多开发者都会疑惑:
到底什么时候使用:
rb.position
什么时候使用:
rb.MovePosition()
实际上,两者最大的区别就是:
是否参与移动过程。
例如:
rb.position = target;
特点:
- 立即到达目标位置。
- 没有移动动画。
- 更像瞬移。
- 不经过物理移动过程。
而:
rb.MovePosition(target);
特点:
- 参与物理更新。
- 插值更加平滑。
- 更适合持续运动。
- 不容易产生碰撞异常。
因此:
一般来说,只要需要看到移动过程,就应该优先使用 MovePosition()。
4.6.6 rotation 与 MoveRotation 的区别
旋转也是同样的道理。
直接修改:
rb.rotation = targetRotation;
表明:
瞬间旋转到目标角度。
而:
rb.MoveRotation(targetRotation);
表明:
在物理更新中旋转到目标姿态。
例如:
角色出生:
rb.rotation = Quaternion.Euler(0, 180, 0);
超级合适。
但是:
自动门缓慢打开:
rb.MoveRotation(nextRotation);
更加自然。
因此:
4.6.7 position 与 transform.position 的区别
许多开发者第一次学习 Rigidbody 时都会产生一个疑问:
既然:
transform.position
也可以修改位置。
为什么还要使用:
rb.position
主要缘由在于:
Transform 属于渲染系统。
Rigidbody 属于物理系统。
如果物体没有 Rigidbody:
使用:
transform.position
完全没有问题。
但是:
如果物体已经带有 Rigidbody。
提议优先使用:
rb.position
缘由包括:
- 能够保持 Rigidbody 状态一致。
- 不容易造成物理系统不同步。
- 更符合 Unity 官方推荐。
同理:
旋转也是一样。
优先使用:
rb.rotation
而不是:
transform.rotation
4.6.8 使用 position 与 rotation 的注意事项
虽然 position 和 rotation 使用起来十分方便,但它们属于直接修改刚体状态的方式,因此需要合理使用。
第一,它们更适合用于初始化或瞬间改变物体的位置和旋转。例如角色出生、场景切换、读取存档、传送门等场景,这些情况下一般不需要表现出移动或旋转过程,直接赋值即可。
其次,不提议在每一帧持续修改 position 或 rotation 来实现移动和旋转。例如:
rb.position += Vector3.forward * Time.deltaTime;
或者:
rb.rotation = Quaternion.Euler(0, angle, 0);
虽然能够达到效果,但这种方式不会利用 MovePosition() 和 MoveRotation() 提供的平滑插值和物理更新机制,也容易影响碰撞检测和物理表现。对于持续运动,应优先使用 MovePosition()、MoveRotation()、velocity 或 angularVelocity 等更适合的 API。
另外,不要同时混合使用 transform.position 与 rb.position,或者 transform.rotation 与 rb.rotation 来控制同一个刚体。两套系统交替修改同一对象,容易导致位置抖动、碰撞异常以及物理状态不同步等问题。
最后,需要牢记一个原则:
- 瞬间改变位置或旋转,使用 position 与 rotation。
- 持续平滑移动或旋转,使用 MovePosition() 与 MoveRotation()。
- 模拟真实物理运动,使用 velocity、angularVelocity、AddForce() 或 AddTorque()。
理解这几个 API 之间的区别,并根据实际需求选择合适的方式,能够协助我们编写更加稳定、规范且符合 Unity 物理系统设计理念的代码。
4.7 centerOfMass
在现实世界中,每一个物体都有自己的重心(Center of Mass)。
例如:
- 一个正方体的重心一般位于几何中心。
- 锤子的重心更靠近锤头。
- 汽车的重心一般位于车身中部偏下的位置。
- 保龄球由于质量分布均匀,重心基本位于中心。
物体是否容易倾倒、翻滚、旋转,都与重心的位置密切相关。
Unity 的 Rigidbody 同样具有重心的概念,并提供了 centerOfMass 属性,允许开发者修改刚体的重心位置。
属性定义如下:
public Vector3 centerOfMass;
需要注意的是,centerOfMass 使用的是刚体局部坐标(Local Space),而不是世界坐标。
修改重心后,会直接影响刚体受到力和力矩时的运动表现,因此它在车辆系统、飞行器、机械装置以及各种物理模拟中都有着十分重大的作用。
4.7.1 什么是重心
可以把重心理解为:
物体所有质量聚焦作用的那个点。
当重力作用于物体时,实际上可以认为重力全部作用在重心位置。
例如,一个完全均匀的立方体。
┌─────────┐
│ │
│ ● │ ← 重心
│ │
└─────────┘
由于质量分布均匀,因此重心位于中心。
而对于锤子来说:
██████========
↑
重心
由于锤头质量更大,因此重心更靠近锤头。
Unity 默认会根据 Collider 的形状自动计算一个合适的重心,因此大多数情况下,我们并不需要手动设置。
4.7.2 查看当前重心
可以直接读取:
using UnityEngine;
public class ReadCenterOfMass : MonoBehaviour
{
public Rigidbody rb;
private void Start()
{
Debug.Log(rb.centerOfMass);
}
}
例如输出:
(0.0, 0.0, 0.0)
表明:
当前重心位于刚体局部坐标中心。
如果模型结构比较特殊,则可能输出:
(0.0, -0.25, 0.1)
说明 Unity 自动计算出的重心并不在几何中心。
需要注意的是,这里的坐标是局部坐标。
例如:
物体位置
(100,0,50)
即使物体位于世界坐标很远的位置:
rb.centerOfMass
依旧可能输出:
(0,0,0)
由于它始终相对于物体自身。
4.7.3 修改重心
可以直接修改:
rb.centerOfMass = new Vector3(0, -0.5f, 0);
完整示例:
using UnityEngine;
public class SetCenterOfMass : MonoBehaviour
{
public Rigidbody rb;
private void Start()
{
rb.centerOfMass = new Vector3(0, -0.5f, 0);
}
}
这样:
重心便向下移动了:
原来
●
修改后
●
虽然模型外观完全没有变化,但物理行为已经发生改变。
4.7.4 修改重心有什么作用
许多初学者第一次修改:
centerOfMass
都会发现:
模型的位置没有变化。
模型也没有旋转。
这是由于:
重心不会改变模型外观。
它改变的是:
物理计算方式
例如:
给物体施加一个侧向力:
rb.AddForce(Vector3.right * 50);
如果:
重心较高。
物体更容易:
翻倒
如果:
重心较低。
物体更加:
稳定
因此:
修改重心主要影响:
- 翻滚。
- 倾倒。
- 力矩。
- 转向稳定性。
- 碰撞后的旋转。
4.7.5 汽车为什么要降低重心
这是 centerOfMass 最经典的应用场景。
例如:
赛车高速转弯时。
如果:
重心很高
那么:
容易侧翻
而:
重心较低
则:
更加稳定
因此,许多赛车游戏都会在车辆初始化时:
rb.centerOfMass = new Vector3(0, -0.5f, 0);
甚至:
rb.centerOfMass = new Vector3(0, -1f, 0);
这样:
车辆:
- 不容易翻车。
- 高速过弯更稳定。
- 驾驶体验更好。
Unity 官方车辆示例中,也常常通过降低重心来提升车辆稳定性。
4.7.6 centerOfMass 与 AddForce 的关系
许多开发者发现:
同样执行:
rb.AddForce(Vector3.forward * 100);
不同重心:
运动效果完全不同。
缘由就在于:
所有力最终都会围绕:
Center Of Mass
进行计算。
例如:
重心在中心:
●
↑
Force
物体主要向前运动。
而:
重心偏下:
↑ Force
●
物体更稳定。
重心偏上:
●
↑ Force
更容易产生翻滚。
因此:
即使施加完全一样的力。
不同重心:
运动效果也完全不同。
4.7.7 重置重心
如果之前修改过:
centerOfMass
希望恢复 Unity 自动计算。
可以调用:
rb.ResetCenterOfMass();
例如:
rb.centerOfMass = new Vector3(0, -1, 0);
之后:
rb.ResetCenterOfMass();
Unity 会重新根据 Collider 自动计算新的重心。
如果修改 Collider 的大小、形状或者新增、删除 Collider 后,也可以调用该方法重新计算重心。
4.7.8 使用 centerOfMass 的注意事项
centerOfMass 是一个能够显著影响刚体物理行为的重大属性,因此在修改时需要谨慎。
第一,它使用的是局部坐标,而不是世界坐标。许多初学者会误以为 (0, -1, 0) 表明世界空间向下移动 1 米,实际上它表明的是相对于刚体自身原点向下偏移 1 个单位。
其次,修改重心不会改变模型的位置、旋转或外观,它只会影响物理计算结果。如果修改后没有立即看到视觉变化,不代表设置没有生效,而是在受到力、碰撞或力矩作用时才会体现出差异。
另外,不提议为了“让物体看起来更稳定”而随意修改重心。过度偏移重心可能导致物理行为与模型外观严重不符,例如车辆几乎无法翻车、箱子始终保持平衡等,降低游戏的真实性。
最后,如果修改了 Collider 的形状或结构,并希望重新使用 Unity 自动计算的重心,应及时调用:
rb.ResetCenterOfMass();
否则,之前手动设置的重心仍会继续生效。
合理使用 centerOfMass,能够有效改善车辆、飞机、船只以及各种机械装置的物理表现,是制作高质量物理系统时常常需要调整的重大参数之一。
4.8 inertiaTensor
在前一节中,我们学习了 centerOfMass(重心),它决定了物体受力时的平衡性和稳定性。
而本节要介绍的 inertiaTensor(惯性张量),则决定了物体旋转时的难易程度。
现实生活中,我们会发现这样一种现象:
- 一个足球很容易旋转。
- 一个巨大的钢球则很难旋转。
- 一根长木棍绕中间旋转比较容易,而绕一端旋转则更加困难。
- 花样滑冰运动员张开双臂时旋转较慢,收拢双臂后旋转速度会明显变快。
这些现象都与物体的**转动惯量(Moment of Inertia)**有关。
Unity 为 Rigidbody 提供了 inertiaTensor 属性,用于描述刚体绕各个坐标轴旋转时的转动惯量。
属性定义如下:
public Vector3 inertiaTensor;
其中:
- X 表明绕 X 轴旋转的惯量。
- Y 表明绕 Y 轴旋转的惯量。
- Z 表明绕 Z 轴旋转的惯量。
惯性张量越大,物体越难绕对应轴旋转;惯性张量越小,则越容易旋转。
默认情况下,Unity 会根据 Collider 的形状和 Rigidbody 的质量自动计算惯性张量,因此大多数情况下不需要手动修改。
4.8.1 什么是转动惯量
许多初学者第一次看到 inertiaTensor,都会疑惑:
为什么旋转也需要“惯性”?
实际上,我们已经很熟悉线性运动中的惯性。
例如:
推动一辆购物车。
空车
很容易推动。
而:
装满货物
则需要更大的力。
旋转也是一样。
例如:
一块木板。
────────
绕中心旋转比较容易。
如果绕一端旋转:
●────────
就明显更困难。
这种旋转时产生的惯性,就是转动惯量。
Unity 中的 inertiaTensor 正是用于描述这种特性。
4.8.2 查看当前惯性张量
可以直接读取:
using UnityEngine;
public class ReadInertia : MonoBehaviour
{
public Rigidbody rb;
private void Start()
{
Debug.Log(rb.inertiaTensor);
}
}
例如输出:
(0.166, 0.166, 0.166)
不同模型:
输出可能完全不同。
例如:
长方体:
(0.08, 0.35, 0.40)
圆柱:
(0.15, 0.08, 0.15)
这是由于:
不同形状的物体。
转动惯量本来就不同。
Unity 会自动计算这些数值。
4.8.3 修改 inertiaTensor
虽然 Unity 已经能够自动计算惯性张量,但开发者也可以手动修改。
例如:
rb.inertiaTensor = new Vector3(1, 1, 1);
或者:
rb.inertiaTensor = new Vector3(10, 1, 1);
表明:
绕 X 轴旋转变得更加困难。
而:
rb.inertiaTensor = new Vector3(0.2f, 1, 1);
表明:
绕 X 轴更容易旋转。
需要注意的是,修改惯性张量不会改变模型外观,也不会影响位置,它只会影响物理引擎计算旋转时的结果。
4.8.4 inertiaTensor 对旋转有什么影响
许多开发者第一次修改:
inertiaTensor
都会发现:
即使模型完全没有变化,旋转效果却发生了明显改变。
例如:
执行:
rb.AddTorque(Vector3.up * 20);
如果:
Y 轴惯量很小
物体会很快开始旋转。
而:
Y 轴惯量很大
则需要更大的力矩才能达到一样的旋转速度。
因此:
在一样力矩下:
惯量越小
↓↓↓
旋转越快
惯量越大
↓↓↓
旋转越慢
这与现实世界中的物理规律一致。
4.8.5 inertiaTensor 与 AddTorque 的关系
inertiaTensor 最直接影响的就是:
AddTorque()
例如:
同样执行:
rb.AddTorque(Vector3.up * 100);
如果:
惯量 = 0.2
可能:
迅速旋转
如果:
惯量 = 20
可能:
几乎没有明显旋转
因此:
施加力矩时:
最终旋转效果不仅取决于:
- Torque 大小。
- Rigidbody Mass。
还取决于:
Inertia Tensor
4.8.6 inertiaTensorRotation
除了:
inertiaTensor
Unity 还提供:
public Quaternion inertiaTensorRotation;
它表明:
惯性张量所对应的旋转方向。
对于规则模型:
一般:
Quaternion.identity
即可。
而对于:
- 不规则模型。
- 倾斜模型。
- 特殊质量分布。
Unity 会自动计算:
inertiaTensorRotation
一般情况下,开发者几乎不会主动修改该属性,由于错误的设置很容易导致旋转行为异常。
4.8.7 重置惯性张量
如果之前手动修改过:
rb.inertiaTensor
希望恢复 Unity 自动计算。
可以调用:
rb.ResetInertiaTensor();
例如:
rb.inertiaTensor = Vector3.one * 5;
之后:
rb.ResetInertiaTensor();
Unity 会重新根据:
- Collider。
- Rigidbody Mass。
- Collider 分布。
重新计算惯性张量。
如果修改了 Collider 的大小、形状或者新增、删除 Collider,也提议调用该方法重新计算惯性张量。
4.8.8 inertiaTensor 与 centerOfMass 的区别
许多初学者容易把:
centerOfMass
和:
inertiaTensor
混淆。
实际上,它们分别控制的是完全不同的内容。
centerOfMass 决定的是:
力作用的位置。
例如:
降低重心后。
车辆:
- 更稳定。
- 不容易侧翻。
而:
inertiaTensor 决定的是:
绕各个轴旋转的难易程度。
例如:
同样受到一个力矩。
惯性张量越大:
旋转越困难
惯性张量越小:
旋转越容易
简单来说:
三者共同决定了一个刚体在受到力和力矩作用时的物理表现。
4.8.9 使用 inertiaTensor 的注意事项
inertiaTensor 是 Rigidbody 中一个比较底层的物理属性,大多数情况下无需手动修改。Unity 会根据刚体的质量、Collider 的形状以及质量分布自动计算出较为合理的惯性张量,这也是绝大多数项目采用的方式。
只有在一些特殊场景下,例如车辆模拟、飞行器、机械臂、机器人、物理实验或需要刻意调整旋转手感的游戏中,才会主动修改惯性张量,以获得更符合设计需求的旋转效果。
需要特别注意的是,不提议随意将 inertiaTensor 设置为过小或过大的数值。惯性张量过小可能导致物体受到很小的力矩就高速旋转,表现得极不稳定;而惯性张量过大,则可能使物体几乎无法转动,即使施加较大的力矩也没有明显效果。
另外,当修改 Collider 的尺寸、形状或结构后,如果希望重新使用 Unity 自动计算的惯性张量,应及时调用:
rb.ResetInertiaTensor();
否则,之前手动设置的惯性张量仍会继续生效。
虽然 inertiaTensor 在日常开发中并不是最常使用的 Rigidbody API,但理解它对于深入掌握 Unity 物理系统十分重大。它与 centerOfMass 一起决定了刚体的受力与旋转特性,是制作高质量车辆系统、飞行器系统以及各种复杂物理模拟的重大基础。
本章小结
本章围绕 Rigidbody 提供的常用 API 展开介绍,学习了如何通过代码控制刚体的速度、旋转、位置以及各种物理属性。相比前面学习的力和力矩,这些 API 更偏向于对刚体状态进行直接控制,在角色控制、机关系统、载具系统以及各种物理交互中都有着广泛的应用。
第一学习了 velocity 与 angularVelocity。它们分别用于控制刚体的线速度和角速度,可以让物体立即获得指定的运动或旋转速度。需要注意的是,velocity 的单位是米/秒,而 angularVelocity 的单位是弧度/秒。它们适合需要快速、准确控制运动速度的场景,而 AddForce() 和 AddTorque() 更适合模拟真实的受力过程。
随后介绍了 MovePosition() 与 MoveRotation()。这两个 API 会参与物理系统更新,并能够与 Rigidbody 的碰撞检测和插值机制保持同步,因此更适合持续移动或旋转运动学刚体。例如角色移动、自动门、移动平台以及炮台转向等,都推荐使用这两个 API,而不是直接修改 Transform。
接着学习了 Sleep()、WakeUp() 以及 IsSleeping()。Unity 会自动管理刚体的休眠状态,以减少不必要的物理计算,提高整体运行效率。在绝大多数情况下,开发者无需主动控制休眠,但在需要优化大量刚体、特殊逻辑控制或网络同步时,这些 API 依旧具有必定的使用价值。
之后介绍了 position 与 rotation。它们能够直接读取或修改刚体的位置和旋转,更适合用于初始化、传送、读取存档等需要瞬间改变状态的场景,而不适合实现持续移动和旋转。在实际开发中,应根据需求合理选择 position、rotation 与 MovePosition()、MoveRotation()。
最后学习了 centerOfMass 与 inertiaTensor。centerOfMass 用于调整刚体的重心位置,会影响物体的稳定性、翻滚和受力效果;inertiaTensor 用于调整刚体绕各坐标轴旋转时的转动惯量,会影响物体在受到力矩作用时的旋转难易程度。这两个属性一般由 Unity 自动计算,但在车辆、飞机、机器人等复杂物理系统中,常常需要根据实际需求进行适当调整。
通过本章的学习,我们已经掌握了 Rigidbody 中最常用的一组 API,并能够根据不同需求选择合适的控制方式。例如,需要立即改变速度时可以使用 velocity;需要模拟真实受力过程时可以使用 AddForce();需要平滑移动刚体时可以使用 MovePosition();需要调整物体稳定性时可以修改 centerOfMass。理解这些 API 之间的区别与适用场景,是编写稳定、高效物理代码的关键,也是学习后续物理关节(Joint)、角色控制器、车辆系统以及更复杂物理交互的重大基础。





