Notifications
Article
碰撞器
Updated 14 days ago
17
0
刚体的主要作用是使物体能够受力并施力,但只有刚体组件的物体不能进行碰撞检测,我们需要额外为物体定义它的物理边界,才可以进行碰撞。碰撞器Collider组件就是划定物理边界的组件。一般情况下我们场景中会有三种物体。

相关文章

  • 刚体
  • 碰撞器

概述

刚体的主要作用是使物体能够受力并施力,但只有刚体组件的物体不能进行碰撞检测,我们需要额外为物体定义它的物理边界,才可以进行碰撞。碰撞器Collider组件就是划定物理边界的组件。一般情况下我们场景中会有三种物体。
  • 一种是既没有刚体也没有碰撞器的物体,这种物体只负责模型显示,不进行物理碰撞或者物理操作,所以和物理系统没有关系。比如游戏场景中的云、太阳等。
  • 第二种是既有刚体又有碰撞器的物体,这类物体我们需要通过物理系统移动它们,又需要进行碰撞检测。这类物体我们叫它动态碰撞器,比如游戏中的主角。
  • 最后一种是只有碰撞器没有刚体的物体,这类物体我们需要进行碰撞检测,所以带有碰撞器,但它们不需要在场景中移动,所以没有刚体。这类物体我们叫它静态碰撞器,比如墙壁、树等。
两个物体,如果它们身上只有刚体组件,运动时是不会发生相互碰撞的,而是互相穿透对方。除非它们两个都具有碰撞器,碰撞检测才能正常发生。碰撞发生的必要条件就是:两个物体中至少一个带有刚体,并且两个物体都应具有碰撞器。没有碰撞器肯定不会发生碰撞。
碰撞器不是一个组件,而是多个组件的统称,不同的碰撞器划定的物理边界形状不同。Unity 为我们提供了 3 个简单的基本碰撞器组件:盒型碰撞器Box Collider球形碰撞器Sphere Collider胶囊碰撞器Capsule Collider
因为基本碰撞器的形状简单、面数少,在碰撞检测时速度更快,节省物理资源,所以平时尽可能地使用基本碰撞器。如果要进行碰撞检测的模型形状比较复杂,使用基本碰撞器无法很好地模拟物体形状时,有两种解决方法:可以使用多个基本碰撞器进行组合,近似地模拟物体形状,或者使用网格碰撞器Mesh Collider
网格碰撞器能够精确地匹配游戏对象的网格形状。但因为它的面数多,所以尽可能不要使用它。
除此以外,还有三个特殊功能的碰撞器。车轮碰撞器Wheel Collider专门模拟汽车轮胎的物理行为,用来创建赛车竞速类游戏项目。地形碰撞器Terrain Collider用于 Unity 内置的地形系统进行碰撞检测(Terrain 地形系统可以快速创建场景地形,但真正项目中几乎不用),还有一个用于微软 HoloLens 混合现实开发的碰撞器,叫做空间映射碰撞器Spatial mapping Collider。不过这些碰撞器都不常用到,除非我们要做的项目是赛车类或者 HoloLens 开发也许会使用它们。

基本碰撞器

接下来我们要分别了解盒型碰撞器、球形碰撞器、胶囊碰撞器,它们都是组件,其实也是脚本,或者说是类。所有的碰撞器组件都有一个共同的父类Collider,在父类中定义了碰撞器共同的行为特征,而子类的主要功能只是根据各自的不同形状,定义物理边界的大小。我们可以先看看这些不同碰撞器中的相同属性,他们都是在父类中定义的。
在盒型碰撞器、球形碰撞器和胶囊碰撞器组件中,都有触发器Is Trigger属性和物理材质Material属性,这两个属性都是在它们共同的父类 Collider 中定义的。触发器属性勾选后,碰撞器会成为一个可穿过的特殊区域,叫做触发器,当有刚体穿过该区域时脚本中会收到事件,用来实现一些特殊效果,比如人物进入某个区域范围时会触发剧情。物理材质属性可以定义碰撞器的弹力、摩擦力等物理细节,能够制作出乒乓球(弹力大)、冰面(光滑,摩擦力小)等特殊的物理效果。
在父类中还提供了一个属性 attachedRigidbody,表示碰撞器所附属的刚体,在属性面板中看不到这个属性,在脚本中可以使用。
附属的刚体Rigidbody attachedRigidbody { get; }
这是一个Rigidbody类型的属性,只有Get方法,会返回当前Collider游戏对象上的刚体,如果Collider游戏对象上没有刚体,会继续往父对象身上查找刚体,如果最终都没有刚体,会返回null
在碰撞器属性面板中其余的属性,比如中心点、大小、半径、高度等,都是用来调整它们各自的形状大小和位置的。
在盒型碰撞器中有中心点Center大小Size两个属性,分别表示盒型碰撞器的几何中心的位置以及长宽高的大小。在球形碰撞器中有中心点半径Radius两个属性,分别表示球形碰撞器的几何中心位置以及球体半径的大小。要注意的是,球形碰撞器永远是一个球形,就算修改缩放Scale属性也不可能把它变成一个椭圆形。
属性面板上这些属性同样可以在脚本中进行获取或修改,比如盒型碰撞器的中心和大小。
球形碰撞器的中心点和半径。
胶囊碰撞器的形状比较特别,看起来就像是一个胶囊。它的两端各是一个半球体,中间是一个圆柱体。
胶囊碰撞器中也有中心点半径这两个属性,除此以外还有高度Height属性,表示从胶囊的一端到另一端的距离,以及一个方向Direction属性,表示胶囊体是沿 XYZ 的哪个轴放置的,它有三个值:X-AxisY-AxisZ-Axis
胶囊碰撞器的方向Direction属性比较特殊,它不是一个枚举类型,而是一个int类型的属性。值只能是 0、1 或者 2,分别表示胶囊体的方向是在 X、Y、Z 轴方向上。

网格碰撞器

网格碰撞器不属于基本碰撞器,因为它的形状是由我们提供的网格决定的。给网格碰撞器设置了网格Mesh属性后,碰撞器的物理边界形状就会和网格形状一致。我们可以给场景中的静态物体做一个近似的网格,然后使用网格碰撞器进行碰撞检测。
网格碰撞器中,也有触发器物理材质这两个属性。不同的是,网格碰撞器的形状大小和位置不可以手动调整,而是要一个网格资源的引用。
比如场景中有一叠轮胎,角色移动时不可以穿透轮胎,会被碰撞,我们可以在建模软件中单独做一个用于碰撞检测的网格,配合网格碰撞器实现碰撞检测。一般只有场景中完全不动的物体我们才使用网格碰撞器。也就是网格碰撞器一般不会和刚体一同使用,它只会静止在场景中,等待其它刚体和它发生碰撞
网格碰撞器的网格Mesh属性也可以在脚本中获取或修改。而且用于网格碰撞器的网格,最好是经过简化的特制碰撞网格,不要直接使用模型网格,因为模型网格面数过多,会极大地影响性能。
网格碰撞器中使用的网格引用是普通的网格文件,但必须转换成适合物理引擎的网格,这个过程叫做 Mesh Cooking,由系统自动完成。在网格碰撞器中有一个Cooking Options属性,可以在转换网格时启用一些功能选项。所有的网格在使用前都要经过这个处理过程。一共有四个可选项。
  • - Inflate Mesh选项,默认不启用。启用后会增加输入的网格体积,用来生成有效的凸面网格(勾选了Convex属性的网格碰撞器)。因为物理引擎对于凸面网格的面数和顶点数有一定限制,如果网格面数大于 255 并且碰撞有问题,可以尝试开启此选项,但此时可能会降低网格的精度。
  • - Cook For Faster Simulation选项,默认启用。启用后会执行一些额外操作,确保最终生成的网格在运行时效率是最高的。
  • - Enable Mesh Clean选项,默认启用。启用后会清除多余或无效的三角形面,使碰撞更准确。
  • - Weld Colocated Vertices选项,默认启用。启用后会合并相同位置的顶点。
在这个属性中还有一个NoneEverything两个选项,分别表示全不选和全选。另外,如果网格碰撞器中使用的网格仅用于物理系统,不用于模型显示的话,可以在模型的导入设置中不导入法线,因为物理系统的网格不需要法线。

碰撞和碰撞检测

有了刚体和碰撞器,就可以发生碰撞,并且当碰撞发生时,可以在脚本中处理碰撞事件。两个物体能够发生碰撞而不是互相穿透对方,必要条件是至少有一个是刚体,并且两个物体都有碰撞器。设置好属性后,碰撞是自动发生的。
只要游戏对象身上带有刚体或者碰撞器组件,就可以挂载脚本响应碰撞事件。碰撞事件一共有三个,分别表示碰撞开始OnCollisionEnter持续碰撞OnCollisionStay碰撞结束OnCollisionExit
有了这三个事件,我们可以当碰撞持续时在发生碰撞的位置添加一些粒子特效、在碰撞开始时播放一些音效,或者在碰撞结束时销毁某个游戏对象,具体如何使用由我们的功能要求决定。
这三个事件方法都没有返回值,同时都有一个Collision类型的参数other。这个参数中保存了碰撞相关的一些信息。
最常用的就是通过Collision类型的参数other,获取与当前游戏对象发生碰撞的游戏对象、刚体、碰撞器或者Transform
我们还能获取两个碰撞对象的相对速度以及冲量。
也可以获取碰撞发生时碰撞点的信息,因为碰撞点有可能不止一个,所以这些点存放在数组中。每个碰撞点我们都能够获取它的世界坐标位置和法线向量。
大部分碰撞都可以正常处理,但也会有一些特殊情况,导致无法正常发生碰撞。最常见的碰撞无效就是因刚体速度过快而穿透碰撞器。因为物理系统对碰撞检测也是每帧检测一次,计算刚体位置更新后是否会和其它碰撞器有接触,这种检测叫做离散碰撞检测。
但如果刚体运动速度过快,有可能发生上一帧还在碰撞器的一侧,但下一帧就运动到了另一侧的情况,此时因为刚体并未接触到障碍物的碰撞器,所以检测不出碰撞的发生,会直接穿透障碍物。
此时我们可以调整刚体组件上的碰撞检测Collision Detection属性,使碰撞能够正常发生。此属性默认是Discrete离散检测。
  • - Discrete离散检测:对场景中的其它碰撞体使用离散碰撞检测。一般情况下我们不需要修改此属性。物理系统默认的碰撞检测是离散的,所以有时高速移动的刚体会穿透障碍物,此时我们才需要使用另外两种碰撞检测模式。
  • - Continuous连续检测:刚体对其它没有刚体的碰撞器采用连续检测,防止刚体速度过快穿透障碍物。比如快速飞行的子弹碰撞静止的墙壁,此时如果采用Discrete离散检测,子弹不会打在墙壁上反弹,而是会穿透墙壁。但该模式下并不能正确检测两颗高速飞行的子弹相互碰撞,因为它们两个都带有刚体,此时请使用第三种模式。要注意连续检测会影响物理性能,非必要情况请使用默认的Discrete离散碰撞检测。
  • - Continuous Dynamic连续动态检测:会对其它刚体(碰撞检测模式不能是Discrete离散碰撞检测)进行连续检测,防止互相穿透。对没有刚体的碰撞器也会采用连续碰撞检测。
简单来说,如果刚体运动速度很快导致碰撞没有正常发生,此时障碍物身上如果没有刚体,就使用连续检测,如果障碍物身上也有刚体,就把这两个刚体的碰撞检测模式都设置为连续动态检测。
对于网格碰撞器的碰撞来说,也有一种特殊情况。网格碰撞器一般不推荐移动它的位置,所以一般也不会和刚体一同使用。如果给网格碰撞器添加了刚体,必须勾选Convex属性。否则它不能和其它碰撞器发生碰撞。

触发器

触发器是碰撞器的一种特殊状态,每个碰撞器组件中都有一个触发器Is Trigger属性,勾选之后这个碰撞器就成为一个触发器。正常的碰撞器发生碰撞时,会弹开对方,不会互相穿透,而触发器不会弹开对方。
触发器划定的物理边界是一个触发范围,其他刚体能够进入触发范围,当有其它刚体进入这个范围时,会在脚本中收到触发相关的事件,进行一些额外处理。比如,如果人物靠近自动门,门就会自己打开。此时可以在门前放一个触发器,当检测到人物进入这个触发范围时,使门播放动画和音效打开。
触发器本身不会阻挡刚体的运动,也不会表现出任何效果,所以使用触发器时需要在脚本中进行额外处理。响应触发事件的脚本可以挂载在刚体身上,也可以挂载在触发器身上。触发事件和碰撞事件很相似,也有三个:触发开始OnTriggerEnter持续触发OnTriggerStay触发结束OnTriggerExit
这三个事件方法同样没有返回值,但都有一个Collider类型的参数other。这个参数表示是哪个碰撞器进入了触发区域,我们可以用这个参数获取到进入出发区域的游戏对象或者刚体,再进一步处理。

物理材质

每一个碰撞器组件中都还有一个物理材质Material属性。物理材质是Unity内置的一种资源类型,所以需要在UnityProject视图中创建一个物理材质。
Project视图中空白区域单击鼠标右键-> Create -> Physic Material就可以创建出一个物理材质,他会直接显示在Project视图中。
之后就能够用它给碰撞器组件中的物理材质Material属性赋值。物理材质可以让碰撞器模拟出弹力和摩擦力的效果,选中物理材质资源后,在属性面板中就能看到可以调整的属性信息。
动摩擦力Dynamic Friction表示当其它碰撞器在自己表面滑动时受到的摩擦力大小。它是一个Float类型属性,取值范围 0-1。0 表示没有动摩擦力,可以模拟非常光滑的表面,比如冰面。1 表示动摩擦力非常大,用来模拟非常粗糙的表面,其它碰撞器在它表面滑动时很快就会减速静止。
静摩擦力Static Friction表示当其它碰撞器静止在自己表面,但有运动倾向时,受到的摩擦力大小。它也是个Float类型属性,取值范围 0-1。
弹力Bounciness表示当前碰撞器表面的弹性,Float类型,取值范围 0-1。0 表示不会反弹,1 表示反弹时没有能量损失。
摩擦力组合Friction Combine弹力组合Bounce Combine。因为摩擦力和弹力效果都需要两个碰撞器相互作用,如果另一个碰撞器也设置了摩擦力和弹力,此时应如何计算最终的结果。有四种计算方式。
  • 平均值Average会取这两个碰撞器摩擦力或弹力的平均值。
  • 最小值Minimum会取这两个碰撞器摩擦力或弹力的最小值。
  • 最大值Maximum会取这两个碰撞器摩擦力或弹力的最大值。
  • 乘积Multiply会取这两个碰撞器摩擦力或弹力的乘积。
当然,这些属性也可以在脚本中获取或进行修改。
但物理系统的模拟并不能完全和现实中的物理效果一样,比如弹力为 1 的小球自由落体撞击弹力为 1 的地面,此时小球反弹也许会达到比初始位置更高的高度。所以我们在使用时要根据预期效果调整这些物理参数。
(The End)

宋博
Unity 讲师 - Educator
3
Comments