由于物理引擎是一类独特的子系统,其行为和一直性是影响产品质量的主要因素,因此花时间改进其行为通常是值得的;
如果错过了重要的碰撞事件,游戏在计算的复杂物理事件时卡顿,或者玩家摔倒在地上,这些都将会对游戏质量产生明显的负面影响;
下面将介绍减少物理迎请中的CPU峰值、开销和内存消耗的方式,在提高或者至少保持游戏质量的同时优化性能;
将以如下的顺序介绍;
理解Unity的物理引擎时如何工作
- 时间步长和FixedUpdate
- 碰撞器类型
- 碰撞
- 射线发射
- 刚体激活状态
物理引擎性能优化
- 如何以优化物理行为构建场景
- 使用相应的碰撞器类型
- 优化碰撞矩阵
- 在避免容易出错的行为前提下提升物理一致性
- 布娃娃和其他基于关节的对象
物理引擎的内部工作情况
物理和时间
物理迎请通常是在时间按照固定值前进的假设下运行的,Unity的两种物理引擎都是按照这种方式运行,而每个执行周期称之为时间不长,物理引擎只使用特定的时间值来处理每个时间不长,这与上一帧渲染所花费的时间无关;
**由于体系结构(浮点数的表达方式不同)**的不同以及客户端之间的延迟,如果物理引擎使用可变的时间步长,很难在两台不同的计算机之间产生一致的碰撞和力的结果,这样的物理引擎往往会在多人的客户端之间或者在录制的期间生成非常不一致的结果;
unity内部的执行逻辑如下
上图中可以看出来,固定更新Fixedupdate在物理引擎执行自己的更新(internal
physics update)之前处理,而这两者之间的更新联系是密不可分的,这个过程开始于确定是否已经过了足够的时间来开始下个固定的更新;
如果经过了足够的时间,则固定更新的处理将调用场景中所有已经激活的MonoBehaviour中的FixedUpdate回调,然后处理与固定更新有关的所有回调与协程,注意,对于这两个过程中调用的方法,没有固定的执行顺序,一定不要假设某个顺序,并在其之上编写逻辑,一旦这些任务完成,物理引擎九讲开始处理当前的时间步长,并且调用任何必要的触发器和碰撞器的回调,相反,如果自上次固定更新以来经过的时间太少(即少于20s)那么就忽略当前的固定更新,并且上面所述任务不会在当前的迭代周期内进行处理,但是输入、游戏逻辑和渲染等都正常执行,因此在高帧率下,渲染更新可能会在物理引擎获得更新机会之前进行多次更新;
为了确保对象在固定更新之前平稳的移动,物理引擎会根据下次一固定更新之前的剩余时间,在处理当前状态之后,在上一个状态与当前状态之间的每个对象的可见位置上进行插值,这样的插值操作会使对象移动的非常平稳
最大允许的时间步长
需要格外注意的使,如果从上次固定更新(例如游戏卡顿)以来,已经过了很长的时间,那么固定更新会在相同的循环中计算,一直到物理引擎的更新赶上当前的进度,比如果上一帧花费了100ms进行渲染,那么物理引擎需要进行五次更新,但是由于默认固定更新的步长为20s,所以在再次调用update之前还需要进行五次fixedUpdate, 但如果某次FixedUpdate所花费的时间超过了20s,那么物理引擎的更新将进行第六次;
因此,当物理活动比较多的时候,物理引擎处理固定更新的时间可能比模拟的时间要长,例如,如果用20ms的时间去处理一个固定更新,模拟20ms的游戏,那么他就已经落后很多了,需要她处理更多的时间步长来尝试跟上,但是这又可能会导致它落后的更远,需要处理更多的时间步长,这种问题通常称之为死亡螺旋,因此为了防止物理引擎在某一个时刻锁定游戏,可以设置物理引擎处理每个固定循环的最长时间,这个阙值称之为时间允许的最大时间不长,如果当前一批固定更新的处理时间太长,则它将停止并放弃进一步的处理,直到下一次渲染完成,这种设计,允许渲染管线至少将当前状态进行渲染,并且允许用户输入以及游戏逻辑在物理引擎出现异常的时刻做一些处理;在edit->project settings ->times->Maximum allowed timestep 中设置
物理更新和运行时变化
当从逻辑上将,在任何给定的固定更新迭代中花费的时间越多,在下一次游戏逻辑和渲染过程中的时间就越少,在某些游戏中,物理引擎可能会在每次固定更新期间执行大量的计算,这种物理处理时间上的频率会影响帧率,导致当物理引擎负担越来越大时候帧率急剧下降,,正常情况下,渲染管线都会去尝试正常进行,但是当需要进行固定更新时(物理引擎处理的时间很长)渲染管线在帧结束之前几乎没有时间去生成画面,导致突然的卡顿,这个时候就会产生某一帧没有画面生成,因此,为了保证平滑、一致的帧率,需要通过最小化物理引擎的处理给定时间不长所需要的时间,或者限制其最大的处理时间;
静态碰撞器和动态碰撞器
动态碰撞器意味着Gameobject包含着Collider、Rigidbody,他会对外部的力,rigibody的碰撞产生李得效果
静态碰撞器就是没有附加rigibody的碰撞器,只起到阻碍的作用,自身不会产生力的作用;
碰撞检测
三种碰撞检测类型
- Discrete 离散
- continuous 连续
- continuous dynamic 连续动态
Discrete(离散检测):当物体这一帧还在前面,下一帧就到后面去了,就检测不到,不适用于高速运动的物体
Continuous(连续检测):防止对象穿过所有静态碰撞体 ,代价显著的高于前一个碰撞检测类型
Continuous Dynamic(动态连续检测):防止对象穿过所有静态碰撞体以及设置为Continuous或Continuous Dynamic的刚体,资源消耗最昂贵
碰撞器类型
一共有四种碰撞体类型,分别时球体、胶囊体、立方体、网格碰撞器,前三个时基础类型,通常可以通过不同方向的缩放来满足要求;
还有三种类型的二维碰撞体:圆、方框、多边形;
另外两种不同的网格碰撞器:Convex(凸的)、Concave(凹的),这两种网格都使用相同的组件MeshCollider,这种网格碰撞器是使用Convex复选框切换生成的,物理引擎会自动简化该网格碰撞器,当简化的时候,物理引擎会尝试生成一个碰撞器,该碰撞器与附加的网格形状匹配,上限为255个顶点;
碰撞矩阵
物理引擎具有一个碰撞矩阵,该矩阵定义了那些对象可以与那些对象发生碰撞
在Edit ->project settings ->jysics/physics2D ->Layer Collision中进行设置
Rigibody 激活和休眠状态
当Rigibody处于休眠状态的时候,在固定更新的过程中,几乎没有任何处理器时间来更新对象,一直到它被外力或者碰撞事件唤醒
如果这个值设置的过大,那么当用户想要让物体进行缓慢移动的时候,如果小于这个阙值,直接进入休眠状态,换句话说就是不动了,如果这个值设置的过小,那么想要保持物体处于休眠状态基本是不能的,而且还要花费每次固定更新的少量处理成本去进行处理;
打开方式 :Editor->project setting-> physics ->sleep threshold
射线和对象投射
物理引擎的另一个常见的特征就是能够将一个涉嫌从一个点投射到另一个点,并且可以获取路径中的一个或多个对象生成的碰撞信息,这就是所谓的射线投射,应用场景如射击;
还可以通过Physics.OverlapSphere 检查空间中固定点的优先距离内获得目标列表,这个函数通常可以应用于效果区域的游戏功能,如手榴弹、火球爆炸;
还有Physics.sphereCast()\physics.CapsuleCast 在空间中向前投射整个对象,这两个方法通常用来模拟激光束,或者确定角色是否在NPC可以检测的光束中出现,并作出相应的逻辑执行;
调试物理
关于物理的碰撞有两类问题
- 本不该碰撞的一对2对象碰撞了
- 本应该碰撞的对象没有碰撞
解决方案
- 确定那个碰撞对象导致了问题、
- 在解决问题之前确定碰撞条件
- 重现碰撞
unity中有一个帮助调试物理问题的工具时Physics Debugger,可以通过Window->Physics Debugger 打开,这个工具可以帮助从scene窗口中过滤出不同类型的碰撞器,从而更加清楚的直到那些对象互相碰撞
个人记录学习 ,借鉴unity游戏优化