在物理学中,静止和匀速直线运动是物体的平衡状态,如果给该物体施加某一个力的话,物体的平衡状态就会改变,当然这个真理的前提是理想状态。我们知道在现实世界中,由于重力和摩擦力的存在,任何一个物体都不可能永无止境的运动下去。因此,要想让物体保持运动状态,我们就必须持续的给物体施加一个力。在Unity中就模拟了这种效果,它是通过一个叫做“Rigidbody刚体”的组件实现的。接下来,我们就来创建一个“PhysicsDemo”3D工程来介绍Rigidbody刚体组件。
首先,我们创建了一个平面,然后在平面的上方创建了一个球体。
接下来,我们就给球体添加刚体Rigidbody组件。添加操作非常简单,可以在球体的Inspector检视视图下面点击“Add Component”。
我们在搜索框中输入“ri”,就会在下拉框中就能看到“Rigidbody”组件,注意不要选择“Rigidbody 2D”,这个虽然也是刚体,但是它是用于2D游戏对象的。我们是这里要选择的是3D的“Rigidbody”组件。
在“Rigidbody”刚体组件下有很多参数,我们暂时先不介绍,直接运行工程来查看添加刚体组件后的球体会有什么效果。如下所示:
运行后,我们发现球体直接落到了平面上面。这是因为,给球体添加刚体组件后,球体就会受到“重力”的作用,沿着Y轴方向向下掉落,直到碰到平面而停止。这个“重力”就是“Rigidbody”刚体组件产生的效果。刚体会接管附加到的游戏对象的运动,如果我们施加一个力来推动游戏对象的话,Unity会自动计算该游戏对象的运行效果。如何施加一个力呢?我们点击菜单栏“Component”->“Physics”->“Constant Force” 恒定力组件。
我们在“Constant Force” 恒定力组件的Force参数的X值修改为2,也就是在X轴方向上施加数值为2的一个力来作用于当前球体。我们运行工程,查看效果。
我们发现球体受到重力作用掉落后,还同时受到X轴方向的力而向右继续运动。但是,有时候我们不希望游戏物体以这样的方式运行,因此我们需要游戏对象的运动摆脱Unity物理引擎的控制。刚体组件有一个名为 Is Kinematic 的属性,该属性可以让刚体摆脱物理引擎的控制,并允许通过脚本以运动学方式来移动刚体。接下来,我就来讲解刚体的一些参数。
Mass 游戏对象的质量(默认单位为千克)。
Drag 游戏对象移动时受到阻力的大小,0 表示没有阻力。
Angular Drag 游戏对象旋转时受到阻力的大小,0 表示没有角阻力。
Use Gravity 如果启用此属性,则对象将受重力影响。默认是启用状态。
Is Kinematic 如果启用此选项,则对象将不会被物理引擎驱动,上文中已经提到。
Interpolate 仅当在刚体运动中看到抖动时才尝试使用提供的选项之一。
- None 不应用插值。
- Interpolate 根据前一帧的变换来平滑变换。
- Extrapolate 根据下一帧的估计变换来平滑变换。
Collision Detection 用于防止快速移动的对象穿过其他对象而不检测碰撞。
Discrete为默认值,表示对场景中的所有其他碰撞体使用离散碰撞检测。
Constraints 对刚体运动的限制,可以限制在X/Y/Z某一轴向上移动或旋转。我们展开下面有两个子项,分别是冻结位置和冻结旋转,每一个冻结子选项都分为X,Y,Z三个轴向的冻结。如果当刚体操作游戏物体运动的时候,不会在这些轴向上产生移动或旋转。
在上面的案例中,我们使用“Constant Force” 恒定力组件给球体施加一个力,让其产生运动。如何使用脚本来使用刚体控制物体运动,或者说如何使用脚本给游戏对象施加一个力呢?首先,我们先取消“Constant Force” 恒定力组件,如下所示
我们只需要取消“Constant Force”组件前面的勾选项即可。接下来,我们创建一个脚本SphereScript.cs,然后附加到球体上面,代码如下所示
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class SphereScript : MonoBehaviour
{
// 刚体组件
private Rigidbody rbody;
// Start is called before the first frame update
void Start()
{
rbody = GetComponent<Rigidbody>();
}
// Update is called once per frame
void Update()
{
if (Input.GetKeyDown(KeyCode.A))
{
rbody.AddForce(Vector3.right * 100);
}
}
}
上述代码就是,当我们按下键盘A键的时候,使用刚体的AddForce方法给球体施加一个Vector3.right方向,大小为100。这里需要注意的是,AddForce施加的力在 FixedUpdate 中计算,而FixedUpdate时间间隔一般是0.02秒。代码完成之后,我们还需要做一个非常关键的设置,就是取消Use Gravity,如下所示
这样,球体就不再受重力影响掉落到平面上面了。我们可以Play运行一下。
我们发现,球体不再掉落下来,并且当我们按下A键的时候,球体向右开始移动。我们知道,移动的物体,最重要的是速度。当我们给物体施加一个力的时候,这个速度与力的关系是如何呢?我们修改一下代码,打印刚体的速度velocit 值。
// Update is called once per frame
void Update()
{
if (Input.GetKeyDown(KeyCode.A))
{
rbody.AddForce(Vector3.right * 100);
}
Debug.Log(rbody.velocity);
}
我们增加一个日志,输出游戏物体的速度。我们重新Play工程
我们发现,当我们施加一个力的时候,球体开始运动,X轴向速度是2.0。这个数值是怎么计算得来的呢?首先了解一下动量定理:物体在一段时间Δt内的动量变化量等于它在这个过程中所受力的冲量,也就是力与力的作用时间的乘积,数学表达式为:FΔt = mΔv 。其中F就是这个力的大小,Δt就是这段时间,m就是物体的质量,Δv表示这段时间的平均速度。我们球体对象的质量mass为1(在Rigidbody组件中设置的),然后给它一个X轴正方向大小为100的力。AddForce方法还有第二个参数ForceMode,代表施加力的类型。ForceMode为枚举类型,有4个枚举成员:ForceMode.Force,ForceMode.Acceleration,ForceMode.Impulse,ForceMode.VelocityChange。
他们代表的含义如下所示:
ForceMode.Force 默认方式,使用刚体的质量计算,以每帧间隔时间为单位计算动量。我们上面已经说了FixedUpdate按0.02s秒计算,则由动量定理公式FΔt = mΔv可得, 速度v = FΔt / m = 100 * 0.02 / 1 = 2.0米/秒。也就是说,刚体1秒移动2米的距离。
ForceMode.Acceleration表示忽略游戏对象的质量(相当于质量固定为1)。
ForceMode.Impulse 表示采用瞬间力作用方式,即把Δt的值默认为1,速度会变快。
ForceMode.VelocityChange 表示忽略质量和时间(两者均固定为1),速度就更加快了。
最后,我们要讲解一下刚体的阻力Drag值。在现实世界中,物体移动一定会受到阻力的影响,其速度会慢慢减少,最终停止运动。在Unity中,为了能够让物体运动一段距离后停止,我们可以给刚体组件增加阻力Drag值。接下来,我们给刚体增加1的阻力值。
然后,我们在重新Play工程,查看日志中速度的变化。
我们发现,刚体的速度从2.0开始依次减少0.1的速度。阻力与速度之间的公式是什么呢?网上查询了PhysX得知:velocity = velocity * ( 1 - deltaTime * drag); 也就是说,速度会随着每帧的执行,越来越小,直到为零。假如阻力为1的话,游戏对象会在运动1秒后速度减小到零(静止不动)。也就是说,不管施加的力有多大,当阻力为1的时候,游戏对象都会在1秒后静止。但是,从我们实际的运行情况来看,好像也没有那么的准确。而且,如果发生碰撞的话(球体在平面上),速度也会不断减少。
我们总结一下:刚体可以接受力和扭矩,力可以让刚体对象移动,扭矩可以让刚体对象旋转,向刚体施加力/扭矩实际上会改变对象的变换组件的位置和旋转。我们可以通过刚体的AddForce或AddTorque方法来给游戏对象添加力或者扭矩。但是,在实际开发中,我们可能取消Use Gravity重力影响,勾选Is Kinematic属性,使用代码控制游戏对象平移或旋转变换。两者各有各的优势,刚体可以模拟现实世界的运动效果,而代码控制更加灵活。并且,刚体组件一般都是搭配碰撞体组件一起使用的。另外,当处于休眠模式的刚体收到外界的影响时候,刚体会被唤醒,继续参与物理运算。如果希望通过脚本来控制刚体的休眠状态,可以调用刚体的Sleep和WakeUp方法让刚体进入休眠或者从休眠中唤醒。