Unity 可帮助您在项目中模拟物理系统,以确保对象正确加速并对碰撞、重力和各种其他力做出响应。
从开发方式的角度出发,Unity提供了面向对象和面向数据两种方式。
面向数据指的是Unity提供的一种新的技术栈DOTS(Data Oriented Tech Stack),而面向数据的物理引擎需要使用专用的DOTS物理包。由于此项技术栈尚不成熟,可用性很低,所以暂不考虑。
面向对象就是现在常用的Unity内置的物理引擎,其中又包括2D和3D两种:
物理引擎通常是在时间按固定值前进的假设下运行的,Unity的两个物理引擎也都以这种方式运行。每个迭代都称为时间步长。物理引擎将只使用非常特定的时间值来处理每个时间步长,这与渲染上所花的时间无关。该时间步长在Unity中称为Fixed Update Timestep,它的值默认为20毫秒(每秒更新50次)。
注意:由于体系结构(浮点值表示方式)的不同以及客户端之间的延迟,如果物理引擎使用可变的时间步长,很难在两台不同的计算机之间产生一致的碰撞和力的结果。这样无理引擎往往会在多人的客户端之间或在录制的重播期间生成非常不一致的结果。
下图为官方提供的物理阶段生命周期图:
由上图可见物理阶段是在初始化阶段之后、输入事件阶段之前的一个阶段,通常这个阶段用于处理物理运动计算。
FixedUpdate基于一个可靠的定时器被调用,独立于帧率之外。如果固定的时间步长小于实际的帧更新时间,那么每帧的物理循环可能会发生不止一次。处理物体的物理属性(Rigidbody、Force、Collider)或者输入事件时,需要用FixedUpdate代替Update,以使物体的物理表现更平滑。实际上,FixedUpdate并不是真的按照现实时间间隔执行的,而是按照Timer时间间隔执行的,但Timer并不是真正意义上的现实时间,它的作用是在运行环境下创造一个与现实时间高度相近的变量来实现物理帧的逻辑稳定。因为FixedUpdate的这个特质,强烈建议在此环节只做物理相关的处理,不要把其他类型(如网络帧同步)的处理也放入此步骤。默认频率大概为0.02s,该频率可手动修改。
固定更新结束后,系统内部会进行一系列的操作,最重要的莫过于Unity的内部物理更新,这个是真正的物理更新操作执行。
具体执行步骤大概如下:
触发器被触发时调用。
产生碰撞事件时调用。
当物理帧执行完毕后会跳转到此协程,协程的调用跟方法是不同的,可以理解为在一段代码中设置一个卡点,当程序执行到这个卡点所匹配的时机时卡点后面的代码才会继续执行。
public class Test : MonoBehaviour
{
void Awake()
{
Debug.Log("0");
StartCoroutine(TestCoroutine());
Debug.Log("2");
}
void FixedUpdate()
{
Debug.Log("3 - FixedUpdate");
}
IEnumerator TestCoroutine()
{
Debug.Log("1");
yield return new WaitForFixedUpdate();
Debug.Log("4"); // 当物理帧结束(触发WaitForFixedUpdate)后才会执行这条语句。
}
}
执行结果:01234。
该阶段的发生与渲染无关,其特性决定了其处理物理事件的功能,使物理展示效果更为平滑,另外固定更新的频率并非真的是固定的,实际的执行会根据CPU轮转时间片产生偏移,但这个偏移基本可以忽略不记。
关于Unity完整的生命周期,官方曾给出两个版本的示意图,如果需要深入了解,可以参考我的另一篇文章:【Unity】Unity 生命周期
刚体可使游戏对象受物理引擎控制,在受到外力时产生真实世界的运动效果。
关于CollisionDetection的选择,可以按照以下思路进行:
Continuous和Continuous Dynamic就是为了用来保证void OnCollisionEnter(Collision hit)函数能够检测到高速运动物体,而至于一个为Continuous Dynamic的刚体和一个Discrete的刚体碰撞,前者会使用使用Continous(连续)碰撞,后者离散碰撞。
主动a | 被动b | 效果 |
---|---|---|
刚体 | 刚体 | 穿过 |
刚体 | 碰撞体 | 穿过 |
刚体 | 刚体+碰撞体 | 穿过 |
碰撞体 | 刚体 | 穿过 |
碰撞体 | 碰撞体 | 穿过 |
碰撞体 | 刚体+碰撞体 | b动 |
刚体+碰撞体 | 刚体 | 穿过 |
刚体+碰撞体 | 碰撞体 | a动 |
刚体+碰撞体 | 刚体+碰撞体 | a、b都动 |
上表为不同情况下a物体主动碰撞b物体产生的效果。
碰撞体使物体拥有实质性的边界,刚体在拥有碰撞体后才能实现碰撞效果,而碰撞体往往可以独立存在于物体上,从而实现更复杂的动态效果。
Unity 3D 为游戏开发者提供了多种类型的碰撞体资源,如下图所示。当游戏对象中的 Rigidbody 碰撞体组件被添加后,其属性面板中会显示相应的属性设置选项,每种碰撞体的资源类型稍有不同。
Box Collider(方块碰撞器):方块形状的碰撞器,也是最常用的碰撞器,可以指定触发器(Is Trigger)、材质(Material)、中心(Center)、大小(Size)。通常用于墙壁、门、平台、箱柜等物体。
Sphere Collider(球状碰撞器):球状碰撞器,可以指定触发器(Is Trigger)、材质(Material)、中心(Center)、半径(Radius)。通常用于球状物体。
Capsule Collider(胶囊碰撞器):胶囊状的碰撞器,可以指定触发器(Is Trigger)、材质(Material)、中心(Center)、半径(Radius)、高度(Height)。通常用于角色碰撞器。
Mesh Collider(网格碰撞器):根据 Mesh 形状产生碰撞体,比起 Box Collider、Sphere Collider 和 Capsule Collider,Mesh Collider 更加精确,但会占用更多的系统资源。
Wheel Collider(车轮碰撞器):车轮碰撞器是一种针对地面车辆的特殊碰撞体,自带碰撞侦测、轮胎物理现象和轮胎模型,专门用于处理轮胎。
Terrain Collider(地形碰撞器):地形碰撞器根据指定的材质(Material)及地形数据(Terrain Data)来实现碰撞效果。
物理材质用于调整碰撞对象的摩擦力和反弹效果。
Unity 3D 提供了一些物理材质资源,通过资源添加方法可以添加到当前项目中。
标准资源包提供了 5 种物理材质:
注意:3D物体由网格和纹理组成,所以如果要更换物体就要改变对象的MeshFilter.Mesh和MeshRender.Material.maintexture两个属性。
如果既想要检测到物体的接触,又不想让碰撞检测影响物体移动,或者要检测一个物体是否经过空间中的某个区域,这时就可以用到触发器。触发器是碰撞器的一个属性,碰撞体是触发器的载体。
例如,碰撞体适合模拟汽车被撞飞、皮球掉在地上又弹起的效果,而触发器适合模拟人站在靠近门的位置时门自动打开的效果。
触发器用于在游戏中触发事件。比如在游戏中的剧情里,玩家通过某个任务道具召唤了任务相关的 NPC 或者可击杀的怪物,就可以用触发器来实现。
在 Unity 3D 中,检测碰撞发生的方式有两种:
勾选了Is Trigger的碰撞体,当满足以下条件后会调用OnTrigger方法。
当绑定了碰撞体的游戏对象进入触发器区域时,会运行触发器对象上的 OnTriggerEnter 函数,同时需要在检视面板中的碰撞体组件中勾选 IsTrigger 复选框。
触发信息检测使用以下 3 个函数:
MonoBehaviour.OnTriggerEnter(Collider collider),当进入触发器时触发。
MonoBehaviour.OnTriggerExit(Collider collider),当退出触发器时触发。
MonoBehaviour.OnTriggerStay(Collider collider),当逗留在触发器中时触发。
刚体为系统提供了物理作用,但其基于帧的计算方式使其容易出现检测失败的情况(运动过快时会出现),所以一般在为了简单实现物理效果时使用刚体。物理引擎消耗较大,所以一般都是游戏主要对象设为刚体,而场景和道具使用碰撞体。碰撞器面数越少性能越好。
另外,运动较快时可以使用Physics来处理,物理引擎检测频率与FixUpdate一致。
本文碰撞检测部分内容参考文章《论Collision Detection的作用》,感谢分享。
更多内容请查看总目录【Unity】Unity学习笔记目录整理