携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第10天,点击查看活动详情
Physics设置优化
关闭 Auto Sync Transforms
当Transform组件改变时,该选项会自动将Transform同步到物理系统,这将会带来更大的消耗。默认情况,该选项是关闭的。关闭该选项时,物理系统会在内部记录Transform的变化,然后在需要的时候进行同步。如果没有足够的理由,不需要打开该选项。
打开 Reuse Collision Callbacks
当碰撞发生时,Unity会调用碰撞回调函数,例如void OnCollisionEnter(Collision collision),彼此碰撞的一对碰撞体都会调用回调函数,并且回调函数会传入Collision参数。由于这个Collision对象是在堆上分配的,因此使用完之后会被GC,这就带来了GC的性能消耗。如果开启 Resue Collision Callbacks,则每对碰撞体的回调函数会共享同一个Collision对象,这减少了GC消耗。除非是需要保存Collision对象的引用然后在之后再使用它才需要关闭此选项。
简化 Layer Collision Matrix
Collision Matrix定义了不同的Layer之间是否会产生物理碰撞,划分合适的层并且仔细定义他们之间的碰撞关系,可以减少不必要的碰撞计算。
根据情况设置Broadphase Type
默认情况下,Unity使用 Sweep And Prune Broadphase,这会沿着一个轴排序物体然后检查每对物体是否分离。当在一个平坦的世界中有大量物体时,这个方法就显得效率较低,因为它会产生很多的False positive即错误的正向判断,这会增加后面的Narrow phase的压力。此时建议将Broadphase Type设置为 Automatic Box Pruning,这个选项会使用网格来将物体分组,在网格内部仍然是使用sweep-and-prune,并且该选项会自动计算世界边界和网格划分的数量。另外一个选项Multibox Pruning Broadphase就是手动挡了,需要自己设置世界边界尺寸和网格划分。
简化碰撞体
尽量使用原子碰撞体或者更简单的mesh collider的组合来代替复杂的mesh collider。
使用物理的方法移动刚体而不是直接移动Transform
对于 Rigidbody, 使用MovePosition或者AddForce来移动他们是更有效率的。如果直接移动Transform会导致物理世界重新迭代,这在复杂的场景中是相当昂贵的。另外最好在FixedUpdate中移动刚体,而不是Update中。
使用可移动的3D Static Colliders
如果一个物体有碰撞,且需要移动,其实不必使用刚体。在Unity5之前的版本,是不推荐移动没有rigidbody的collider物体的,因为这需要重新生成broad phase所需要的空间树结构。而Unity5之后,已经优化了3D物理的空间树重建,所以可以放心的直接移动静态Colliders。但是对于2D物理,仍然不推荐移动静态collider因为重建树仍然比较耗时。总之,对于3D物理,如果你只是想移动一个可碰撞的物体,而不需要刚体的特性,那么就可以不使用刚体,直接移动Transform。另外,如果有更复杂的需求,还可以使用kinematiced rigidbody。
针对单个物体设置Solver Iterations
当我们需要更精确的物理模拟时,往往可以通过缩短Fixed Timestemp来增加物理系统的迭代次数。但是这是全局的,会有更大的消耗。而往往我们并不需要所有的物理对象都使用更多的迭代次数,其实是可以单独给某个物体设置迭代次数的,例如:
Rigidbody body = GetComponent<Rigidbody>();
body.solverIterations = 60;
这个值覆盖了全局的默认Solver iterations设置,即Physics设置中的Default Solver Iterations。
射线检测和查询
Raycast和其他物理查询例如overlap sphere,boxcast允许我们检测和收集在一定方向和距离上的碰撞体。射线检测有一定的消耗,但我们可以优化。
使用Non Allocating查询
任何物理查询如果返回多个对象实例,就需要在堆上分配这些对象,这最终会导致GC。为了降低GC的消耗,一般来说是建议使用Non Allocating版本的物理查询方法,例如:
static Collider[] s_tempColliders = new Collider[32];
int count = Physics.OverlapBoxNonAlloc(pos, extents, s_tempColliders, rotation, s_overlapLayerMask, QueryTriggerInteraction.Ignore);
使用预先分配的collider数组,而不是每次都重新分配collider的实例。NonAlloc方法会填充所有的collider到数组中,直到数组满,因此需要设置足够大的数组,或者如果你并不关心所有的collider,那么设置数组容量到你关心的数量即可。
合理设置查询的LayerMask
使用LayerMask可以直接过滤掉你不关心的物体,从而节约查询操作的时间。例如上面的例子中,检测Player之外的所有的层,可以这么设置:
int ignoreLayer = LayerMask.NameToLayer("Player");
s_overlapLayerMask = ~ (1 << ignoreLayer);
使用Batch Query
当需要进行大量查询时,可以使用Batch Query,这使用了Unity的Job System,通过RaycastCommand.ScheduleBatch执行一组RaycastCommand。
JobHandle jobHandle;
NativeArray<RaycastHit> hitResults;
NatvieArray<RaycastCommand> commands;
RaycastHit[] buffer;
void OnEnable()
{
hitResults = new NativeArray<RaycastHit>(size, Allocator.Persistent);
commands = new NativeArray<RaycastCommand>(size, Allocator.Persistent);
buffer = new RaycastHit[1];
}
void OnDisable()
{
hitResults.Dispose();
commands.Dispose();
}
void Update()
{
jobHandle.Complete();
float start = -100f;
for(int i=0; i < size; i++)
{
Vector3 from = new Vector3(start + i, 10f, 0);
commands[i] = new RaycastCommand(from, Vector3.down, distance, -1, 1);
}
jobHandle = RaycastCommand.ScheduleBatch(commands, hitResults, 1);
}