Unity笔记——3.物理引擎
Unity在3D物理使用的是Nvidia的PhysX,2D物理使用的是开源项目Box2D,虽然3D和2D项目使用了不同的物理引擎,但是Unity在实现上对它们进行了高度抽象,即从Unity引擎配置的更高级别Unity API来看两个物理引擎解决方案以功能相同的方式运行。
物理和时间
无论哪个物理引擎都是在时间按固定值前进的前提下运行的
固定更新时间步长
物理引擎使用特定的时间值来处理每个时间步长,与渲染上一帧用的时间无关,该时间步长在unity中被称为Fixed Update TimeStep,默认设置为0.02秒/20毫秒,即每秒进行50次的固定更新和物理引擎更新
由于每台客户端体系结构的不同以及客户端之间的延迟关系,如果物理引擎使用可变的时间步长,就很难在两台不同的计算机上产生一致的碰撞和力的结果
固定更新和物理引擎更新
由上图可以看出物理引擎内部的具体执行顺序,当经过了距离上次固定更新后足够固定时间步长的时间,就开始该次固定更新和物理引擎更新,执行顺序如下:
- 固定更新的处理将调用在场景中所有激活的MonoBehaviour类中定义的FixedUpdate()回调
- 内部动画更新
- 内部物理更新,并调用任何需要触发的触发器和碰撞器回调
- 处理与固定更新相关的协程WaitForFixedUpdate()
当距离上次固定更新后经过不足一个固定时间步长的时间,则会跳过该次固定更新和物理引擎更新
最大允许的时间步长
假设我们在Project Settings中设置固定时间步长为0.01秒/10毫秒,即每秒进行100次固定更新和物理引擎更新
当我们的游戏以60fps运行时,每帧大约0.01666秒/16.67毫秒,由于我们设置的固定时间步长为0.01秒/10毫秒,可以知道此时进行一次固定更新和物理引擎更新大概10毫秒,即此时每帧画面都调用一次固定更新和物理引擎更新
当我们的游戏以30fps运行时,每帧大约0.03333秒/33.33毫秒,由于我们设置的固定时间步长0.01秒/10毫秒,可以知道此时,进行一次固定更新和物理引擎更新大概10毫秒,即此时每帧画面要调用三次的固定更新和物理引擎更新
当我们的游戏以20fps运行时,每帧大约0.05秒/50毫秒,由于我们设置的固定时间步长0.01秒/10毫秒,可以知道此时,进行一次固定更新和物理引擎更新大概10毫秒,即此时每帧画面要调用五次的固定更新和物理引擎更新
当帧率继续降低时,每一帧需要的固定更新和物理引擎更新次数将会越来越多
因此,我们需要最大允许的时间步长Maximum Allowed Timestep,一般设置为0.3333秒/333.33毫秒,即每帧渲染超过333.33毫秒之后不再进行固定更新和物理引擎更新,来为其他的处理省下时间
一般情况下FixedUpdate()回调会有很多时间来完成工作,并且物理引擎的工作也很少,但是某些游戏中,物理引擎需要在更新期间执行大量的计算,从而造成瓶颈,由于物理引擎需要在完全处理一次更新之前尽早退出时间步长,因此刚体的运动会突然减速或者停止;同时渲染管线在帧结束之前会几乎没有时间来生成当前画面,从而导致画面停顿
动态碰撞器和静态碰撞器
动态碰撞器
动态碰撞器指GameObject上包含Collider组件和Rigidbody组件的碰撞器,将Rigidbody组件添加到Collider所附加的对象上时,物理引擎就会将该碰撞器视为带有包围物理对象的立体,从而使它会对外部的力(例如重力)以及其他Rigidbody的碰撞作出反应
静态碰撞器
静态碰撞器指GameObject只包含Collider组件的碰撞器,这种碰撞器可以作为无影屏障或者其他不能移动的障碍物,动态碰撞器可以碰到静态碰撞器,但是静态碰撞器并不会作出反应
物理引擎会为动态碰撞器和静态碰撞器生成两个单独的数据结构,对于所有的静态碰撞器会进行似于静态批处理的处理,如果在运行时将新对象加入静态碰撞器的数据结构,则需要重新生成它们,会导致显著的CPU峰值,所以在游戏运行时要避免去实例化静态碰撞器,如果要在游戏中实现一个运动的静态碰撞器,正确做法应该是为该碰撞器Collider添加一个Rigidbody组件,开启Rigidbody组件的IsKinematic属性,IsKinematic标识符用来防止对对象间的碰撞作出反应,就像静态碰撞器一样,但是,添加了Rigidbody组件并开启IsKinemetic属性的碰撞器则可以通过移动其Transform组件或者施加在其Rigidbody上力的作用下进行移动,此时如果碰到其他动态碰撞器,该碰撞器的运动并不会收到影响,但是其他的动态碰撞器则会对碰撞作出反应
触发体积
碰撞器组件包含一个IsTrigger属性,勾选后它们将被视为非物理对象,称之为触发体积,当其他碰撞器进入、保持或者离开它们时可以调用相应的物理事件。
-
当一个碰撞器接触、保持接触、停止接触时,会分别调用另一个碰撞器的OnCollisionEnter()、OnCollisionStay()和OnCollisionExit()回调;当接触的是触发体积时,会分别调用触发体积的OnTriggerEnter()、OnTriggerStay()和OnTriggerExit()回调
-
碰撞器的OnCollisionXXX()回调与触发体积的OnTriggerXXX()的回调,区别是OnCollisionXXX()回调提供一个Collision对象最为回调参数,其中包括精确的碰撞位置、接触法线等信息,而OnTriggerXXX()类的回调则没有这些信息,因此,触发体积不应该用于对碰撞做出反应,而适合用于一些触发某一功能的场景
碰撞检测
Unity中的碰撞检测有三种设置,可以在Rigidbody组件中的Collision Detection属性进行设置,离散Discrete、连续Continuous、连续动态ContinuousDynamic
- 离散Discrete
根据物体的速度,每个时间步长物体移动一小段距离,物体移动后物理引擎会检查物体之间是否有重叠部分并对重叠部分进行边界立体检查,经过检查如果它们被视为碰撞,之后就会根据物体的物理属性以及重叠方式来处理它们的碰撞。离散式碰撞检测在小对象物体移动速度较快的情况下会有丢失碰撞的风险
- 连续Continuous
连续碰撞检测和连续动态碰撞检测是在时间步长的开始位置和结束位置之间检查是否有重叠碰撞,因为碰撞有可能发生在时间步长的过程中,多以连续的碰撞检测降低了丢失碰撞的风险,但是也提高了CPU的开销。连续式碰撞检测仅在给定碰撞器与静态碰撞器之间启用
- 连续动态ContinuousDynamics
连续动态碰撞检测使碰撞器与所有静态碰撞器和动态碰撞器之间进行连续碰撞检测,这种碰撞检测资源消耗最高
射线投射
射线投射指将射线从一个点投射到另一个点,与路径中的一个或者多个对象发生碰撞信息
物理性能优化
场景设置
- 尽可能使游戏中的所有物理物体的缩放接近(1, 1, 1),地球表面重力为9.8m/s2,因此Unity世界空间中1个单位等于1米,中默认重力为-9.81,负号意味着会把物体向下拉;
- 保持所有对象在世界空间的位置接近(0, 0, 0),这样会具有更好的浮点数精度;
- 物理物体的质量保存在刚体组件下的质量属性中,我们可以自由的选择使用1.0为基准,然后对其他物体进行缩放
优化碰撞矩阵
物理引擎的碰撞矩阵定义了指定层的对象是否可以与另一个层的对象进行碰撞,使物理引擎可以有效的忽略其他层的对象,减少了每次固定更新需要检查的边界体积的数量,有效的减轻物理引擎的工作负载
假如游戏中有Player层对象1个、PlayerMissiles层对象7个、Powerups层对象2个、Enemy层对象10个、EnemyMissiles层对象20个,一共有40个对象,在不设置碰撞矩阵的情况下,每个对象都会与其他层的对象进行碰撞,所以一共会有40 * 39 / 2 = 780 个碰撞对,使用碰撞矩阵之后(按按行对每个层对于其他层的碰撞对,如果在上一行已经处理了当前两个碰撞对层,则当前行就不用再次勾选了)就可以将碰撞对的数量降低至不到100个,可有效释放一些CPU周期
REF
文档
https://docs.unity3d.com/Manual/ExecutionOrder.html
书籍
Unity游戏优化