网易首页 > 网易号 > 正文 申请入驻

硬核分析:Unreal Tick 实现

0
分享至

作者:reckzhang,腾讯IEG魔方工作室后台开发工程师

前言

参与游戏场景开发中, 需要设计 Tick 机制用以驱动场景中各个单位, 故对 Unreal 的 Tick 机制进行简要分析学习.

Unreal Tick 机制

Tick 通常表示进程循环中的一次执行, 由进程主函数循环驱动. 因此从 Unreal 主函数开始, 分析 Tick 调用栈信息:

// 主函数循环驱动int32 GuardedMain( const TCHAR* CmdLine ) { while( !IsEngineExitRequested() ) { EngineTick(); }}// 主函数驱动从层级关系GuardedMain(const wchar_t * CmdLine) EngineTick() FEngineLoop::Tick() UGameEngine::Tick(float DeltaSeconds, bool bIdleMode) UWorld::Tick(ELevelTick TickType, float DeltaSeconds)

可知 Unreal Tick 机制 是由主函数 GuardedMain 循环调用 EngineTick() 方法, 最终在 UWorld::Tick 内实现具体逻辑. 分析 UWorld::Tick 代码:

void UWorld::Tick( ELevelTick TickType, float DeltaSeconds ) { // UWorld 逐 Level 驱动 for (int32 i = 0; i < LevelCollections.Num(); ++i) { // 1. Actor Ticking RunTickGroup(TG_PrePhysics); RunTickGroup(TG_StartPhysics); RunTickGroup(TG_DuringPhysics, false); RunTickGroup(TG_EndPhysics); RunTickGroup(TG_PostPhysics); RunTickGroup(TG_PostUpdateWork); RunTickGroup(TG_LastDemotable); // 2. Timer Ticking GetTimerManager().Tick(DeltaSeconds); // 3. Tickable Ticking FTickableGameObject::TickObjects(this, TickType, bIsPaused, DeltaSeconds); }}

提取出 UWorld::Tick() 中的核心逻辑, 可以看到 UWorld::Tick() 是以 Level 为单位逐个遍历, 依次调用三种 Tick 框架, 分别为:

  1. Actor Ticking.
  2. Timer Ticking.
  3. Tickable Ticking.

下面对这三种 Tick 框架进行具体分析.

Actor Ticking

Actor Ticking 服务对象为 Actor / Actor Component, 固定时间间隔执行类内的一段代码或者蓝图脚本. Actor Ticking 可以设置 Tick 的开启与关闭, Tick 的时间间隔, 被调用的时机和前置 Tick 依赖.

使用方法

  • Actor Ticking 给业务提供的两个 Tick 回调, 重载 Tick 执行的具体逻辑:
// Actorvirtual void AActor::Tick( float DeltaSeconds );// Actor Componentvirtual void UActorComponent::TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction);
  • Actor Ticking 可以设置 Tick 的开启与关闭, Tick 的时间间隔, 被调用的时机和前置 Tick 依赖. 前置 Tick 依赖在本文中不做赘述, 有兴趣的同学可以了解一下前置 Tick 依赖.
// 1. 开启 Tick(::BeforePlay阶段默认开启)void AActor::SetActorTickEnabled(bool bEnabled)// 2. 设置 Tick 时间间隔, 单位为秒, 如果小于等于0则每帧都会调用void AActor::SetActorTickInterval(float TickInterval);// 3. 设置 Tick 组, 即被调用的时机, 默认为TG_PrePhysicsvoid AActor::SetTickGroup(ETickingGroup NewTickGroup)
  • Tick 组 (Tick Group) 表示在一次Tick中的调用时机, 因此可以表示被调度的优先级. 在 UWorld::Tick 中可以看到各个组按照先后顺序依次调用:
RunTickGroup(TG_PrePhysics);RunTickGroup(TG_StartPhysics);RunTickGroup(TG_DuringPhysics, false);RunTickGroup(TG_EndPhysics);RunTickGroup(TG_PostPhysics);RunTickGroup(TG_PostUpdateWork);RunTickGroup(TG_LastDemotable);

Tick 组

引擎活动

TG_PrePhysics

帧的开始。
- Actor 将与物理对象(包括基于物理的附着物)进行交互时使用的 tick 组。如此,actor 的运动便完成,并可被纳入物理模拟因素。
- 此 tick 中的物理模拟数据属于上一帧 — 也就是上一帧渲染到屏幕上的数据。

TG_DuringPhysics

到达此步骤时,物理模拟已开始。Tick 此组时的任意时候,或所有组成员已 tick 后,模拟完成并更新引擎的物理数据。
- 因它在物理模拟的同时运行,无法确定此 tick 中的物理数据来自上一帧或当前帧。物理模拟可在此 tick 组中的任意时候完成,且不会显示信息来表达此结果。
- 因为物理模拟数据可能来自当前帧或上一帧,此 tick 组只推荐用于无视物理数据或允许一帧偏差的逻辑。常见用途为更新物品栏画面或小地图显示。此处物理数据完全无关,或显示要求不精确,一帧延迟不会造成问题。

TG_PostPhysics

此步骤开始时物理模拟已完成,引擎正在使用当前帧的数据。
- 此 tick 组运行时,此帧的物理模拟结果已完成。
- 此组可用于武器或运动追踪。渲染此帧时所有物理对象将位于它们的最终位置。这尤其适用于射击游戏中的激光瞄准器。在此情况中激光束必须从枪的最终位置发出,即便出现一帧延迟也会十分明显。

n/a

处理隐藏操作、tick 世界时间管理器、更新摄像机、更新关卡流送体积域和流送操作。

TG_PostUpdateWork

- 这在 TG_PostPhysics 之后运行。从以往来看,它的基函数是将最靠后的信息送入粒子系统。
- TG_PostUpdateWork 在摄像机更新后发生。如特效必须知晓摄像机朝向的准确位置,可将控制这些特效的 actor 放置于此。
- 这也可用于在帧中绝对最靠后运行的游戏逻辑,如解决格斗游戏中两个角色在同一帧中尝试抓住对方的情况。

n/a

处理之前在帧中创建的 actor 的延迟生成。完成帧并渲染。

以一个游戏来举例说明如何使用以上列出的每种 tick 组:在这个游戏中玩家控制一个动画 actor 进行激光瞄准,在影响的点上放置一个特殊的瞄准标线 actor。只要激光对准特定类型的目标物体,一个特殊的条便会开始填充。一个 HUD actor 将把此条显示在屏幕上。 玩家的动画 actor 将在 TG_PrePhysics 中移动和执行动画。需要在物理之前完成它的动画设置,使物理模拟对象正常跟随并与之交互。 HUD 可在任意 tick 组中更新,但出于两大原因,TG_DuringPhysics 为优选。第一,TG_DuringPhysics 可接受,因为它不直接与游戏的物理模拟产生交互或使用来自物理模拟的数据。第二,没有原因强制物理模拟等待 HUD 完成更新,也没有原因强制 HUD 等待物理模拟完成。注意,HUD 为游戏后的一帧。对准目标物体时,此帧不会反映到条中,下一帧才会。 标线 actor 在 TG_PostPhysics 中更新。如此标线便可了解其对场景的追踪。因为它将在帧的最后渲染,所以它将正常出现在物体表面。它还将基于目标物体的正确位置调整条的数值。 最后,在 TG_PostUpdateWork 中,激光粒子效果将更新到瞄准 actor 和标线的最后位置
Actor Ticking 组件介绍
  • FTickFunction 是 Actor Ticking 调度的基本单元 , 作为成员变量存储在 Actor / Actor Component 中
class AActor { UPROPERTY(EditDefaultsOnly, Category=Tick) struct FActorTickFunction PrimaryActorTick;}class UActorComponent { UPROPERTY(EditDefaultsOnly, Category="ComponentTick") struct FActorComponentTickFunction PrimaryComponentTick;}
  • FTickTaskLevel 管理 Level 内所有 Tick 的单元. SetActorTickEnable / SetComponentTickEnabled 时, 就会**将 FTickFunction 注册到所在 Level 的 FTickTaskLevel **中.
void AActor::BeginPlay() { // 省略跳转, 最终所在 Level 对应 FTickTaskLevel 将 TickFunction 添加 FTickTaskLevel* Level = TickTaskLevelForLevel(InLevel); Level->AddTickFunction(TickFunction);}class FTickTaskLevel { // 包含当前 Level 的所有 Tick 单元. TSet AllEnabledTickFunctions; FCoolingDownTickFunctionList AllCoolingDownTickFunctions; TSet AllDisabledTickFunctions; TArrayWithThreadsafeAdd TickFunctionsToReschedule; TSet NewlySpawnedTickFunctions;}
  • FTickTaskManager 管理所有 FTickTaskLevel, 是 Tick 的管理单例类.
class FTickTaskManager { // 管理所有 FTickTaskLevel TArray LevelList;} Actor Ticking 调度流程 // 主函数驱动从层级关系GuardedMain(const wchar_t * CmdLine) EngineTick() FEngineLoop::Tick() UGameEngine::Tick(float DeltaSeconds, bool bIdleMode) UWorld::Tick(ELevelTick TickType, float DeltaSeconds) for (int32 i = 0; i < LevelCollections.Num(); ++i) { RunTickGroup(TG_PrePhysics); RunTickGroup(TG_StartPhysics); RunTickGroup(TG_DuringPhysics, false); RunTickGroup(TG_EndPhysics); RunTickGroup(TG_PostPhysics); RunTickGroup(TG_PostUpdateWork); RunTickGroup(TG_LastDemotable); }

UWorld 逐 Level 驱动, 按照 Tick 组顺序调用对应 Actor / Actor Component, 即 RunTickGroup(ETickingGroup Group).

Cooldown 机制

考虑到不同的 Actor / Actor Component 业务场景各不相同, 有些组件不会每帧Tick, 而是可能是几秒钟或者几分钟才会 Tick 一次, 因此 Unreal 设计了 Cooldown 机制来满足此种场景:

  1. 遍历所有 Tick 任务(O(n)), 如果时间间隔大于 0(意味着并非每帧都会调用), 就会放到 CoolingDown 队列.
  2. 根据 Tick 间隔对临时队列排序(O(mlogm)), 遍历临时队列判断到达 Tick 时间的加入就绪队列.
  3. 执行处于就绪队列中的 Tick 任务.
  4. 执行结束后, 将执行完的任务重新放回 CoolingDown队列.

Timer Ticking

Timer Ticking 作为 Unreal 的定时器调度机制, 服务对象为 Delegate.

使用方法

调用 TimerManager 的 SetTimer 方法, 传入需要定时执行的 Delegate 和自定义参数.

/** * Sets a timer to call the given native function at a set interval. If a timer is already set * for this handle, it will replace the current timer. * * @param InOutHandle If the passed-in handle refers to an existing timer, it will be cleared before the new timer is added. A new handle to the new timer is returned in either case. * @param InObj Object to call the timer function on. * @param InTimerMethod Method to call when timer fires. * @param InRate The amount of time (in seconds) between set and firing. If <= 0.f, clears existing timers. * @param InbLoop true to keep firing at Rate intervals, false to fire only once. * @param InFirstDelay The time (in seconds) for the first iteration of a looping timer. If < 0.f InRate will be used. */template< class UserClass >FORCEINLINE void SetTimer(FTimerHandle& InOutHandle, UserClass* InObj, typename FTimerDelegate::TUObjectMethodDelegate< UserClass >::FMethodPtr InTimerMethod, float InRate, bool InbLoop = false, float InFirstDelay = -1.f);GetWorldTimerManager().SetTimer(SelectActorTickHandle, this, &UGameplayDebuggerLocalController::OnSelectActorTick, 0.01f, bLooping); Timer Ticking 实现机制

Timer Ticking 定时器基于实现, TimerManager 在每次 Tick 时从检查堆顶元素, 如果到了执行时间就取出并执行, 直到条件不满足停止本次 Tick.

void FTimerManager::Tick(float DeltaTime) { while (ActiveTimerHeap.Num() > 0) { // 堆定定时任务到达执行时间 if (InternalTime > Top->ExpireTime) { // 执行定时任务 ActiveTimerHeap.HeapPop(CurrentlyExecutingTimer, FTimerHeapOrder(Timers), /*bAllowShrinking=*/ false); Top->TimerDelegate.Execute(); } }}

Tickable Ticking

Tickable Ticking 服务对象为 C++ 类, 继承自 FTickableGameObject 基类, 获得 Tick 能力.

使用方法

C++类继承FTickableGameObject, 重载 Tick 和 GetStatId 虚函数.

// 1. 继承 FTickableGameObject 基类classs TickableClass : public FTickableGameObject// 2. 实现函数 Tick(float DeltaTime) 和 GetStatId()// Tick 具体逻辑virtual void Tick(float DeltaTime) override;// stats 性能统计埋点, 如果不希望被统计, return TStatId()即可virtual TStatId GetStatId() const override { RETURN_QUICK_DECLARE_CYCLE_STAT(TickableClass, STATGROUP_Tickables);} Tickable Ticking 实现机制

新 Tickable 对象初始化后会添加到单例 FTickableStatics 集合中, FTickableGameObject 单例在每次 Tick 后遍历 FTickableStatics 集合中的所有 Tickable 对象并执行, 因此 Tickable 对象会在每一帧执行, 不能设置 Tick 的时间间隔.

void FTickableGameObject::TickObjects(UWorld* World, const int32 InTickType, const bool bIsPaused, const float DeltaSeconds) { for (const FTickableObjectEntry& TickableEntry : Statics.TickableObjects) { TickableObject->Tick(DeltaSeconds); }}

总结

横向比较 Unreal Tick 中 Actor Ticking / Timer Ticking / Tickable Ticking 三种实现机制:

Actor Ticking

Timer Ticking

Tickable Ticking


实现机制

Cooldown 机制, 以 Tick 间隔维护有序队列

以 Tick 间隔维护小顶堆

无序数组

插入复杂度

O(1)

O(logn)


O(1)

删除复杂度

O(n)

O(n)

O(n)

Tick 复杂度

O(nlogn)

O(nlogn)

O(n)

服务对象

Actor / Actor Component

Delegate

C++类

设置 Tick 时间间隔

可以

可以

不可以

特点

1. 通过设置 Tick 组, 确定 Tick 被调用的时机.
2. 可以设置 Tick 之间的前置依赖关系.

1. 粒度细化为 Delegate.

1. 每帧调用, 不能设置 Tick 时间间隔.

特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。

Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.

相关推荐
热点推荐
查阅了牛来导演的母亲,真是不看不知道,一看大为惊叹,十分伟大

查阅了牛来导演的母亲,真是不看不知道,一看大为惊叹,十分伟大

乡野小珥
2026-08-24 10:05:13
巴沙尔为什么被赶下台?原来,叙利亚一直不承认,库尔德人的权利

巴沙尔为什么被赶下台?原来,叙利亚一直不承认,库尔德人的权利

掠影后有感
2026-08-24 10:39:57
中央气象台8月25日10时继续发布暴雨黄色预警

中央气象台8月25日10时继续发布暴雨黄色预警

环球网资讯
2026-08-25 10:54:24
20.7万!特斯拉新车突然官宣:9月3日 ,正式发布

20.7万!特斯拉新车突然官宣:9月3日 ,正式发布

科技堡垒
2026-08-24 12:23:17
俄乌大结局终于要来?最大罪人已浮现,不是美国

俄乌大结局终于要来?最大罪人已浮现,不是美国

江启
2026-08-24 07:31:00
形势到底有多严峻?网传变天了,大型超市撤退了,寒意凉凉...

形势到底有多严峻?网传变天了,大型超市撤退了,寒意凉凉...

慧翔百科
2026-08-24 12:08:45
走私 74 台 B300、净赚 1.4 亿!7 人被捕、面临最高 5 年刑期

走私 74 台 B300、净赚 1.4 亿!7 人被捕、面临最高 5 年刑期

云头条
2026-08-24 22:56:09
老板电器的AI生意,最难复制的不是AI

老板电器的AI生意,最难复制的不是AI

中国家电网
2026-08-24 11:25:40
重磅!曼城欲签27岁利物浦王牌+正积极谈判 转会费或近亿 截胡热刺

重磅!曼城欲签27岁利物浦王牌+正积极谈判 转会费或近亿 截胡热刺

我爱英超
2026-08-25 01:28:54
对巴勒斯坦人的偏袒,才是巴以冲突持续的根本原因

对巴勒斯坦人的偏袒,才是巴以冲突持续的根本原因

老王说正义
2026-08-23 00:02:25
恒大数百万烂尾业主:为什么很少看见激烈闹事,他们都去哪里了?

恒大数百万烂尾业主:为什么很少看见激烈闹事,他们都去哪里了?

混沌录
2026-08-24 22:59:15
“神药”阿托伐他汀:进口与国产,一场理智与情感的抉择

“神药”阿托伐他汀:进口与国产,一场理智与情感的抉择

重症医生张伟
2026-08-24 05:40:57
网红魏莹陪睡新进展,父亲:我女儿陪睡过一年,每个月他来厦门都要陪睡一次

网红魏莹陪睡新进展,父亲:我女儿陪睡过一年,每个月他来厦门都要陪睡一次

江山挥笔
2026-08-22 09:46:45
一百多年前,康有为花150万买下瑞典的一座岛,如今岛权归谁

一百多年前,康有为花150万买下瑞典的一座岛,如今岛权归谁

浩渺青史
2026-08-24 01:49:35
马云太“鸡贼”了!多年前恒大夺冠庆功宴照片再登热搜:举杯瞬间,马云留意许家印的动作,观察对方是否举杯饮酒

马云太“鸡贼”了!多年前恒大夺冠庆功宴照片再登热搜:举杯瞬间,马云留意许家印的动作,观察对方是否举杯饮酒

火山詩话
2026-08-23 07:03:56
原清华教授郑毓煌爆料!评院士要花1000万到1亿,长江学者、杰青也得拜50个评委

原清华教授郑毓煌爆料!评院士要花1000万到1亿,长江学者、杰青也得拜50个评委

小徐讲八卦
2026-08-24 12:54:08
迪巴拉梦幻1战:1v4强突 倒三角妙传!意甲3助 6万人狂欢

迪巴拉梦幻1战:1v4强突 倒三角妙传!意甲3助 6万人狂欢

叶青足球世界
2026-08-25 09:48:19
彻底炸锅!北大副校长自首全是演的?网友:被逼顶罪,大鱼没动!

彻底炸锅!北大副校长自首全是演的?网友:被逼顶罪,大鱼没动!

天天热点见闻
2026-08-25 08:28:08
“甲醛白菜”屡禁不止:十余年间被多次曝光,甲醛为何仍未纳入蔬菜常规检测?

“甲醛白菜”屡禁不止:十余年间被多次曝光,甲醛为何仍未纳入蔬菜常规检测?

齐鲁壹点
2026-08-25 07:50:14
刘畊宏直播间凉了,“老板娘”的身材戳破了多少人的减肥梦?

刘畊宏直播间凉了,“老板娘”的身材戳破了多少人的减肥梦?

看尽落尘花q
2026-08-23 19:28:46
2026-08-25 12:24:49
腾讯技术工程
腾讯技术工程
不止于技术
1447文章数 601关注度
往期回顾 全部

科技要闻

马斯克:我不习惯输,Grok必须追上对手

头条要闻

帮扶老人反被索赔新进展:店家支付的1.9万获全额返还

头条要闻

帮扶老人反被索赔新进展:店家支付的1.9万获全额返还

体育要闻

迪巴拉梦幻一战:1v4强突 倒三角妙传

娱乐要闻

韩佩颖直播说错话,终究毁了路人缘

财经要闻

瓜子二手车乱象调查

汽车要闻

旗舰豪华MPV新高度 尊界V800亮相2026成都车展

态度原创

家居
房产
艺术
本地
公开课

家居要闻

2026建博会(广州) 公装联探展交流活动

房产要闻

规格罕见!海南房地产,发出最强信号!

艺术要闻

翁凯旋 2026年8月油画写生新作

本地新闻

《牛来》的一声“妈妈”,到底多少人被精神污染了

公开课

李玫瑾:为什么性格比能力更重要?

无障碍浏览 进入关怀版