第4章 Rigidbody API

第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)、角色控制器、车辆系统以及更复杂物理交互的重大基础。

© 版权声明

相关文章

1 条评论

none
暂无评论...