Notifications
Article
刚体
Updated 4 months ago
279
0
在整个 Unity 物理系统中,最重要概念就是刚体(Rigidbody)。刚体是物理学中的概念,它是指在运动中和受力后,形状和大小不变,并且内部各点相对位置不变的物体。刚体是一种为了方便物理计算而提出的理想化模型,在不需要考虑物体本身的形变时,可以把物体当做刚体进行处理。

相关文章

  • 刚体
  • 碰撞器

概述

在整个 Unity 物理系统中,最重要概念就是刚体(Rigidbody
刚体是物理学中的概念,它是指在运动中和受力后,形状和大小不变,并且内部各点相对位置不变的物体。刚体是一种为了方便物理计算而提出的理想化模型,在不需要考虑物体本身的形变时,可以把物体当做刚体进行处理。
在 Unity 中,提供了 Rigidbody 组件,用来模拟刚体,我们叫它刚体组件。给游戏对象添加刚体组件,就能够使游戏对象受到碰撞弹开、能够受到重力下落、能够给其它刚体施加力等。所以刚体组件的主要作用就是:
使游戏对象能够受力和施力

刚体组件属性

Mass 质量:用来模拟物体的质量,单位为千克。质量会影响惯性大小,而且质量不同的两个刚体相互碰撞时,它们各自的反应也不同。要注意物体的质量大小不会影响自由落体时的速度,想想伽利略的两个铁球同时落地。
Drag 空气阻力:物体受力移动时受到的空气阻力大小,0 表示没有空气阻力。如果想让两个物体自由落体时速度不同,就给它们设置不同的空气阻力。空气阻力越大,物体感觉起来就越轻,比如铁块的空气阻力可以设置为 0.001,羽毛的空气阻力可以设置为 10。这个值一般不需要设置,如有特殊需要的话,就参考铁块和羽毛的阻力范围进行设置。
Angular Drag 角阻力:物体受力旋转时受到的空气阻力大小。因为物体旋转时的速度叫做角速度,所以旋转时受到的阻力就叫做角阻力。这个属性和 Drag 空气阻力属性类似, Drag 空气阻力属性会影响物体在空气中移动时的速度,Angular Drag 角阻力会影响物体在空气中旋转时的速度。
Use Gravity 启用重力:物体是否受重力影响。受重力影响的物体会进行自由落体。
Is Kinematic 运动学:如果我们只是需要让物理系统进行碰撞检测,不需要使用物理系统控制游戏对象,而是在脚本中使用 Transform 控制物体,此时我们可以勾选刚体组件中的 Is Kinematic 属性。此属性的详细讲解请参看《运动学(施工中)》。
Interpolate 插值:物体在进行物理运动时的插值方式,默认是 None 不使用插值
- None 不使用插值:这个选项是默认值,一般情况下此选项不需要设置。如果物体在物理系统控制下,会发生轻微的抖动,可以尝试使用另外两种选项;
- Interpolate 插值:这个选项会根据上一帧中物体的 Transform 来计算当前帧的插值;
- Extrapolate 向后推断:这个选项会推断下一帧中物体的 Transform 来计算当前帧的插值。
Collision Detection 碰撞检测:碰撞检测模式,默认是 Discrete 离散检测。此属性的详细讲解请参看《碰撞器》。
- Discrete 离散检测:对场景中的其它碰撞体使用离散碰撞检测。一般情况下我们不需要修改此属性。物理系统默认的碰撞检测是离散的,所以有时高速移动的刚体会穿透障碍物,此时我们才需要使用另外两种碰撞检测模式。
- Continuous 连续检测:刚体对其它没有刚体的碰撞器采用连续检测,防止刚体速度过快穿透障碍物。比如快速飞行的子弹碰撞静止的墙壁,此时如果采用 Dscrete 离散检测,子弹不会打在墙壁上反弹,而是会穿透墙壁。但该模式下并不能正确检测两颗高速飞行的子弹相互碰撞,因为它们两个都带有刚体,此时请使用第三种模式。要注意连续检测会影响物理性能,非必要情况请使用默认的 Discrete 离散碰撞检测。
- Continuous Dynamic 连续动态检测:会对其它刚体(碰撞检测模式不能是 Discrete 离散碰撞检测)进行连续检测,防止互相穿透。对没有刚体的碰撞器也会采用连续碰撞检测
Constraints 限制:对刚体的运动进行限制。
- Freeze Position 冻结位移:当刚体移动时,可选地忽略在某个轴上的位移。有 3 个可勾选属性 X、Y、Z,分别表示在 X 轴、Y 轴和 Z 轴方向上的位移。如果勾选了 Y 则表示,刚体受力运动后,不会在 Y 轴方向上发生位移。
- Freeze Rotation 冻结旋转:当刚体旋转时,可选地忽略绕某个轴的旋转。也有 3 个可勾选属性,分别表示绕 X 轴旋转、绕 Y 轴旋转和绕 Z 轴旋转。如果勾选了 X 则表示,刚体受力运动后,不会绕 X 轴发生旋转。

物理系统和 Transform

在 Unity 中,控制游戏对象有两种方式:一种是直接使用 Transform,比如 Transform.Position 或者 Transform.Rotation 等;另一种方式就是给游戏对象添加刚体,通过物理系统控制游戏对象。
需要注意的是,一旦给游戏对象添加了刚体组件,那么这个游戏对象的运动会由物理系统负责计算。此时不要在脚本中直接使用 Transform.Position 或者 Transform.Rotation 等代码直接修改游戏对象的位置和旋转,否则会导致物理计算结果不正确。也就是说:
使用物理系统控制的游戏对象,不要直接修改 Transform 属性
这是因为物理系统会根据刚体的受力情况,计算出每一帧物体的正确位置以及旋转,然后改变物体的 Transform。此时如果我们通过代码强行修改 Transform 的属性,最终会导致物体的运动不正确,或无法正确地碰撞。
使用物理系统时,如果在脚本中需要控制刚体,可以使用刚体的 void AddForce (Vector3) 方法,给刚体施力使刚体移动,或者使用刚体的 void AddTorque (Vector3) 方法,给刚体施加力矩使刚体产生旋转。如果需要让刚体相对精确地移动和旋转,可以使用刚体的 void MovePosition (Vector3) 方法,移动刚体的位置,或者使用 void MoveRotation (Quaternion) 方法,旋转刚体。
如果我们只是需要让物理系统进行碰撞检测,不需要使用物理系统控制游戏对象,而是在脚本中使用 Transform 控制物体,此时我们可以勾选刚体组件中的 Is Kinematic 属性。关于运动学属性的详细讲解请参看《运动学(施工中)》。
在使用刚体时还有一个需要额外注意的就是刚体的大小。场景中的刚体要保证它的大小,或者说缩放比例是合适的。一个鸡蛋大小的球体和一个月球大小的球体,自由下落时看起来效果是不一样的,尽管它们两个下落速度相同,但更大的物体下落时,在感官上会觉得下落速度很慢。所以如果感觉场景中物体的物理效果不对时,也要先检查一下物体大小是否正确。

刚体常用属性和方法

在脚本中,我们可以使用 GetComponent<Rigidbody> () 方法获取当前游戏对象身上的刚体组件,当然前提是它有这个组件。
除了这行代码以外,其它关于物理控制的代码都建议放在 void FixedUpdate () 方法中完成,不建议在 void Update () 方法中。因为 Update 方法是帧更新,也就是场景中视图的更新,帧更新的速度并不稳定并且频率比 FixedUpdate 方法低。将物理控制代码放在 FixedUpdate 中能够保证物理效果的及时处理,这样等到每次帧更新时,场景中的刚体都会是正确的位置和旋转。
获取到刚体组件后,我们可以通过代码给它施加力、力矩,或者改变它的位置、旋转,或者直接修改它的速度、角速度等。不过首先我们看看刚体属性面板中的这些属性,如何通过代码进行修改。
其中 constraints 属性比较特殊,它是一个枚举类型,表示刚体运动时限制的位移或旋转轴。它的值可以通过按位或运算进行状态叠加,比如我们只需要限制 PositionXPositionZ,其它的都不限制,那么可以这样写,两个枚举值中间进行按位或运算(|)。

位置和旋转

刚体最常用的功能就是以物理的方式控制游戏对象的运动,所以 Rigidbody 中有一些与位置、旋转相关的属性和方法。
其中用来控制刚体位置的有一个 position 属性和一个 MovePosition 方法,用来控制刚体旋转的是 rotation 属性MoveRotation 方法
使用 transform.position 或者 transform.rotation 也可以控制位移和旋转, rigidbody.positionrigidbody.rotation 使用方式和 Transform 没太大区别,不一样的是 Rigidbody 这两个属性修改后,会直接在下一次物理循环中更新 Transform,响应速度更快一些,能保证物理效果的正确性。
rigidbody.MovePosition () 方法和 rigidbody.MoveRotation () 方法更适合FixedUpdate 中连续移动或旋转物体。这两个方法会考虑刚体的插值设置,在连续运动时减少抖动。如不需要连续运动想直接瞬移物体,就使用 rigidbody.positionrigidbody.rotation 属性。连续移动并旋转刚体的例子如下。

力和速度

除了直接修改刚体的位置和旋转以外,我们也可以给刚体施力让它产生运动。力是矢量,所以一般用 Vector3 表示一个力的方向和大小。在脚本中有四种方法给刚体施力。
最常用的是 AddForce 方法。它直接给刚体的质心(质量中心,默认是几何中心)施加力,并且这个力会以世界坐标系进行处理。如果我们参数填写的是 Vector3.forward * 10f,那么这个力的方向就是世界坐标系中 Z 轴正方向,大小是 10。
如果我们需要让这个力以刚体本地坐标系进行处理,可以使用 AddRelativeForce 方法。它的使用方式和 AddForce 方法完全一样,唯一不同的就是,这个方法会把参数中设置的力,以刚体的本地坐标系进行处理,而不是世界坐标。如果我们使用这个方法,参数填写的是 Vector3.forward * 10f,那么这个力的方向就是刚体本地坐标系中 Z 轴正方向,大小是 10。
我们也可以不把力施加到刚体的质心,使用 AddForceAtPosition 方法可以在指定的位置向刚体施力。此时第一个参数表示力的大小和方向,第二个参数就是施力的位置,这两个参数都是以世界坐标系进行处理的。除此以外这个方法使用起来和 AddForce 方法一样。
如果我们要模拟爆炸时周围物体都被炸飞的效果,可以使用 AddExplosionForce 方法添加爆炸力。方法至少需要三个参数,分别表示力的大小(float)、爆炸中心位置(世界坐标系 Vector3)和爆炸的有效半径(float)。添加爆炸力的方法需要由刚体实例来调用,爆炸发生时需要让哪些刚体炸飞,那么这些刚体都需要在同一时间调用这个方法,传递相同的参数。
爆炸发生时,会以爆炸中心位置和爆炸有效半径限定一个球形范围,若刚体在此范围内则会受到一个力的影响,力的方向是从爆炸中心指向刚体质心的向量,力的大小决定于刚体和爆炸中心的距离。
使用爆炸力时,还有可选的第 4 个参数,upwardsModifier。它是 float 类型,默认值为 0。表示在爆炸发生时计算受力方向,会将爆炸中心点位置的 Y 值,往负方向移动多远。比如此参数设置为 2 时,爆炸看起来的位置就在实际位置向下 2 单位,最终的爆炸效果会把周围刚体都向上炸飞。这个参数只会影响爆炸方向,不会影响力的大小。
以上四种添加力的方法都还有一个可选参数,ForceMode 枚举类型参数,施力模式。默认值都是 ForceMode.Force 表示以力的方式施加。另外还有 3 个枚举值,我们可以根据需要设置施力模式。
加速度模式 ForceMode.Acceleration在此模式下,Vector3 类型的参数不再表示力的方向和大小,而是表示加速度的方向和大小(加速度也是矢量)。
此时会直接给刚体添加一个加速度。设置了加速度之后,假设当前帧刚体速度是 v1,那么等到下次物理帧刷新时刚体的速度就是 v2,而 Δt 则是 Time.deltaTime
冲量模式 ForceMode.Impulse在此模式下,Vector3 类型的参数也不再表示一个力,而是一个冲量。
设置冲量可以给物体一个瞬时冲击力,而且要注意冲量的作用效果和物体的质量相关,给不同质量的物体设置相同的冲量,作用效果是不一样的。
速度模式 ForceMode.VelocityChange在此模式下,Vector3 类型的参数直接表示一个速度。
以上三种模式并不常用到,但有些特殊的物理相关的游戏中也许有用。直接给刚体添加冲量或加速度能更容易实现某些物理效果,而不需要我们手动计算力或者速度大小。
力矩也是物理学中的概念,用来描述物体受力旋转的矢量。力矩的方向垂直于旋转平面。我们可以直接给刚体添加力矩使之旋转。
使用 AddTorque 方法或者 AddRelativeTorque 方法,它们两个的区别在于坐标系不同。
我们也可以使用更直观的方式,直接控制刚体的速度或者角速度,角速度的方向也是垂直于旋转平面的。但不建议直接对速度和角速度进行修改,因为这有可能导致物理效果不真实。在某些特殊情况下,我们需要让速度或角速度立即生效,此时可以直接修改速度属性。
一般情况下,我们使用 AddForce 等相关方法施加力或者控制速度,使用 AddTorque 方法控制旋转或者角速度,用 MovePositionMoveRotation 方法连续精确地控制位移和旋转。除了这些常用的属性和方法,还有一些不常用的,比如设置质心的位置、设置最大角速度(默认值是 7)、刚体的休眠和唤醒(默认系统自动完成)、获取刚体上某一点的瞬时速度等。
(TheEnd)


宋博
Unity 讲师 - Educator
3
Comments