本文是为您的 Unity 项目介绍优化技巧的系列文章的第五篇。将它们作为指南,以更少的资源运行更高的帧速率。尝试过这些最佳实践后,请务必查看本系列的其他页面:
物理原理可以创造复杂的游戏玩法,但这需要付出性能代价。了解这些成本后,您就可以调整模拟,对其进行适当管理。使用这些技巧可保持目标帧频,并利用 Unity 内置的物理特性 (NVIDIA PhysX) 创建流畅的播放。
物理学中使用的网格需要经过一个名为 "烹饪"的过程。这将为网格做好准备,使其能够处理射线投射、接触等物理查询。
网格对撞机有多个CookingOptions(烹饪选项),可帮助您验证网格的物理特性。如果您确定您的网格不需要这些检查,您可以禁用它们来加快烹饪时间。
在每个网格对撞机的CookingOptions(烹饪选项)中,取消选中EnableMeshCleaning(启用网格清洁)、WeldColocatedVertices(焊接定位顶点)和CookForFasterSimulation(烹饪更快的仿真)。这些选项对运行时程序生成的网格非常有用,但如果网格已经有了适当的三角形,则可以禁用这些选项。
如果您专门针对 PC,请确保启用 "使用快速中相"。在模拟的中期阶段,它会切换到 PhysX 4.1 中更快的算法(这有助于缩小一小部分可能相交三角形的范围,以便进行物理查询)。非桌面平台仍必须使用较慢的算法来生成 R 树.
网格对撞器的成本通常较高,因此可以考虑用原始或简化的网格对撞器来替代更复杂的网格对撞器,以逼近原始形状。
更多信息请参阅CookingOptions 文档。
如果在游戏过程中按程序生成网格,则可以在运行时创建网格碰撞器。然而,直接在网格上添加网格对撞器组件会在主线程上进行物理处理。这会消耗大量的 CPU 时间。
使用Physics.BakeMesh为网格对撞机准备网格,并将烘焙数据与网格本身一起保存。引用此网格的新网格对撞器将重复使用这些预烘焙数据,而不是再次烘焙网格。这有助于减少场景加载时间或稍后的实例化时间。为了优化性能,您可以使用 C# 工作系统.
有关如何跨多个线程烘焙网格的详细信息,请参阅此示例。
在"播放器 设置"中,尽可能选中 "预烘烤碰撞网格"。我们还建议查看 "碰撞矩阵"设置,以确保玩家和游戏机械对象位于正确的图层中。
从触发器中移除不必要层的回调会带来很大的好处,因此请尽量简化层碰撞矩阵。您可以通过 "项目设置">"物理"编辑 "物理 设置"。
更多信息,请参阅碰撞矩阵文档。
物理引擎以固定的时间步运行。要查看项目运行的固定速率,请转至编辑>项目设置>时间。
固定时间步长 "字段定义了每个物理步长所使用的时间差。例如,默认值 0.02 秒(20 毫秒)相当于每秒 50 帧(fps)或 50 赫兹。
由于 Unity 中每一帧所需的时间都不尽相同,因此它与物理模拟并不完全同步。引擎倒计时到下一个物理计时器。如果某帧的运行速度稍慢或稍快,Unity 会使用经过的时间来确定何时以适当的 Timestep 运行物理模拟。
如果帧的准备时间过长,可能会导致性能问题。因此,如果您的游戏出现峰值(例如,实例化许多游戏对象或从磁盘加载文件),运行一帧可能需要 40 毫秒或更长的时间。如果使用默认的 20 毫秒 "固定时间步",这将导致在下一帧中运行两次物理模拟,以 "赶上 "可变的 "时间步"。
额外的物理模拟反过来又增加了处理帧的时间。在低端平台上,这可能会导致性能下降。
后续帧的准备时间更长,也会使物理模拟的积压时间更长。这导致帧速度更慢,每帧需要运行更多的模拟。其结果是削弱了性能。
最终,物理更新的间隔时间可能会超过最大允许时间步长。过了这个截止时间,Unity 就会开始放弃物理更新,游戏也会出现卡顿。
为了避免物理性能问题:
- 降低模拟频率:对于低端平台,将 "固定的时间步长"(Fixed Timestep)调至略高于目标帧频。例如,在移动设备上使用 0.035 秒表示 30 fps。这有助于防止绩效螺旋式下降。
- 降低最大允许时间步长:使用较小的数值(如 0.1 秒)会牺牲一些物理模拟的准确性,但同时也会限制一帧中物理更新的次数。尝试不同的值,找到适合项目要求的值。
- 必要时手动模拟物理步骤:禁用 "物理设置 "中的 "自动模拟 "选项,并在帧的更新阶段直接调用Physics.Simulate。这样就可以主动决定何时运行物理步骤。将Time.deltaTime传递给Physics.Simulate,以保持物理效果与模拟时间同步。
- 注意:这种方法可能会导致物理模拟不稳定,尤其是在具有复杂物理特性或帧时间变化很大的场景中。谨慎使用。
更多信息,请参阅Physics.Simulate 文档。
如果想更精确地模拟特定物理体,请增加其Rigidbody.solverIterations。这将覆盖Physics.defaultSolverIterations,可通过 "编辑">"项目设置">"物理">"默认求解器迭代 "找到。
为了优化物理模拟,请在项目的默认求解迭代中设置一个相对较低的值。然后对需要更多细节的单个实例应用更高的自定义Rigidbody.solverIterations值。
获取有关Rigidbody.solverIterations 的更多信息。
更新 "变换 "时,Unity 不会自动将其同步到物理引擎。Unity 会累积变换,然后等待执行物理更新或用户调用 物理同步变换.
如果想更频繁地同步物理和变换,可以设置 Physics.autoSyncTransform设置为True(也可在 "项目设置">"物理">"自动同步变换"中找到)。启用此功能后,该变换上的任何刚体或碰撞 体及其子实体都会随该变换自动更新。
不过,如果不是绝对必要,请禁用它。一系列连续的物理查询(如射线发射)会导致性能下降。
了解有关Physics.SyncTransforms 的更多信息。
碰撞和触发事件 (碰撞和触发事件, 碰撞停留时, 碰撞结束时, 触发时进入, 触发时停留, 触发时退出) 会触发任何实现了这些函数并满足交互标准的MonoBehaviour。这些事件也将发送给禁用的 MonoBehaviours。
这就是为什么建议只在需要时执行这些函数,因为非必要的空函数会被调用。在使用 OnCollisionStay 和 OnTriggerStay 时必须特别小心,因为根据所涉及碰撞器的数量,这两个函数可能会被多次调用。
回调MonoBehaviour.OnCollisionEnter, MonoBehaviour.OnCollisionStay和 也会将碰撞实例作为参数。也将碰撞实例作为参数。该碰撞实例在托管堆上分配,必须进行垃圾回收。
要减少产生的垃圾量,请启用 Physics.reuseCollisionCallbacks(在 "项目设置">"物理">"重复使用碰撞回调"中找到)。激活后,Unity 只为每个回调分配一个碰撞对实例。这减少了垃圾收集器 (GC) 的浪费,提高了整体性能。
注意:如果在碰撞回调之外引用碰撞实例进行后处理,则必须禁用 "重复使用碰撞回调"(Reuse Collision Callbacks)。
了解有关Physics.reuseCollisionCallbacks 的更多信息。
静态对撞机是具有对撞机组件但没有刚体的游戏对象。与静态对撞机的名称相反,它可以随意移动。
只需修改物理体的位置,累积位置变化,然后在物理更新前同步即可。但如果你想让静态对撞机以更复杂的方式与其他物理体交互,可以给它一个 运动刚体.使用 Rigidbody.position和 旋转来移动它,而不是访问变换组件。这保证了物理引擎的行为更具可预测性。
注意:在二维物理中,不要移动静态碰撞器,因为树的重建非常耗时。
了解有关刚性体的更多信息。
要检测和收集特定距离、特定方向上的对撞机,可使用射线播报和其他物理查询,如 盒播.
以数组形式返回多个对撞机的物理查询,如 重叠球或 重叠框等物理查询,需要在托管堆上分配这些对象。这意味着垃圾回收器最终需要回收已分配的对象,如果在错误的时间进行回收,可能会降低性能。
要减少这种开销,请使用这些查询的NonAlloc版本。例如,如果使用 OverlapSphere 来收集点周围的所有潜在碰撞器,请确保使用 OverlapSphereNonAlloc.这样就可以传入一个碰撞器数组(resultsparameter)来充当缓冲器。
NonAlloc 方法不会产生垃圾。否则,其功能与相应的分配方法相同。使用 NonAlloc 方法时,请记住定义一个足够大的结果缓冲区。如果空间用完,缓冲区不会增长。
更多信息,请参阅NonAlloc 方法文档。
虽然可以使用 Physics.Raycast来运行射线播报查询,但这会耗费大量的 CPU 时间。如果要进行大量的光线投射操作(例如,为 10,000 个代理计算视线),情况尤其如此。
使用 RaycastCommand来使用 C# 作业系统批处理查询。这就卸载了主线程的工作,从而可以异步并行地进行射线广播。
请参阅RaycastCommands 文档中的示例。
使用 "物理调试"窗口("窗口">"分析">"物理调试器")帮助排查任何有问题的对撞机或差异。该窗口以彩色编码显示可相互碰撞的游戏对象。
更多信息,请参阅物理调试可视化页面。
我们有史以来最全面的指南之一,收集了 80 多条实用技巧,教你如何优化 PC 和游戏机上的游戏。这些深入的技巧由我们的专家 Success 和 Accelerate Solutions 工程师编写,将帮助您充分利用 Unity 并提高游戏性能。