5.2 物理优化
5.2.1 场景设置
1.缩放
应该尽可能地使游戏世界中所有物理物体的缩放接近(1,1,1)。
Unity世界空间中的1个单位等于1米。
物体大小应该反映有效的世界尺度,因为缩放过大会导致重力移动物体的速度比预期的要慢得多。
重力参数设置:
任何浮点运算再数值接近0时都会更精确。
再项目早期,应该导入与缩放最常见的物理对象,使其比例值为(1,1,1),然后调整合适的重力值。这将再引入新对象时提供一个参考点。
2.位置
保持所有对象在世界空间的位置接近(0,0,0),将具有更好的浮点数精度,提高模拟的一致性。
3.质量
质量以浮点值的形式存储在刚体组件的质量属性下。
值为1.0的质量表示1kg。
最重要的部分是质量的相对差异,这样,这些物体之间的碰撞看起来可信,而不会对引擎施加过大的压力。
理想情况下,质量值保持在1.0左右,并确保最大相对质量比在100左右。
地球中心的重力对所有物体都有同等的影响,不管它们的质量如何。
落下时不同空气阻力导致的差异,需要通过自定义重力来实现。
5.2.2 适当使用静态碰撞器
物理引擎自动生成两个单独的数据结构,分别包含静态碰撞器和动态碰撞器。如果在运行时将新对象引入静态碰撞器数据结构,那么必须重新生成它,这可能会导致CPU峰值。
在游戏中避免实例化新的静态碰撞器是至关重要的。
仅移动、旋转或缩放静态碰撞器也会触发此重新生成的过程,应避免。
动态碰撞器开启Kinematic标志能类似静态碰撞器,不会对对象间碰撞做出反应,但能够通过Transform或Rigidbody上的力的作用移动。它运动时会简单地把其他动态碰撞器推开,玩家角色可以这么设置。
5.2.3 恰当使用触发体积
不应该尝试使用触发体积对碰撞做出反应,因为没有足够的信息使得碰撞看起来准确。
5.2.4 优化碰撞矩阵
碰撞矩阵定义了物理引擎关心的对象碰撞对。物理引擎简单地忽略了其他每一个对象层对,这使得碰撞矩阵成为最小化物理引擎工作负载的重要途径,因为它减少了每次固定更新必须检查的边界体积的数量,以及在应用程序的生命周期中需要处理的碰撞数量。
应该对碰撞矩阵中所有潜在的层组合执行这样的逻辑健全性检查,以查看是否在浪费宝贵的时间检查不必要的对象对之间的碰撞。
5.2.5 首选离散碰撞检测
离散碰撞检测的消耗相当低,因为只传送一次对象并在附件的对象对之间执行一次重叠检查,在一个时间步长的工作量相当小。
连续碰撞检测高出一个数量级,连续动态碰撞检测又高出一个数量级。
只有在极端情况下使用连续碰撞检测设置。
5.2.6 修改固定更新频率
可以自定义物理时间步长,通过修改引擎检查固定更新的频率,为了离散碰撞检测系统提供更好的捕获此类碰撞的机会(大量小物体高速移动碰撞)。
固定更新和物理时间步长处理是强耦合的。
更改该值可能是又风险的,因为这将更改一个非常重要的假设,即调用这些方法(FixedUpdate()回调和协程)的频率。
Time.fixedDeltaTime
Time|Fixed Timestep
减少该值(增加频率)将迫使物理引擎更频繁地进行处理,从而使其更容易通过离散碰撞检测捕获碰撞。这将增加CPU成本。
每次更改固定时间步长值时执行大量测试变得非常重要。对该值的更改应该在项目生命周期的早期进行,之后少做调整。
当涉及实时事件和用户输入驱动的应用程序时,自动化测试的支持和维护成本往往高于它的价值,所以手动测试成为最明智的方法。
总是把连续的碰撞检测作为最后的手段来抵消所观察到的一些不稳定性。
5.2.7 调整允许的最大时间步长
Maximum Allowed Timestep
如果处理物理计算的时间超过允许的最大时间步长,那么将导致一些看起来很奇怪的物理行为。
阈值将防止游戏在物理处理过程的峰值中完全卡住。
仅在用尽所有其他方法的情况下调整此值。
5.2.8 最小化射线发射和边界体积检查
所有射线投射方法都非常有用,但它们相对于其他方法来说消耗较大,特别是CapsuleCast()和SphereCast()方法。
应该使用层遮罩(LayerMask)来最小化每个射线投射的处理量,如Physics.RaycastAll()。
该优化对于Physics.RaycastHit()函数来说并不是很好,因为该版本只为射线与之碰撞的第一个对象提供光线碰撞信息,而不管是否使用LayerMask。
5.2.9 避免复杂的网格碰撞器
碰撞体的性能效率:球体>胶囊体>立方体>凸网格碰撞体>凹网格碰撞体。
碰撞总是涉及成对的对象,解决碰撞所需的工作量(数学)将取决于两个对象的复杂化度。
检测两个基本物体之间的碰撞可以简化为一组相对简单的数学方程,这些方程是高度优化的。与一对凸碰撞器进行比较时一个更复杂的方程,比基本体多一个数量级。两个凹网格碰撞器之间发生碰撞非常复杂,无法简化为一个公式,需要在两个网格的每对三角形之间解决碰撞检查,消耗的数量级更高。
球体的碰撞检测是最简单的,渲染是最复杂的(完美的球体)。立方体的渲染是最简单的,碰撞检测比球体复杂。
创建最大数量物体的最有效方法是用带有球体碰撞器的立方体物体填充游戏世界。
对象的物理表示(Collider)并不一定需要于其图形表示相匹配。这是有益的,因为图形网格通常可以压缩成一个更简单的形状,仍然生成非常相似的物理行为,且不需要使用过于复杂的网格碰撞器。
图形和物理之间的这种分离表示允许优化一个系统的性能,而不(必然)对另一个系统产生负面影响。
1.使用更简单的基本体
大多数形状可以使用3个基本碰撞体的一个进行近似模拟。
实际上,不需要使用单一碰撞器代表对象。
这通常比使用单一的网格碰撞器消耗更低,应该是首选的做法。
2.使用更简单的网格碰撞体
可以为网格碰撞器的mesh属性指定一个更简单的网格。
这将大大减少确定边界体积与其他碰撞器重叠所需的开销。
简化的网格不太可能被离散碰撞检测遗漏,因此也更可取。
5.2.10 避免复杂的物理组件
某些特殊的物理碰撞器组件,如TerrainCollider、Cloth和WheelCollider,在某些情况下比所有基础碰撞器甚至网格碰撞器的消耗都要高上几个数量级。不应该在场景中包含这些组件,除非它们是绝对必要的。
5.2.11 使物理对象休眠
物理引擎的休眠特效会给游戏带来一些问题。
碰撞频率和活动物体的总累积时间更有可能以指数形式而不是线性形式增加。每次在模拟中引入新的物理对象,都会导致意外的性能成本。
在运行时修改Rigidbody组件的任何属性会重新唤醒对象。
大量互相接触的刚体的唤醒过程可能导致群岛问题。
5.2.12 修改处理器迭代次数
在物理引擎中,使用关节、弹簧和其他方法将刚体连接在一起是相当复杂的模拟。当物理链的任何一部分的速度发生变化时,需要使用这种多迭代方法来计算精确的结果。
处理器允许尝试的最大迭代次数称为Solver Iteration Count
这个是默认值,可以修改物体的Rigidbody.solverIterations修改。
如果发现游戏中使用复杂的基于关节的对象(如碎布娃娃)经常遇到不稳定、违反物理规则的情况,那么应该考虑逐渐增加该值,直到问题被控制。
还有个Default Solver Velocity Iterations。
5.2.13 优化布娃娃
1.减少关节和碰撞器
创建布娃娃需要13个碰撞器
可以只使用7个(骨盆、胸部、头部和每个肢体一个)可以大大降低消耗,代价是牺牲了布娃娃的真实性。
注意布娃娃的质量值与游戏中的其他对象相比,不会太高或太低。
2.避免布娃娃间碰撞
通过层和碰撞矩阵避免布娃娃之间碰撞。
3.更换、禁用或移除不活跃的布娃娃。
在某些游戏中,一旦布娃娃到达它的最终目的地,就不再需要它作为一个可交互的对象留在游戏世界中。让后当不再需要布娃娃时,可以禁用、销毁它,或用更简单的替代品替换他。
无论是通过实例化更少的布娃娃,给它们更少的复杂性,还是给它们更短的寿命,但这些都是提升性能的合理折中。
5.2.14 确定何时使用物理
提高特效性能最明显的方法是尽量避免使用它。对于游戏中所有可移动的物体,应该花点时间问问自己,是否有必要使用物理引擎。如果没有,应该寻找机会用更简单、消耗更低的东西取代它们。
可以通过简化情况,并将工作放到脚本代码中来减少物理开销。
反过来也是可能的。在某些情况下,可能会通过脚本代码执行大量计算,而脚本代码可以相对简单地通过物理进行处理。
需要具体比较。