Again, you can't connect the dots looking forward; you can only connect them looking backwards. So you have to trust that the dots will somehow connect in your future. You have to trust in something - your gut, destiny, life, karma, whatever. This approach has never let me down, and it has made all the difference in my life. ——史蒂夫·乔布斯
游戏中,往往一帧内会发生很多事情,有些bug就是由于在同一帧内调用的时序出错所引发的。这类时序的bug都比较隐蔽,只能打断点、查Log或者Dump File进行分析。
本文并不分析如何进行debug,而是想一次为切入点,带大家来一起看一看一帧之中到底发生了些什么、我们重写的Tick函数到底在哪里调用、TickGroup到底是什么东西......
1 Tick类别
1.1 TickFunction
应用场景:Actor、Component、自定义TickFunction等。
TickGroup:TickFunction可决定TickGroup,TickGroup内划分了Tick的时机。
1.2 TickableObject
应用场景:TickableSubsystem等
可重写TickableObject的World、是否编辑器Tick、是否暂停Tick等
1.3 LatentActionManager
应用场景:蓝图内的延时相关节点,如Delay、DelayUntilNextTick等
调用相关节点时,会将待执行函数的数据添加至LatentActionManager内保存,并在LevelTick内触发。
1.4 UI Tick
应用场景:UserWidget的Tick函数。
1.5 Ticker
应用场景:可以绑定委托以在Tick中触发。
2 Tick调用流程
【1】在EngineLoop层会有三处Tick的调用位置,GEngineTick、UI Tick和Ticker。其中GEngineTick在编辑器模式下进入EditorEngine,打包模式下进入GameEngine,二者的Tick内调用逻辑基本无差异,故后续不做特别区分。
【2】Engine的Tick主要有两个重要的调用位置:WorldTick和TickableObjectTick。这两个位置可基本覆盖项目内的Tick逻辑。
【3】WorldTick内,会依次执行TickGroup(具体调用顺序见上图),这里是大部分TickFunction的Tick位置(即大部分Actor、Component的Tick位置)。
执行完TickGroup的TG_PostPhysics组后,会执行LatentActionManager的ProcessLatentActions函数,此时会执行蓝图节点Delay、DelayUntilNextTick等的Tick。
在执行TickGroup之间,会夹杂一次TickableGameObject的Tick执行,传入参数为当前世界(TickableGameObject的Tick逻辑见后文)。
【4】执行完WorldTick后,TickableGameObject仍会再执行一次,传入参数为nullptr。
【5】在TickableGameObject的Tick内,会遍历所有的TickableGameObject,在循环内会检查Object的TickableObjectWorld是否与传入的World相同,若相同则执行该Object的Tick。
TickableGameObject基类默认World为nullptr,可重写指定相应的World。
【6】执行完GEngineTick后,执行UI Tick。
【7】TSTicker的CoreTicker(全局单例)会统一执行1次Tick。
3 常用Tick整理
Tick位置估计方法:
- 先判断是否是LatentAction、Timer、UI Tick或Ticker,上述Tick位置明确,详细见上文;
- 再判断是TickFunction还是TickableGameObject,TickFunction统一走WorldTick,再对其TickGroup进行分析;
- TickableGameObject执行TickableGameObject的Tick执行,若指定World走WorldTick内的TickableGameObject的Tick位置,否则走WorldTick后的Tick位置。
- ActorTick:走WorldTick,TickGroup一般为PrePhysics
- ComponentTick:走WorldTick,TickGroup一般为DuringPhysics
- 各种TickFunction:走WorldTick,TickGroup根据设置决定
- TickableWorldSubsystem:走WorldTick内的TickableGameObject的Tick调用
- BinkMediaPlayer:走WorldTick后的TickableGameObject的Tick调用(BinkMediaPlayer没重写GetTickableGameObjectWorld函数,所以走WorldTick后的Tick点位)
本文的目的希望可以通过分析大致明确自己的逻辑在哪个位置,虽然分析到最后有点像罗列“茴香豆的茴字有几种写法”,其实作者认为分析Tick有几种的同时更应该分析Unreal为什么把Tick这般划分,如果有时间的话,就把后者补充上吧!