37.性能优化-CPU-脚本-Transform
37.1 知识点
避免运行时修改 Transform 父节点
在运行时通过 SetParent 修改 Transform 的父节点是一项开销较大的操作。
主要原因:
触发 Transform 系统重建
修改父节点后,Unity 会重建该物体及其子物体的层级关系与矩阵缓存,触发一系列递归更新,在父子层级多时尤其耗性能。可能导致批处理中断
若对象原本参与同一静态批处理,修改父节点后可能触发重新分组甚至退出批处理。可能导致缓冲区扩展
Transform 底层父子关系类似动态数组,修改父节点可能引起缓冲区扩展,带来不必要的内存分配。
因此应尽量避免在运行时频繁或大规模地改父节点;若必须在运行时挂到新父节点下,尽量集中、少量地做。
避免运行时频繁修改 Transform 相关属性
频繁修改 Transform 的属性会“牵一发动全身”,可能引发连锁更新,在物体多或低端设备上影响更明显,应尽量避免。
position、rotation、scale 的开销
Transform 内部会存储与父节点相关的数据,直接改 position、rotation、scale 等世界属性时,引擎内部会做大量矩阵运算,且物体在 Hierarchy 中层级越深,计算量越大。
优化建议: 在逻辑允许的前提下,优先使用 localPosition、localRotation、localScale 等本地属性,修改成本相对更小。
对其他组件的影响
每次修改 Transform 属性,引擎会向依赖它的组件发送内部通知,例如 Collider、Rigidbody、Light、Camera 等,物理与渲染系统都要拿到新的 Transform 数据才能更新。因此应避免在一帧内多次修改 Transform 属性。
优化建议: 用缓存方式减少写回次数:先读取到局部变量,在局部变量上做完所有运算,最后再一次性写回。
示例(先取位置,在托管侧做完运算再写回):
// 先拿到位置信息,在局部变量上做一系列操作
Vector3 cachedPosition = transform.position;
cachedPosition += new Vector3(1, 1, 1);
cachedPosition += new Vector3(1, 1, 1);
cachedPosition += new Vector3(1, 1, 1);
cachedPosition -= new Vector3(1, 1, 1);
cachedPosition -= new Vector3(1, 1, 1);
cachedPosition -= new Vector3(1, 1, 1);
cachedPosition -= new Vector3(1, 1, 1);
// 最后再设置回去,只触发一次桥接与内部更新
transform.position = cachedPosition;
若相对父节点修改即可满足需求,可改用本地属性,减少内部矩阵链更新:
// 能用本地属性时优先用本地属性,成本更小
transform.localPosition += offset;
// 或先缓存再写回
Vector3 localPosition = transform.localPosition;
localPosition += offset;
transform.localPosition = localPosition;
避免运行时频繁使用 Transform 相关方法
避免滥用封装好的方法
LookAt、RotateAround 等封装方法内部可能包含额外计算,在每帧或高频调用时会带来明显开销。若对性能敏感,可自行用向量、四元数等计算旋转或“看向”逻辑,按需只做必要运算。
避免频繁使用层级遍历方法
transform.Find(string) 等查找方法会在内部做层级遍历,在子物体多时非常耗性能,不应在 Update 等每帧执行的方法里使用。
优化建议: 在 Awake 或 Start 中查找一次并缓存引用,后续直接使用缓存。
// 不推荐:在 Update 中每帧查找
void Update()
{
Transform child = transform.Find("SomeChild");
}
// 推荐:初始化时查找并缓存
private Transform cachedChild;
void Awake()
{
cachedChild = transform.Find("SomeChild");
}
37.2 知识点代码
Lesson37_性能优化_CPU_脚本_Transform.cs
using UnityEngine;
public class Lesson37_性能优化_CPU_脚本_Transform : MonoBehaviour
{
void Start()
{
#region 知识点一 避免运行时修改Transform父节点问题
/* 运行时 SetParent 会触发:层级与矩阵重建、批处理可能中断、缓冲区可能扩展;
* 应尽量避免在运行时频繁或大规模修改父节点。*/
#endregion
#region 知识点二 避免运行时频繁修改Transform相关属性
/* 频繁改 position/rotation/scale 会触发大量矩阵运算并通知 Collider、Rigidbody 等;
* 优化:优先用 localPosition/localRotation/localScale,并用缓存减少写回次数。*/
#endregion
#region 知识点三 避免运行时频繁使用Transform相关方法
/* LookAt、RotateAround 等内部有额外计算,可改为自行计算;
* transform.Find 等会遍历层级,应在 Awake/Start 中缓存,避免在 Update 中使用。*/
#endregion
}
void Update()
{
// 先拿到位置信息,做好一系列操作后最后再设置回去,减少对 transform 的写回次数
Vector3 cachedPosition = transform.position;
// 一系列操作在局部变量上完成
cachedPosition += new Vector3(1, 1, 1);
cachedPosition += new Vector3(1, 1, 1);
cachedPosition += new Vector3(1, 1, 1);
cachedPosition -= new Vector3(1, 1, 1);
cachedPosition -= new Vector3(1, 1, 1);
cachedPosition -= new Vector3(1, 1, 1);
cachedPosition -= new Vector3(1, 1, 1);
// 最后再设置回去
transform.position = cachedPosition;
}
}
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 785293209@qq.com