Unity 物理系统入门文档梳理

这周在工作的时候,看到了项目中使用了做物理材质,这才反应过来,自己从来没有用过,也不太清楚他和刚体等其他物理组件的关系,所以就重新阅读一下官方文档,进行一定的实践和总结。

Unity支持多种物理引擎,我平时用到的是面向对象的3D物理引擎,所以也主要对这里的内容进行研究。

本文介绍可通过 Unity 内置的 3D 物理引擎获得的主要组件,您可以在面向对象的项目中使用这些组件。本节包括以下主题:

  • 与物理系统相关的主要概念的概述:刚体、碰撞体、关节、物理接合和角色控制器。
  • 有关特定物理环境的一些说明:连续碰撞检测和多场景物理。
  • 物理调试可视化的描述。

刚体

刚体 (Rigidbody) 是实现游戏对象的物理行为的主要组件。连接刚体后,对象将立即响应重力。如果还添加了一个或多个__碰撞体__组件,则游戏对象会因发生碰撞而移动。

由于刚体组件会接管附加到的游戏对象的运动,因此不应试图借助脚本通过更改变换属性(如位置和旋转)来移动游戏对象。相反,应该施加__力__来推动游戏对象并让物理引擎计算结果。

也就是说,刚体的作用是让物体能够响应力,但是不会产生力,我们需要通过脚本或者碰撞等方式来产生力。

在某些情况下,可能希望游戏对象具有刚体,并让刚体的运动摆脱物理引擎的控制。例如,可能希望直接从脚本代码控制角色,但仍允许触发器检测角色。脚本产生的这种非物理运动称为_运动学_运动。刚体组件有一个名为 Is Kinematic 的属性,该属性可以让刚体摆脱物理引擎的控制,并允许通过脚本以运动学方式来移动刚体。可以通过脚本来更改 Is Kinematic 的值,从而为某个对象开启和关闭物理引擎,但这会产生性能开销,应谨慎使用。

这里产生了一个问题,如果想要游戏对象摆脱物理引擎的控制,为什么不直接把刚体移除?还要通过设置 Is Kinematic 属性这种方式。这方面的一个常见例子是“布娃娃”效果;在这种效果中,角色通常在动画下移动,但在爆炸或猛烈碰撞时被真实抛出。角色的四肢可被赋予自己的刚体组件,并在默认情况下启用 IsKinematic。肢体将通过动画正常移动,直到所有这些肢体关闭 IsKinematic 为止,然后它们立即表现为物理对象。此时,碰撞或爆炸力将使角色飞出,使肢体以令人信服的方式被抛出。

睡眠

当刚体移动速度低于规定的最小线性速度或转速时,物理引擎会认为刚体已经停止。发生这种情况时,游戏对象在受到碰撞或力之前不会再次移动,因此将其设置为“睡眠”模式。这种优化意味着,在刚体下一次被“唤醒”(即再次进入运动状态)之前,不会花费处理器时间来更新刚体。

在大多数情况下,刚体组件的睡眠和唤醒是透明发生的。但是,如果通过修改__变换__位置将静态碰撞体(即,没有刚体的碰撞体)移入游戏对象或远离游戏对象,则可能无法唤醒游戏对象。这种情况下可能会导致问题,例如,已经从刚体游戏对象下面移走地板时,刚体游戏对象会悬在空中。在这种情况下,可以使用 WakeUp 函数显式唤醒游戏对象。

也就是说,如果一个刚体已经进入睡眠状态了,用一个静态碰撞体去尝试改变刚体的受力状态,可能并不会有效果,需要我们显式唤醒

碰撞体

碰撞体组件可定义用于物理碰撞的游戏对象的形状。碰撞体是不可见的,其形状不需要与游戏对象的网格完全相同。网格的粗略近似方法通常更有效,在游戏运行过程中难以察觉。

原始碰撞体

最简单(并且也是处理器开销最低)的碰撞体是__原始__碰撞体类型。在 3D 中,这些碰撞体为盒型碰撞体、球形碰撞体和胶囊碰撞体。在 2D 中,可以使用 2D 盒型碰撞体 和 2D 圆形碰撞体。可以将任意数量的上述碰撞体添加到单个游戏对象以创建__复合碰撞体__。

复合碰撞体

复合碰撞体可以模拟游戏对象的形状,同时保持较低的处理器开销。为了获得更多灵活性,可以在子游戏对象上添加额外的碰撞体。例如,可以相对于父游戏对象的本地轴来旋转盒体。在创建像这样的复合碰撞体时,层级视图中的根游戏对象上应该只使用一个刚体组件。

原始碰撞体无法正常处理剪切变换。如果在变换层级视图中组合使用旋转和非均匀比例,从而使产生的形状不再是原始形状,则原始碰撞体无法正确表示这个形状。

网格碰撞体

然而,在某些情况下,即使复合碰撞体也不够准确。在 3D 中,可以使用网格碰撞体精确匹配游戏对象网格的形状。在 2D 中,2D 多边形碰撞体不能完美匹配精灵图形的形状,但您可以将形状细化到所需的任何细节级别。

这些碰撞体比原始类型具有更高的处理器开销,因此请谨慎使用以保持良好的性能。此外,网格碰撞体无法与另一个网格碰撞体碰撞(即,当它们进行接触时不会发生任何事情)。在某些情况下,可以通过在 Inspector 中将网格碰撞体标记为 Convex 来解决此问题。此设置会产生“凸面外壳”形式的碰撞体形状,类似于原始网格,但填充了底切。

这样做的好处是,凸面网格碰撞体可与其他网格碰撞体碰撞,因此,当有一个包含合适形状的移动角色时,便可以使用此功能。但是,一条适用的规则是将网格碰撞体用于场景几何体,并使用复合原始碰撞体近似得出移动游戏对象的形状。

静态碰撞体(没有刚体)

可将碰撞体添加到没有刚体组件的游戏对象,从而创建场景的地板、墙壁和其他静止元素。这些被称为_静态_碰撞体。相反,具有刚体的游戏对象上的碰撞体称为_动态_碰撞体。静态碰撞体可与动态碰撞体相互作用,但由于没有刚体,因此不会通过移动来响应碰撞。

物理材质

当碰撞体相互作用时,它们的表面需要模拟所应代表的材质的属性。例如,一块冰将是光滑的,而橡胶球将提供大量摩擦力并且弹性很好。虽然碰撞时碰撞体的形状不会变形,但可以使用 Physics Materials(物理材质)__配置碰撞体的摩擦力和弹力。可能需要进行多次试验和纠错后才能获得正确参数,比如冰材质将具有零(或非常低的)摩擦力,而橡胶材质则具有高摩擦力和近乎完美的弹性。有关可用参数的更多详细信息,请参阅物理材质和 2D 物理材质的参考页面。请注意,由于历史原因,3D 资源实际上称为 Physic Material

触发器

脚本系统可以使用 OnCollisionEnter 函数检测何时发生碰撞并启动操作。但是,也可以直接使用物理引擎检测碰撞体何时进入另一个对象的空间而不会产生碰撞。配置为__触发器__(使用 Is Trigger 属性)的碰撞体不会表现为实体对象,只会允许其他碰撞体穿过。当碰撞体进入其空间时,触发器将在触发器对象的脚本上调用 OnTriggerEnter 函数

碰撞体回调

发生碰撞时,物理引擎会在附加到相关对象的所有脚本上调用特定名称的函数。可以在这些函数中放置所需的任何代码来响应碰撞事件。例如,当汽车撞到障碍物时,可以播放碰撞音效。

在第一个检测到碰撞的物理更新中,将调用 OnCollisionEnter 函数。在保持接触的更新期间,将调用 OnCollisionStay,最后由 OnCollisionExit 指示接触已经中断。触发碰撞体会调用模拟的 OnTriggerEnter、OnTriggerStay 和 OnTriggerExit 函数。请注意,对于 2D 物理,可使用在名称中附加了 2D 字样的等效函数,例如 OnCollisionEnter2D。有关这些函数和代码示例的完整详细信息,请参阅关于 MonoBehaviour 类的脚本参考页面。

对于正常的非触发碰撞,还有一个额外的细节,即所涉及的对象中至少有一个对象必须具有非运动刚体(即,必须关闭 Is Kinematic)。如果两个对象都是运动刚体,则不会调用 OnCollisionEnter 等函数。对于触发碰撞,此限制不适用,因此运动和非运动刚体都会在进入触发碰撞体时提示调用 OnTriggerEnter。

这一点也比较好理解,如果两个运动刚体碰到一起,是不会发生重合的,自然也不会产生OnCollisionEnter事件

也就是说,根据碰撞对象的碰撞体的Is Trigger属性,碰撞分为触发器碰撞和非触发器碰撞,分别会触发不同的函数,而非触发碰撞,需要至少有一个对象具有非运动刚体

碰撞体相互作用

碰撞体彼此之间的相互作用根据刚体组件的配置不同而不同。三个重要的配置是_静态碰撞体 (Static Collider)(即完全没有附加任何刚体)、刚体碰撞体 (Rigidbody Collider)_ 和_运动刚体碰撞体 (Kinematic Rigidbody Collider)_。

静态碰撞体

静态碰撞体是具有碰撞体而没有刚体的游戏对象。静态碰撞体在大多数情况下用于表示始终停留在同一个地方而永远不会四处移动的关卡几何体。靠近的刚体对象会与静态碰撞体发生碰撞,但不会移动静态碰撞体。

在特殊情况下,物理引擎针对永远不会移动的静态碰撞体进行优化。例如,即使您移动静态碰撞体,停在静态碰撞体上的车辆也会保持睡眠状态。您可以在运行时启用、禁用或移动静态碰撞体,而不会特别影响物理引擎的计算速度。另外,只要缩放比例是统一的(无偏差),就可以安全地缩放静态网格碰撞体。

刚体碰撞体

这是一种附加了碰撞体和普通非运动刚体的游戏对象。刚体碰撞体完全由物理引擎模拟,并可响应通过脚本施加的碰撞和力。刚体碰撞体可与其他对象(包括静态碰撞体)碰撞,是使用物理组件的游戏中最常用的碰撞体配置。

运动刚体碰撞体

这是一种附加了碰撞体和_运动_刚体(即启用了刚体的 IsKinematic 属性)的游戏对象。可使用脚本来移动运动刚体对象(通过修改对象的变换组件),但该对象不会像非运动刚体一样响应碰撞和力。运动刚体应该用于符合以下特征的碰撞体:偶尔可能被移动或禁用/启用,除此之外的行为应该像静态碰撞体一样。这方面的一个例子是滑动门,这种门通常用作不可移动的物理障碍物,但必要时可以打开。与静态碰撞体不同,移动的运动刚体会对其他对象施加摩擦力,并在双方接触时“唤醒”其他刚体。

即使处于不动状态,运动刚体碰撞体也会对静态碰撞体产生不同的行为。例如,如果将碰撞体设置为触发器,则还需要向其添加刚体以便在脚本中接收触发器事件。如果不希望触发器在重力作用下跌落或在其他方面受物理影响,则可以在其刚体上设置 IsKinematic 属性。

可使用 IsKinematic 属性随时让刚体组件在正常和运动行为之间切换。

碰撞体操作矩阵

具体可以直接看一下文档:https://docs.unity3d.com/cn/2021.2/Manual/CollidersOverview.html

刚体和碰撞体的交叉关系

这里插一段关于刚体和碰撞体的总结。

从物理引擎的角度讲,只有有了刚体才能响应力的作用,而碰撞体的作用就是产生力,同时碰撞体可以关联一个物理材质,用于设置比如摩擦系数,反弹系数等参数来对应控制在碰撞时产生不同的力。

同时刚体有一个开关叫做 IsKinematic ,可以控制刚体是否参与物理引擎的计算,从而控制刚体是否响应力的作用

而碰撞体有一个开关叫做 Is Trigger,用于控制这个碰撞体是否是一个触发器,一旦是一个触发器了,该碰撞体就不会在和另一个碰撞体发生碰撞时产生力,而是让其他碰撞体穿过,同时触发OnTriggerEnter、OnTriggerStay等函数

还有一点要注意的是,碰撞体是有具体形状的,刚体其实没有,也可以理解刚体的形状就是所有会产生力的碰撞体的集合

关节

关节组件将刚体连接到另一个刚体或空间中的固定点。关节施加使刚体移动的力,而关节限制功能可以限制该移动。关节赋予刚体以下自由度:

Unity 提供的以下关节可以对刚体组件施加不同的力和限制,从而使这些刚体具有不同的运动:

属性:功能:
Character Joint模拟球窝关节,例如臀部或肩膀。沿所有线性自由度约束刚体移动,并实现所有角度自由度。连接到角色关节 (Character Joint) 的刚体围绕每个轴进行定向并从共享原点开始转动。
Configurable Joint模拟任何骨骼关节,例如布娃娃中的关节。您可以配置此关节以任何自由度驱动和限制刚体的移动。
Fixed Joint限制刚体的移动以跟随所连接到的刚体的移动。当您需要一些可以轻松相互分离的刚体,或者您想连接两个刚体的移动而无需在 Transform 层级视图中进行父级化时,这种关节很有用。
Hinge Joint在一个共享原点将一个刚体连接到另一个刚体或空间中的一个点,并允许刚体从该原点绕特定轴旋转。用于模拟门和手指关节。
Spring Joint使刚体彼此分开,但使刚体之间的距离略微拉伸。弹簧就像一块弹性物,试图将两个锚点一起拉到完全相同的位置。

关节还有其他可用于实现特定效果的选项。例如,可设置一个关节,确保在刚体施加到关节的力超过某个阈值时破坏关节。一些关节允许在连接的刚体之间产生__驱动力__以使它们自动运动。

注意:如果要在工业应用的上下文中构建运动链,例如模拟具有逼真物理行为的机械臂,应使用物理接合而不是这里描述的常规关节。

物理接合

一个物理接合是一组以逻辑树的形式组织而成的接合体,其中每个父子关系都反映了相互约束的相对运动。

物理接合的主要目的是为涉及关节的非游戏工业应用提供逼真的物理行为。相比常规关节,物理接合在模拟机械臂和运动链等方面要便捷很多。

物理接合与关节

接合设置使用常规关节设置
层级视图游戏对象 + 接合体 ; 游戏对象 + 接合体游戏对象 + 刚体 ;游戏对象 + 刚体 + 关节
关系游戏对象具有层级关系(父子)注意:物理引擎使用 Unity 变换层级视图来表达父子关系。游戏对象不一定具有层级关系。注意:在更高级的场景中,您可以自由模拟运动环路。
物理体两个游戏对象都有一个接合体组件,它定义了物理体属性(等等)。两个游戏对象都有一个刚体组件,它定义了物理体属性(等等)。
Joint子游戏对象的接合体包含关节属性,您可以在其中选择关节类型。其中一个游戏对象也有一个关节组件。关节属性取决于您添加的关节组件的类型。

但是,两种情况下的总体结果行为并不相同,尤其是如果您将此原则扩展到多个物理体和关节。

如果您尝试使用常规关节对运动链进行建模,例如在布娃娃、机械臂或具有多个并行铰链的机构中,物理引擎可能会遇到无法求解的情况并遗留一些无法满足的约束。这可能会导致卡顿和不切实际的运动。这些关节不仅看起来很怪异,而且也不可能将它们用于模拟真实设备,从而阻碍对工业设计进行建模或原型构建。

在Unity中构建物理接合

要在 Unity 中构建物理接合,您必须向构成接合的每个游戏对象添加一个接合体组件。每个接合体组件的配置都可以集中在一个位置进行:

对应的游戏对象的物理体属性。基本上,它的质量及其对物理环境的响应方式。

将游戏对象链接到其父游戏对象的关节的类型和属性(接合的根除外)。

下面的例子显示了一个简单的物理接合,涉及 3 个物理体和 2 个关节:

要在 Unity 中构建这样的接合:

1.创建 3 个游戏对象的线性层级结构。

2.分别为这 3 个游戏对象添加一个接合体组件。

3.配置每个接合体组件(根据上图):

GameObject接合体组件配置
A(根)您只能为游戏对象 A 定义物理体属性。
B您可以定义:游戏对象 B 的物理体属性. 与游戏对象 A 连接的关节的类型和属性。
C您可以定义:游戏对象 C 的物理体属性。与游戏对象 B 连接的关节的类型和属性。

注意:根据定义,一个接合只能有一个根,并且不允许有运动环路。如果您需要运动环路,请使用常规关节。

局限性:如果您想构建非常长的接合链,仍需注意 Unity 支持的最大层级深度为 64 个游戏对象。

接合关节类型和自由度

通过接合体,您可以选择和配置四种类型的接合关节:

  • 固定关节:在物理体之间设置刚性、牢不可破和不可拉伸的链接。

  • 棱形关节:阻止除了沿特定轴滑动之外的所有运动。

  • 旋转关节:允许绕特定轴旋转(如铰链)。

  • 球形关节:解剖学关节,允许两个摆动和一个扭转。

接合中所有锁定的自由度在设计上都是牢不可破和不可拉伸的。为了实现这一点,物理引擎使用降维坐标空间,物理体只有关于未锁定运动轴的坐标

相比之下,对于常规迭代关节,物理引擎采用最大坐标空间,只有当求解器能够在一组迭代后收敛,才能保证满足约束

角色控制器

第一人称或第三人称游戏中的角色通常需要一些基于碰撞的物理效果,这样角色就不会跌穿地板或穿过墙壁。但是,通常情况下,角色的加速度和移动在物理上并不真实,因此角色可以不受动量影响而几乎瞬间加速、制动和改变方向。

在 3D 物理中,可以使用__角色控制器__创建此类行为。该组件为角色提供了一个始终处于直立状态的简单胶囊碰撞体。控制器有自己的特殊函数来设置对象的速度和方向,但与真正的碰撞体不同,控制器不需要刚体,动量效果也不真实。

角色控制器无法穿过场景中的静态碰撞体,因此将紧贴地板并被墙壁阻挡。控制器可以在移动时将刚体对象推到一边,但不会被接近的碰撞加速。这意味着可以使用标准 3D 碰撞体来创建供控制器行走的场景,但您不受角色本身的真实物理行为的限制。

详细设置:https://docs.unity3d.com/cn/2021.2/Manual/class-CharacterController.html

连续碰撞检测

CCD 确保快速移动的物体与对象碰撞,而不会穿过这些对象。Unity 提供以下 CCD 方法:

  • 基于扫掠的 CCD
  • 推断性 CCD

要使用基于扫掠的 CCD,请在 Inspector 窗口中选择一个刚体 (RigidBody),并将 Collision Detection 设置为 Continuous 或 Continuous Dynamic。要使用推断性 CCD,请将 Collision Detection 设置为 Continuous Speculative。

基于扫掠的CCD

基于扫掠的 CCD 采用撞击时间 (TOI) 算法,通过扫掠对象的前向轨迹来计算对象的潜在碰撞(采用对象的当前速度)。如果沿对象移动方向有接触,该算法会计算撞击时间并移动对象直至达到该时间。该算法可从该时间开始执行子步骤,即计算 TOI 之后的速度,然后重新扫掠,代价是需要经历更多的 CPU 周期。

然而,因为此方法依赖于线性扫掠,所以会忽略物体的角运动,这在对象迅速旋转时会引起穿隧效应。例如,弹球机上的弹球杆固定在一端,围绕一个固定点旋转。弹球杆只做角运动,不做线性运动。因此,很容易打不中弹球:

此方法的另一个问题是性能问题。如果附近有大量启用 CCD 的高速对象,CCD 的开销将由于进行额外的扫掠而很快增加,因此物理引擎不得不执行更多的 CCD 子步骤

推断性CCD

推断性 CCD 的工作原理是基于对象的线性运动和角运动增大一个对象的粗筛阶段轴对齐最小包围盒 (AABB)。该算法是一种推测性的算法,因为会选取下一物理步骤中的所有潜在触点。然后将所有触点送入解算器,因此可确保满足所有的触点约束,使对象不会穿过任何碰撞。

下图显示了从 t0 开始移动的球体如何获得预期的 t1 位置(如果其路径中没有墙)。通过使用目标姿势将 AABB 扩大,推测性算法选取与 n1 和 n2 法线之间的两个触点。然后,该算法告诉解算器遵循这些触点,使球体不会穿过墙壁。

基于当前速度并扩大的 AABB 有助于检测沿运动轨迹的所有潜在触点,使解算器能够防止对象穿过去。

推测性 CCD 的成本通常低于基于扫掠的方法,因为只在碰撞检测阶段(而不在求解和积分阶段)计算触点。此外,由于推测性 CCD 根据对象的线性运动和角运动来扩展粗筛阶段 AABB,因此能发现基于扫掠的 CCD 可能遗漏的触点。

幽灵碰撞

但是,推测性 CCD 可能会导致幽灵碰撞;在这种碰撞中,对象的运动受到推测性触点的影响,而这是不应发生的。这是因为推测性 CCD 根据最近点算法收集所有潜在触点,所以触点法线不太准确。这通常会使高速对象沿着细分的碰撞特征滑动并跳起来,但不应该这样。例如,下图中,球体从 t0 开始向右水平移动,积分后的预测位置为 t1。扩大后的 AABB 与框形 b0 和 b1 重叠,而 CCD 在 c0 和 c1 产生两个推测性触点。由于推测性 CCD 使用最近点算法来生成触点,c0 具有非常倾斜的法线,因此解算器会将其视作斜坡。

解算器认为 c0 处的触点是斜坡,因为最近点算法生成了不准确的触点法线

穿隧

推测性 CCD 还可能导致发生穿隧,因为只会在碰撞检测阶段计算推测性触点。在触点求解过程中,如果一个对象从解算器获得太多能量,在积分后,其最终位置可能在初始扩大的 AABB 之外。如果在紧邻 AABB 的外部发生碰撞,对象会从右边穿出。

例如,下图显示了球体从 t0 向左移动,而球杆顺时针旋转。如果球体从撞击中获得太多能量,最终可能离开扩大的 AABB(红点矩形),落在 t1 处。如果在紧邻 AABB 的外部发生碰撞(如下面的蓝色框所示),球体最终可能会从右边穿出。这是因为解算器只计算扩大的 AABB 的内部触点,在求解和积分阶段不会执行碰撞检测