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

硬核分析: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-01-31 00:10:09
“失业无人管,创业有人查”!这句话刺痛了多少中年人?

“失业无人管,创业有人查”!这句话刺痛了多少中年人?

今朝牛马
2026-01-28 22:04:41
红楼梦:秦可卿为何不反抗公公贾珍?一个耳熟能详的绰号里有答案

红楼梦:秦可卿为何不反抗公公贾珍?一个耳熟能详的绰号里有答案

谈史论天地
2026-02-01 10:55:06
全不反华了?9国高层排队赴京,中美一起享用晚宴,欧盟突然抱怨

全不反华了?9国高层排队赴京,中美一起享用晚宴,欧盟突然抱怨

爱下厨的阿酾
2026-02-01 05:44:42
中国平陆运河收尾,俄罗斯直呼“等到”,越南却坐不住了

中国平陆运河收尾,俄罗斯直呼“等到”,越南却坐不住了

他想要很多很多的梦
2026-02-01 16:58:07
史诗级暴跌之后,周末暗盘继续下挫,下周黄金白银将何去何从?

史诗级暴跌之后,周末暗盘继续下挫,下周黄金白银将何去何从?

东方豪侠
2026-02-01 09:22:01
10亿换一命!京东副总裁蔡磊对抗渐冻症4年后,终于赢得一线生机

10亿换一命!京东副总裁蔡磊对抗渐冻症4年后,终于赢得一线生机

王二哥老搞笑
2026-02-01 10:38:03
女子怀双胞胎人流半月后仍腹痛难忍 ,检查发现还有“第三子”,医生:宫内宫外同时妊娠,罕见中的罕见!

女子怀双胞胎人流半月后仍腹痛难忍 ,检查发现还有“第三子”,医生:宫内宫外同时妊娠,罕见中的罕见!

观威海
2026-01-25 10:22:10
四年暴跌1800亿!从千元一粒到无人问津,“国宝级”神药崩盘了?

四年暴跌1800亿!从千元一粒到无人问津,“国宝级”神药崩盘了?

顾史
2025-12-19 19:06:53
发现一个奇怪的现象:村里凡是大学毕业的子女,过年回家都很安静

发现一个奇怪的现象:村里凡是大学毕业的子女,过年回家都很安静

洪生鹏
2026-02-01 13:31:38
美伊大战在即,俄紧急送米-28援伊,中国买不到的杀器为啥给伊朗

美伊大战在即,俄紧急送米-28援伊,中国买不到的杀器为啥给伊朗

爱吃醋的猫咪
2026-02-01 19:37:17
老蒋扣押傅作义夫人,地下党束手无策,周总理悄悄放出一条消息

老蒋扣押傅作义夫人,地下党束手无策,周总理悄悄放出一条消息

搜史君
2026-01-21 10:05:09
狄龙:詹姆斯是现在最被高估球员 得把火炬传给东契奇亚历山大了

狄龙:詹姆斯是现在最被高估球员 得把火炬传给东契奇亚历山大了

罗说NBA
2026-02-01 10:24:47
赵恒多出演蒋介石,因扮演太像轰动台湾,连林青霞都找他合影

赵恒多出演蒋介石,因扮演太像轰动台湾,连林青霞都找他合影

史之铭
2026-01-26 21:12:54
伊朗议长宣布欧洲国家军队将被视为“恐怖组织”

伊朗议长宣布欧洲国家军队将被视为“恐怖组织”

界面新闻
2026-02-01 14:31:15
人民日报:坚持八个好习惯,越活越年轻不是难事

人民日报:坚持八个好习惯,越活越年轻不是难事

洞见
2026-01-29 20:31:40
越南要成为下一个乌克兰?一旦中越开战,中国不再对其留有余地?

越南要成为下一个乌克兰?一旦中越开战,中国不再对其留有余地?

梁讯
2026-02-01 15:14:45
Kimi公开喊话百度:搜官网前4条全是广告!有网友称被坑199元

Kimi公开喊话百度:搜官网前4条全是广告!有网友称被坑199元

新浪财经
2026-02-01 16:43:37
切尔西3比2西汉姆完成双杀,恩佐绝杀球证明关键性,主帅应变到位

切尔西3比2西汉姆完成双杀,恩佐绝杀球证明关键性,主帅应变到位

云儿评球
2026-02-01 18:38:13
神仙姐姐公开承认过的男朋友

神仙姐姐公开承认过的男朋友

微微热评
2026-01-20 18:35:54
2026-02-01 20:15:00
腾讯技术工程
腾讯技术工程
不止于技术
1363文章数 600关注度
往期回顾 全部

科技要闻

腾讯元宝宣布:10亿现金红包,今日开抢

头条要闻

2.5吨白银建造的地标被拍卖 其中1.75吨银折算1204万

头条要闻

2.5吨白银建造的地标被拍卖 其中1.75吨银折算1204万

体育要闻

锁喉吃红牌+扇耳光 英超15人打群架

娱乐要闻

春晚第三次联排阵容曝光:全是实力派

财经要闻

黄仁勋台北"夜宴":汇聚近40位台企高管

汽车要闻

岚图汽车1月交付10515辆 同比增长31%

态度原创

教育
手机
亲子
艺术
军事航空

教育要闻

批评高中语文,不教逻辑,压根没批到点上

手机要闻

别急着换机,只因下半年各大厂商都有大升级,这次等等党要赢了

亲子要闻

我发现一个暖心真相:春节带娃出游,竟是闹中养静的好契机

艺术要闻

柔美的色彩感,英国当代具象画家Emma McClure

军事要闻

伊朗民众:伊朗不会屈服于美国霸权

无障碍浏览 进入关怀版