
这是系列文章中的第五篇,解读了您在 Unity 项目中的优化技巧。将其作为在更少资源下以更高帧率运行的指南。一旦您尝试了这些最佳实践,请务必查看系列中的其他页面:
物理可以创造复杂的游戏玩法,但这会带来性能成本。一旦您了解这些成本,您可以调整模拟以适当地管理它们。使用这些技巧保持在目标帧率内,并利用 Unity 内置的物理引擎(NVIDIA PhysX)创建流畅的播放效果。
查看我们为 Unity 6 开发者和艺术家提供的最新优化指南:
用于物理的网格经过称为烹饪的过程。这准备了网格,以便它可以与物理查询(如射线检测、接触等)一起工作。
MeshCollider有几个CookingOptions来帮助您验证物理网格。如果您确定您的网格不需要这些检查,可以禁用它们以加快烹饪时间。
在每个MeshCollider的CookingOptions中,只需取消选中EnableMeshCleaning、WeldColocatedVertices和CookForFasterSimulation。这些选项对于在运行时程序生成的网格非常有价值,但如果您的网格已经具有适当的三角形,则可以禁用它们。
此外,如果您针对PC,请确保保持Use Fast Midphase启用。这在模拟的中期阶段切换到PhysX 4.1中的更快算法(有助于缩小一小组可能相交的三角形以进行物理查询)。
在CookingOptions文档中了解更多。

如果您在游戏过程中以程序方式生成网格,可以在运行时创建Mesh Collider。然而,直接将MeshCollider组件添加到网格会在主线程上烹饪/烘焙物理。这可能会消耗大量CPU时间。
使用 Physics.BakeMesh 准备网格以供 MeshCollider 使用,并将烘焙数据与网格本身一起保存。引用此网格的新 MeshCollider 将重用此预烘焙数据(而不是再次烘焙网格)。这可以帮助减少场景加载时间或后续实例化时间。
为了优化性能,您可以使用 C# 作业系统 将网格烹饪卸载到另一个线程。
有关如何在多个线程中烘焙网格的详细信息,请参阅 此示例。

在 Player Settings 中,尽可能检查 预烘焙碰撞网格。我们还建议检查 碰撞矩阵 设置,以确保玩家和游戏机制对象位于正确的层中。
从不必要的层中移除触发器的回调可以带来很大的收益,因此请尝试简化您的 层碰撞矩阵。您可以通过 项目设置 > 物理 编辑您的 物理设置。
在 碰撞矩阵文档 中了解更多信息。

物理引擎通过在固定时间步长上运行来工作。要查看您的项目运行的固定速率,请转到 编辑 > 项目设置 > 时间。
固定时间步长 字段定义每个物理步骤使用的时间增量。例如,默认值 0.02 秒(20 毫秒)相当于 50 fps 或 50 Hz。
由于 Unity 中的每一帧所需的时间是可变的,因此它与物理模拟并不完全同步。引擎计数到下一个物理时间步长。如果一个帧运行得稍微慢一点或快一点,Unity会使用经过的时间来知道何时以适当的时间步长运行物理模拟。
如果一个帧准备时间过长,这可能会导致性能问题。例如,如果你的游戏经历了一个高峰(例如,实例化许多GameObjects或从磁盘加载文件),该帧可能需要40毫秒或更长时间才能运行。在默认的20毫秒固定时间步长下,这将导致在下一个帧上运行两个物理模拟,以便“赶上”可变时间步长。
额外的物理模拟反过来又增加了处理帧所需的时间。在低端平台上,这可能导致性能的恶性循环。
后续帧准备时间更长也使得物理模拟的积压时间更长。这导致帧变得更慢,每帧需要运行更多的模拟。结果是性能越来越差。
最终,物理更新之间的时间可能超过最大允许时间步长。在这个截止点之后,Unity开始丢弃物理更新,游戏会出现卡顿。
为了避免物理性能问题:
如果必要,在帧的更新阶段选择模拟模式手动模拟物理步骤。这使您能够控制何时运行物理步骤。将 Time.deltaTime 传递给 Physics.Simulate,以保持物理与仿真时间同步。这种方法可能会导致在具有复杂物理或高度可变帧时间的场景中物理仿真不稳定,因此请谨慎使用。
在 Physics.Simulate 文档 中了解更多信息。


如果您想更准确地模拟特定物理体,请增加其 Rigidbody.solverIterations。
这会覆盖 Physics.defaultSolverIterations,您也可以在 编辑 > 项目设置 > 物理 > 默认求解器迭代 中找到。
要优化您的物理仿真,请在项目的 defaultSolveIterations 中设置相对较低的值。然后将更高的自定义 Rigidbody.solverIterations 值应用于需要更多细节的单个实例。
获取有关 Rigidbody.solverIterations 的更多信息。

默认情况下,Unity不会自动将变换的更改与物理引擎同步。相反,它会等待下一个物理更新,或者直到您手动调用Physics.SyncTransforms。启用此选项时,任何在该Transform或其子对象上的Rigidbody或Collider会自动与物理引擎同步。
何时手动同步
当autoSyncTransforms被禁用时,Unity仅在FixedUpdate的物理仿真步骤之前或在通过Physics.Simulate显式请求时同步变换。如果您使用在变换更改和物理更新之间直接从物理引擎读取的API,您可能需要执行额外的同步。示例包括访问Rigidbody.position或执行Physics.Raycast。
性能最佳实践
尽管autoSyncTransforms确保物理查询是最新的,但它会带来性能成本。每个与物理相关的API调用都会强制同步,这可能会降低性能,尤其是在多次连续查询时。遵循以下最佳实践:
了解更多关于Physics.SyncTransforms的信息。

接触数组通常显著更快,因此一般建议使用这些而不是重用碰撞回调,但如果您确实有特定的用例,请考虑以下内容。
回调MonoBehaviour.OnCollisionEnter、MonoBehaviour.OnCollisionStay和MonoBehaviour.OnCollisionExit都将碰撞实例作为参数。此碰撞实例分配在托管堆上,必须进行垃圾回收。
为了减少生成的垃圾量,请启用Physics.reuseCollisionCallbacks(也可以在项目设置 > 物理 > 重用碰撞回调中找到)。启用此功能后,Unity仅为每个回调分配一个碰撞对实例。这减少了垃圾回收器的浪费并提高了性能。
一般建议始终启用重用碰撞回调以获得性能收益。您只应在代码依赖于单个碰撞类实例的遗留项目中禁用此功能,这使得存储单个字段变得不切实际。
了解更多关于Physics.reuseCollisionCallbacks的信息。

静态碰撞体是具有碰撞器组件但没有刚体的游戏对象。
请注意,您可以移动静态碰撞体,这与“静态”一词相反。要做到这一点,只需修改物理体的位置。累积位置变化并在物理更新之前同步。您不需要为静态碰撞体添加刚体组件以便移动它。
但是,如果您希望静态碰撞体以更复杂的方式与其他物理体交互,请为其提供运动刚体。使用Rigidbody.position和Rigidbody.rotation来移动它,而不是访问变换组件。这保证了物理引擎更可预测的行为。
注意:如果需要在运行时移动或重新配置单个静态碰撞体 2D,则添加一个刚体 2D 组件并将其设置为 静态 体类型,因为当碰撞体 2D 拥有自己的刚体 2D 时,模拟速度更快。如果需要在运行时移动或重新配置一组碰撞体 2D,最好将它们全部作为单个隐藏父刚体 2D 的子对象,而不是单独移动每个游戏对象。
获取有关 刚体 的更多信息。
要在 3D 项目中检测和收集一定距离和特定方向上的碰撞体,请使用射线投射和其他物理查询,如 盒子投射。请注意,
返回多个碰撞体作为数组的物理查询,如 重叠球 或 重叠盒,需要在托管堆上分配这些对象。这意味着垃圾收集器最终需要收集分配的对象,如果在错误的时间发生,可能会降低性能。
为了减少这种开销,请使用这些查询的 非分配 版本。例如,如果您使用重叠球来收集某个点周围的所有潜在碰撞体,请改用 重叠球非分配。
这允许您传入一个碰撞体数组(结果参数)作为缓冲区。非分配方法在不生成垃圾的情况下工作。否则,它的功能与相应的分配方法相同。
请注意,在使用非分配方法时,您需要定义一个足够大小的结果缓冲区。如果缓冲区用完空间,则不会增长。
2D 物理
请注意,上述建议不适用于 2D 物理查询,因为在 Unity 的 2D 物理系统中,方法没有 "非分配" 后缀。相反,所有 2D 物理方法,包括返回多个结果的方法,都提供接受数组或列表的重载。例如,虽然 3D 物理系统有像 RaycastNonAlloc 这样的函数,但 2D 等效函数仅使用可以接受数组或 List 作为参数的 Raycast 的重载版本,如:
var results = new List();
int hitCount = Physics2D.Raycast(origin, direction, contactFilter, results);
通过使用重载,您可以在 2D 物理系统中执行非分配查询,而无需专门的非分配方法。
在NonAlloc方法文档中了解更多信息。
您可以使用Physics.Raycast运行射线查询。然而,如果您有大量的射线操作(例如,为10,000个代理计算视线),这可能会占用大量的CPU时间。
使用RaycastCommand通过C#作业系统批处理查询。这将工作从主线程卸载,以便射线可以异步并行发生。
在RaycastCommands文档中查看示例。
使用物理调试窗口(窗口 > 分析 > 物理调试器)帮助排除任何问题碰撞体或差异。这显示了可以相互碰撞的游戏对象的颜色编码指示器。
有关更多信息,请参见物理调试器文档。


从Unity最佳实践中心找到更多最佳实践和技巧。从超过30个指南中选择,这些指南由行业专家、Unity工程师和技术艺术家创建,将帮助您高效地使用Unity的工具集和系统进行开发。