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位置估计方法:

  1. 先判断是否是LatentAction、Timer、UI Tick或Ticker,上述Tick位置明确,详细见上文;
  2. 再判断是TickFunction还是TickableGameObject,TickFunction统一走WorldTick,再对其TickGroup进行分析;
  3. 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这般划分,如果有时间的话,就把后者补充上吧!