UPL5-3——Update 相关

移除空 Update

如果在脚本中定义了生命周期函数,但没有在其中写任何逻辑,他们仍然会带来额外的性能开销
因此我们要避免在脚本中写空的生命周期函数

特别是那些会每帧调用的生命周期函数

  1. Update
  2. LateUpdate
  3. FixedUpdate
  4. OnGUI

需要在 Update 中频繁使用的组件应该缓存

在 Unity 中反复获取一个组件是一种常见错误,对于一些常用的组件,我们不应该每次都重复的去获取,
应当通过泛型获取组件的方式将其缓存下来

我们应该避免在 Update 中调用如:

  • GetComponent 寻找组件相关API
  • Find 寻找对象相关API

等等

降低执行频率

我们时长会在 Update​ 中重复执行一些逻辑,如果超出需要的频率重复调用某些代码,会造成性能的浪费
比如:怪物 AI 中,怪物需要不停的朝玩家寻路并移动,如果我们在 Update 中不停每帧重复寻路会造成不必要的性能浪费

我们可以通过降低执行频率来避免性能浪费,最常用的方式就是自定义更新间隔

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
private float _updateTime = 0.25f;      // 间隔多久更新一次
private float _timer; // 计时

void Update()
{
// 将每帧执行的逻辑变成每隔一段事件执行的逻辑
_timer += Time.deltaTime;
if (_timer > _updateTime)
{
MonsterAILogic();
_timer = 0;
}
}

// 假设是执行怪物AI逻辑的函数
private void MonsterAILogic() { }

减少复杂计算

我们要尽量减少在 Update​ 中进行复杂的数学计算,对于一些不变的计算结果
应该在初始化时(Awake​、Start​ 中)就计算好 Update 中直接使用

事件驱动代替每帧检测

利用观察者模式取代在 Update​ 中每帧进行状态检测,例如:将 玩家每帧检测怪物血量判断怪物死亡 改为 怪物死亡时通知玩家怪物死亡
观察者模式的一个实践就是 事件中心,相关实现可参考:UFL4——事件中心模块

分帧执行

对大批量对象更新时,使用异步方法,或者自行记录的方式进行分帧处理,避免单帧卡顿

Unity 的协同程序也可以达到对应效果,但是协程自身存在也有一些性能问题需要解决,详见:UPL5-4——协程相关

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
private int _index;
private int _monsterNum = 10;
private List<GameObject> monsters = new List<GameObject>(10000);

void Update()
{
// 每帧遍历指定数量的怪物
for (int i = 0; i < _monsterNum; i++)
{
if (_index >= monsters.Count)
_index = 0;
monsters[_index].GetType(); // 假设这是要让怪物执行的逻辑
_index++;
}
}

自定义 Update 管理器(统一管理 Update 更新)

在 Unity 中,如果每个继承 MonoBehaviour​ 的脚本独立处理 Update 生命周期函数

当这样的对象过多时,由于引擎底层机制(Unity 底层 C++ 层调用 C# 会有跨语言的开销、内存连续访问问题),会增加性能的开销
如果我们对 Update​ 进行统一管理,可以大幅提升性能,并且独立调用 Update 生命周期函数的对象越多,统一管理器的优势就越明显(特别是中大型对象场景中对象非常多时)

优化方式:

通过一个统一更新管理器 统一 对所有需要 Update​ 更新的脚本进行管理,只需要一个 Update​ 生命周期函数 执行所有对象的更新
对应的实现可参考公共 MonoBehaviour​ 管理器,相关内容:UFL2——公共Mono模块
公共 MonoBehaviour 管理器中是通过 事件 记录所有Update函数的,也可以通过列表记录对象的形式调用