Transform 仅仅观察Unity编辑器的层级窗口无法了解Transform组件,但Transform组件在Unity 5和Unity 2018之间发生了很多变化,这也为性能提升过程提供了新的可能性。 回到Unity 4和Unity 5,在创建Transform数据时,对象会被分配到Unity本地内存堆的某个位置。该对象可能在本地内存堆的任何位置,我们无法保证二个连续分配的Transform数据会分配到相邻位置,也无法保证子Transform会分配到父Transform的附近位置。 这意味着,在线性迭代Transform层级时,我们不会在连续内存区域上进行线性迭代。这会造成处理器重复发生停顿,因为它要等待从L2缓存或主内存获取Transform数据。 在Unity后端中,每当Transform的位置,角度或缩放发生改变时,该Transform都会发送OnTransformChanged信息。所有子Transform必须接收此消息,从而使它们可以更新自己的数据,而且它们也可以通知其它和Transform变化有关的组件,例如:在子Transform或父Transform发生变化时,带有碰撞体的子Transform必须更新物理系统。 这个无法避免的信息会造成很多性能问题,而且我们没有内置方法来避免虚假信息。如果你要修改Transform,也会改变它的子对象,无法避免Unity在每次修改后发送OnTransformChanged信息,这样会浪费大量CPU时间。 因为这一细节,对于旧版本Unity最常见的建议之一便是对Transform的改动进行批处理。也就是说,在一帧开始的时候,一次获取Transform的位置和角度信息,在一帧的时间内使用和更新那些缓存的数值,仅在帧的结尾对位置和角度应用一次改动。这是一个很好的建议,一直适用到Unity 2017.2。 在Unity 2017.4和Unity 2018.1中,新的TransformChangeDispatch替代了OnTransformChanged。 TransformChangeDispatch最初在Unity 5.4加入。在该版本中,Transform不再是可以位于Unity本地内存堆中任何位置的独立对象。场景中的每个根Transform都会由连续数据缓冲区表示。该缓冲区叫TransformHierarchy结构,它包含位于根Transform下所有Transform的数据。 此外,TransformHierarchy还存储其中每个Transform的元数据,元数据包含表示特定Transform是否被污染的位掩码,它表示:自从上次Transform被标记为“Clean”(干净)后,它的位置,角度或缩放是否发生变化。它还包含一个特别的位掩码,用于跟踪Unity有哪些系统和特定Transform的改动有关。 通过使用该数据,Unity可以为每个内部系统创建受污染Transform的列表,例如:粒子系统可以查询TransformChangeDispatch,以获取自从上次粒子系统运行FixedUpdate后,数据发生变化的Transform列表。 为了收集改动Transform的列表,TransformChangeDispatch不应该迭代场景中的所有Transform,如果场景包含大量Transform,那会使速度变得非常慢,而且在多数情况下,仅有少量Transform会发生改变。 为了解决该问题,TransformChangeDispatch会跟踪受污染TransformHierarchy结构的列表。当Transform发生变化时,它会把自身和子对象标记为“Dirty”(受污染的),然后使用TransformChangeDispatch系统注册存储它的TransformHierarchy结构。 在Unity中的其它系统请求发生变化的Transform列表时,TransformChangeDispatch会迭代保存在每个受污染TransformHierarchy结构中的每个Transform。带有污染位组和相关位组的Transform会添加到列表中,该列表会返回到提出请求的系统。 因为这种架构,层级分离得越多,可以更好地让Unity功能以粒度等级跟踪变化。存在场景根位置的Transform越多,在变化时要检查的Transform就越少。 但还有潜在影响。在检查TransformHierarchy结构时,TransformChangeDispatch会使用Unity内部的多线程处理系统来划分它需要做的工作。每次某个系统需要从TransformChangeDispatch请求变化的列表时,这种划分操作和组合结果的操作会增加少量性能开销。 Unity的大多数内部系统会在运行前,在每帧请求一次更新内容,例如:动画系统会在它评估场景中所有活动Animators前,请求更新内容。类似的,渲染系统会在开始剔除可见对象列表前,请求对场景中所有活动渲染器的更新。 只有一个系统与众不同,那就是Physics物理系统。 在Unity 2017.1及更早版本,物理更新是同步的。当移动或旋转带有碰撞体的Transform时,会立即更新物理场景。这样会确保碰撞体的改动位置或角度可以反映到物理世界中,从而使光线投射和其它物理查询是准确的。 在Unity 2017.2中,我们把物理功能转变为使用TransformChangeDispatch会造成一些性能问题。在执行光线投射的时候,我们必须查询TransformChangeDispatch,获取发生变化的Transform列表,然后把它们应用到物理世界。这会耗费较多性能,具体取决于Transform Hierarchies的大小,以及代码调用Physics API的方法。 这种行为由新的设置Physics.autoSyncTransforms管理。从Unity 2017.2到Unity 2018.2,这项设置默认设为”True”,每次调用Raycast或Spherecast等物理查询API时,Unity会自动同步物理世界到Transform更新。 这项设置也可以进行修改,既可以在Unity编辑器的Physics Settings修改,也可以通过设置Physics.autoSyncTransforms属性在运行时修改。如果将它设为"False",并禁用自动物理同步功能,那么物理系统仅会查询TransformChangeDispatch,以了解特定时间即在运行FixedUpdate之前的变化。 如果在调用物理查询API时遇到性能问题,我们有二个方法进行处理。 第一种方法,我们可以把Physics.autoSyncTransforms设为“False”,它会消除由TransformChangeDispatch和来自物理查询的物理场景更新造成的峰值情况。但是,如果执行此操作,在执行下一次FixedUpdate之前,对碰撞体的改动不会立即同步到物理场景。 这意味着,如果禁用AutoSyncTransforms,移动碰撞体,然后调用光线投射,使光线的方向为碰撞体新位置的话,光线投射可能不会击中碰撞体。这是因为光线投射会作用于物理场景的上一次更新版本,而那时物理场景还没有使用碰撞体的新位置来更新。 这会造成奇怪的Bug,所以应该小心测试自己的游戏,以确保禁用自动Transform同步功能不会造成问题。如果需要让物理效果通过Transform变化更新物理场景,我们可以调用Physics.SyncTransforms。由于该API速度较慢,因此最好别在每帧多次调用它。 请注意:从Unity 2018.3开始,Physics.autoSyncTransforms将默认设为“False”。 第二种方法,优化TransformChangeDispatch查询时间是重新安排查询和更新物理场景的顺序,使它对新系统更加友好。 由于Physics.autoSyncTransforms设为”True”,所有物理查询都会检查TransformChangeDispatch的变化,但如果TransformChangeDispatch没有任何受污染的TransformHierarchy结构要检查,而且物理系统没有更新的Transform要应用到物理场景,那么物理查询几乎没有任何开销。 所以,我们可以在一次批处理中执行所有物理查询,然后在一次批处理应用所有Transform变化,但是不要把Transform改动和物理查询API的调用混合。