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

硬核分析: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-10-09 10:46:39
7罚7中百分百!重回桑托斯,34岁的内马尔解锁点球满分答卷

7罚7中百分百!重回桑托斯,34岁的内马尔解锁点球满分答卷

友谊第一
2026-10-10 11:59:41
随着河南2-2、北京国安3-0、上海海港4-2,中超最新积分榜出炉

随着河南2-2、北京国安3-0、上海海港4-2,中超最新积分榜出炉

侧身凌空斩
2026-10-09 22:01:26
美籍华人发帖炮轰:中国引以为荣的扫码支付,真是最蠢的设计吗?

美籍华人发帖炮轰:中国引以为荣的扫码支付,真是最蠢的设计吗?

素衣读史
2026-10-08 01:23:20
又一电诈头目落网!坑害国人超27亿,杀人不眨眼,全网呼吁查赵薇

又一电诈头目落网!坑害国人超27亿,杀人不眨眼,全网呼吁查赵薇

泪满过眼
2026-10-10 07:24:15
阿根廷为什么沦落到要靠卖国籍来筹钱

阿根廷为什么沦落到要靠卖国籍来筹钱

小眼睛小世界
2026-10-10 09:12:00
牡丹花下死?汪峰将森林北踢出局,这次终于彻底印证葛荟婕的评价

牡丹花下死?汪峰将森林北踢出局,这次终于彻底印证葛荟婕的评价

历史的烟火
2026-08-14 15:48:04
近3亿人单身,结婚人数创40年新低!

近3亿人单身,结婚人数创40年新低!

城事堂
2026-10-08 15:59:00
65岁老同学想跟我搭伙过日子,他每月9000退休金全归我支配,刚开始觉得有了依靠,3个月后我后悔了

65岁老同学想跟我搭伙过日子,他每月9000退休金全归我支配,刚开始觉得有了依靠,3个月后我后悔了

蝉吟槐蕊
2026-10-08 12:09:30
美国富人为啥不来中国过安稳日子?其实在全球富人眼里,中国才是“最危险的”

美国富人为啥不来中国过安稳日子?其实在全球富人眼里,中国才是“最危险的”

扶苏聊历史
2026-10-10 10:37:15
龙凤胎高考出分哥哥690妹妹350,次日妹妹在家中失踪警方查后傻眼

龙凤胎高考出分哥哥690妹妹350,次日妹妹在家中失踪警方查后傻眼

罪案洞察者
2025-10-18 10:08:44
游本昌遗产公开后,全网惊觉:原来我们一直都小看了“济公”

游本昌遗产公开后,全网惊觉:原来我们一直都小看了“济公”

古事寻踪记
2026-10-08 11:44:44
蔡天凤银行账户被曝不足600万,房产已抵押,蔡母称亲家给了三亿

蔡天凤银行账户被曝不足600万,房产已抵押,蔡母称亲家给了三亿

无处不风景love
2026-10-08 18:40:56
秦桧32世孙发出质疑:祖上跪岳飞800年我认了,凭啥还跪岳飞之母

秦桧32世孙发出质疑:祖上跪岳飞800年我认了,凭啥还跪岳飞之母

古渡观书
2026-09-24 20:30:56
侠客岛谈64岁蔡康永现身“台独”分子竞选会场:蔡康永故意装“中立”,至今未认错,大是大非不容骑墙,“两面人”行为必须承受后果和代价

侠客岛谈64岁蔡康永现身“台独”分子竞选会场:蔡康永故意装“中立”,至今未认错,大是大非不容骑墙,“两面人”行为必须承受后果和代价

大风新闻
2026-10-09 21:24:07
开场连得9分,全场轰17+2+7!火箭主控复出即巅峰,2500万真不贵

开场连得9分,全场轰17+2+7!火箭主控复出即巅峰,2500万真不贵

熊哥爱篮球
2026-10-10 11:36:39
女子去无人区自驾游拍照,岂料隔天警方上门:这张照片你仔细看

女子去无人区自驾游拍照,岂料隔天警方上门:这张照片你仔细看

罪案洞察者
2025-12-08 11:19:52
奥委会这下尴尬了:2036年奥运会,中国上海、成都、广州都没申办

奥委会这下尴尬了:2036年奥运会,中国上海、成都、广州都没申办

轻扬墨雨
2026-08-14 13:21:23
两名中国游客溺亡,知情人士:两人是夫妻,今年9月底结婚,来马尔代夫度蜜月;事发度假村仍正常营业;我大使馆回应

两名中国游客溺亡,知情人士:两人是夫妻,今年9月底结婚,来马尔代夫度蜜月;事发度假村仍正常营业;我大使馆回应

南方都市报
2026-10-09 15:52:20
2米13!2米31!广东男篮欲裁萨姆纳,瞄准NBA伊克,卢卡也危险了

2米13!2米31!广东男篮欲裁萨姆纳,瞄准NBA伊克,卢卡也危险了

体坛小快灵
2026-10-10 10:01:29
2026-10-10 12:24:49
腾讯技术工程
腾讯技术工程
不止于技术
1458文章数 601关注度
往期回顾 全部

科技要闻

上世纪成熟的踏板,为何今天还会断裂?

头条要闻

牛弹琴:诺奖没颁给特朗普当事人很生气 严重后果来了

头条要闻

牛弹琴:诺奖没颁给特朗普当事人很生气 严重后果来了

体育要闻

与C罗硬碰硬,一个72岁老人的倔强

娱乐要闻

王仁君获飞天视帝,赵丽颖发文祝贺

财经要闻

蹭尊界流量?岚图汽车在“玩火”

汽车要闻

2027款深蓝L06及追风版上市 限时权益价11.49万元起

态度原创

本地
艺术
旅游
房产
军事航空

本地新闻

踏访沙县第一村,寻国民小吃本源

艺术要闻

21幅 野兽派创始人‌ 马蒂斯作品集二

旅游要闻

杭州:千岛湖隧道艺术馆焕发文旅新活力

房产要闻

475万人,猛增13.1%!国庆海南,又爆了?

军事要闻

美乌将举行新一轮会谈

无障碍浏览 进入关怀版