中EN
概念页

委托与 Tick 机制

Delegates & Tick

更新于 2026-10-12 约 12 分钟UE5委托事件TickTickGroup多播委托动态委托

UE 有两种驱动游戏逻辑的方式:事件驱动——委托 (Delegate) 在某事发生时回调绑定的函数;轮询驱动——Tick 每帧(或按最小间隔)调用 Actor/组件的更新函数,并按 Tick 组 (Tick Group) 与物理模拟排序。官方建议能用定时器、委托就不用 Tick,必须 Tick 时用 Tick 组和 Tick 依赖保证顺序,避免“差一帧”问题。

委托的三种类型

委托以类型安全的方式在将来调用任意对象的成员函数,调用方无需知道对象的类型。

类型声明宏绑定数返回值蓝图特点
单播 (Single)DECLARE_DELEGATE...1支持否支持载荷 (Payload)
多播 (Multicast)DECLARE_MULTICAST_DELEGATE...多个不支持否Broadcast() 调用全部
动态 (Dynamic)DECLARE_DYNAMIC_[MULTICAST_]DELEGATE...1 或多个单播支持是可序列化,按函数名查找,比普通委托慢,不支持载荷

声明规则:签名可带返回值、const、最多 4 个载荷变量和最多 8 个参数;宏后缀如 _OneParam、_TwoParams、_RetVal。声明可在全局、命名空间或类声明内,不能在函数体内。UDELEGATE(...) 可给委托加 UFunction 说明符,例如 BlueprintAuthorityOnly。

绑定方式与安全性

单播绑定多播添加目标安全性
BindUObjectAddUObjectUObject 成员函数保存弱引用,对象销毁后可用 IsBound/ExecuteIfBound 处理
BindSPAddSP共享指针对象弱引用;AddSP 快但非线程安全
BindRawAddRaw原生 C++ 对象无引用,对象删除后调用不安全
BindStaticAddStatic全局/静态函数—
BindLambdaAddLambdaLambda多播版用于不捕获指针的 lambda
—AddWeakLambda / AddSPLambda带弱引用的 Lambda目标 UObject / 共享指针仍有效时才调用
BindDynamicAddDynamic / RemoveDynamic动态委托辅助宏自动生成函数名字符串,目标须为 UFUNCTION

执行

  • 单播:Execute() 不检查绑定;未绑定时调用会触发断言,在某些情况下甚至可能写坏内存。应先 IsBound(),无返回值时可用 ExecuteIfBound()(仍要小心未初始化的输出参数)。
  • 多播:Broadcast() 即使没有绑定也总是安全,会跳过已失效的对象。调用顺序未定义,不一定按添加顺序。Remove() 为 O(N)O(N) 且可能打乱顺序;RemoveAll(对象) 移除绑定到该对象的全部函数,但不会移除未绑定对象指针的 Raw 委托。
  • 委托可以复制;按值传递会在堆上分配内存,应尽量按引用传递。

实际项目中的用法

// 头文件:声明一个可在蓝图中绑定的事件
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnHealthChanged, float, NewHealth);

UPROPERTY(BlueprintAssignable)
FOnHealthChanged OnHealthChanged;

// 改变生命值处
OnHealthChanged.Broadcast(Health);

// 订阅方(处理函数必须是 UFUNCTION)
HealthComp->OnHealthChanged.AddDynamic(this, &AMyHUDActor::HandleHealthChanged);
  • 需要蓝图绑定的事件(即蓝图里的事件分发器)用动态多播 + UPROPERTY(BlueprintAssignable);纯 C++ 内部回调用普通单播/多播,更快且支持载荷。
  • 引擎自身的大量事件就是委托,例如组件重叠事件 OnComponentBeginOverlap.AddDynamic(...),见 蓝图与 C++ 协作。
  • UFunction 可以绑定委托,常用于把操作绑定到用户输入。

Tick 机制

Tick 指在 Actor 或组件上按固定间隔(通常每帧一次)运行代码或蓝图脚本。可设置为每帧、按最小间隔或完全不 Tick。普通 UObject 没有内置更新能力,需要时可继承 FTickableGameObject 并实现 Tick()。

Tick 组顺序:每个 Tick 组中的所有 Actor 和组件都 Tick 完,下一组才开始。下图按一帧内的时间顺序排列。

flowchart LR
  A["TG_PrePhysics<br/>帧开始;物理数据是上一帧"] --> B["TG_DuringPhysics<br/>物理模拟已开始,数据可能是上一帧或本帧"]
  B --> C["TG_PostPhysics<br/>本帧物理已完成"]
  C --> D["引擎:潜在动作、定时器管理器、<br/>更新相机、关卡流送"]
  D --> E["TG_PostUpdateWork<br/>相机已更新"]
  E --> F["引擎:处理延迟生成的 Actor,<br/>结束帧并渲染"]
Tick 组适合放什么
TG_PrePhysics要与物理对象交互的 Actor(含基于物理的附着),让其移动参与本帧物理模拟
TG_DuringPhysics不关心物理数据或能容忍一帧延迟的逻辑,如背包界面、小地图;与物理并行,互不等待
TG_PostPhysics需要物体最终位置的武器/移动射线检测,如激光瞄准器(差一帧会很明显)
TG_PostUpdateWork依赖相机最终朝向的效果、给粒子系统喂最后时刻的数据、帧内最后裁决(如格斗游戏中两个角色同一帧互抓)

Tick 依赖:AddTickPrerequisiteActor / AddTickPrerequisiteComponent 让本对象等指定对象 Tick 完再执行。与其把一批对象挪到更晚的组,不如只对真正依赖的一两个对象设依赖——同组内的对象可以并行更新,这样更高效。不同组之间天然有序,无需再设依赖。

官方例子:玩家角色瞄准激光、准星落在命中点、HUD 显示充能条。

对象安排理由
动画角色PrePhysics先动画,物理对象才能正确跟随和交互
HUDDuringPhysics不用物理数据,没必要互相等待;代价是显示晚一帧
准星PostPhysics对最终场景做检测,正好落在表面上
激光粒子PostUpdateWork,或 PostPhysics + 依赖准星后者只等准星,不等其他 PostPhysics 对象,更高效

设置方式(通常在构造函数中,确保 BeginPlay 前生效;Actor 在 BeginPlay 中注册自身及组件的 Tick 函数):

PrimaryActorTick.bCanEverTick = true;
PrimaryActorTick.bTickEvenWhenPaused = true;
PrimaryActorTick.TickGroup = TG_PrePhysics;
// 组件用 PrimaryComponentTick,对应 TickComponent()
  • 运行时开关:SetActorTickEnabled / SetComponentTickEnabled。
  • 一个对象可以有多个 Tick 函数:继承 FTickFunction,重写 ExecuteTick 和 DiagnosticMessage;重写 AActor::RegisterActorTickFunctions,调用 SetTickFunctionEnable 与 RegisterTickFunction;用 AddPrerequisite 设依赖。可参考 EngineBaseTypes.h 中的 FActorTickFunction、FComponentTickFunction。
  • 组件可以和所属 Actor 处于不同的 Tick 组。

取舍:事件、定时器还是 Tick

方式优点缺点
委托 / 事件只在发生时执行,零空闲开销;解耦发送方与接收方多播顺序未定义;Raw 绑定有悬空风险;动态委托较慢
定时器适合延时、周期性动作(如拾取道具后无敌 10 秒、毒气每秒伤害)世界定时器管理器在 PostPhysics 之后、PostUpdateWork 之前统一处理,不能任意选择执行阶段
Tick适合连续变化、需与物理和相机精确对齐的逻辑每帧开销,实例多时累积明显(尤其蓝图)

官方建议:蓝图中用定时器或委托代替 Tick;Tick 依赖类且实例很多时是蓝图与 C++ 性能差异最大的场景之一。整体帧预算见 帧时间预算与渲染性能。

常见坑

  • 默认 Tick 组下读取物理结果却在 PrePhysics 中:拿到的是上一帧数据,表现为跟随、瞄准“慢一帧”。
  • 依赖多播委托的调用顺序实现逻辑:顺序未定义,随时可能改变。
  • 绑定 Raw 委托后忘记解绑而对象已删除:回调时崩溃;UObject 优先用 AddUObject/AddDynamic(弱引用)。
  • 动态委托按函数名查找绑定的函数,AddDynamic 的目标应是 UFunction(官方示例中的处理函数均为 UFUNCTION)。
  • GC 只在帧间运行,因此一帧内解引用一次弱指针后使用是安全的,见 垃圾回收与对象生命周期。

GEA 的视角:分阶段、分桶与 Tick 组(卷 II 17.6.3、17.7.4)

GEA 第 17 章描述的更新调度可以和本页的 Tick 机制逐项对照,详见 游戏对象的实时更新:

  • 分阶段更新:Naughty Dog 引擎每帧在动画混合前、混合后(最终姿势前)、最终姿势后三次更新对象;UE 的 Tick 组则以物理模拟为参照划分阶段(PrePhysics、DuringPhysics、PostPhysics、PostUpdateWork)。两者都是"在帧内固定位置运行对象逻辑"(对比归纳)。
  • 回调优于遍历:GEA 认为与其遍历所有对象调用每个阶段的钩子,不如只让需要的客户注册回调。本页记录的"能用定时器或委托就不用 Tick,可以关闭不需要的 Tick"体现了同一原则。
  • 分桶 vs Tick 依赖:GEA 按依赖森林的层级把对象分成固定的几个桶(载具/平台 → 角色 → 手持物),逐桶完整更新;UE 用 AddTickPrerequisiteActor/Component 为单个对象设依赖,并建议不要为此把一批对象挪到更晚的组,以保留同组内的并行。GEA 自己也说桶切换会引入同步点,是该方案的主要不足之一。
  • 一帧延迟:GEA 的规则是"对象状态在更新循环前后一致、循环中可能不一致",读取尚未更新的对象就会出现一帧延迟;本页 HUD 放在 DuringPhysics、"代价是显示晚一帧"的官方例子,就是有意接受这种延迟的一个选择。
  1. Delegates UE 5.8 文档
  2. Multi-cast Delegates UE 5.8 文档
  3. Dynamic Delegates UE 5.8 文档
  4. Actor Ticking UE 5.8 文档
  5. Objects in Unreal Engine UE 5.8 文档
  6. Coding in UE: Blueprint vs. C++ UE 5.8 文档
  7. UFunctions UE 5.8 文档
  8. Properties (UPROPERTY) UE 5.8 文档
  9. Game_Engine_Architecture_4th_Edtion_Volume_2_-_Jason_Gregory.pdf
  10. Jason Gregory《Game Engine Architecture》第 4 版(2026)卷 II 第 17 章 17.6.3 节
  11. Jason Gregory《Game Engine Architecture》第 4 版(2026)卷 II 第 17 章 17.7.4 节

相关页面

游戏开发 · UE5UObject 与反射系统UObject & Reflection System虚幻引擎 (Unreal Engine) 的对象基类是 UObject。在 C++ 类、属性、函数上加 UCLASS/UPROPERTY/UFUNCTION 等宏,再由构建前的预处理工具虚幻头文件工具 (Unreal Header Tool, UHT) 扫描头文件生成代码,引擎就能在运行时“看见”这些类型——这就是反射系统 (Reflection System)。垃圾回收、序列化、编辑器面板、蓝图、网络复制都建立在它之上。游戏开发 · UE5垃圾回收与对象生命周期Garbage Collection & Object LifecycleUE 用标记-清除式的可达性分析管理 UObject 内存:从根集合 (Root Set) 出发沿反射可见的强引用遍历对象图,找不到的对象在下次垃圾回收 (Garbage Collection, GC) 时被删除。因此“谁持有 UPROPERTY 引用”决定了对象活多久;Actor 调用 Destroy() 只是请求销毁,真正释放仍取决于引用是否全部放开。游戏开发 · UE5Gameplay 框架Gameplay FrameworkGameplay 框架 (Gameplay Framework) 是 UE 提供的一组相互配合的类,用来搭建游戏的基本结构:Actor 与组件 (Component) 构成场景中的一切;GameMode 在服务器上定规则并生成玩家;玩家由“控制器 (Controller) + 棋子 (Pawn)”组成;GameState 与 PlayerState 负责把全局与每个玩家的状态复制给所有客户端;GameInstance 跨关卡存活。游戏开发 · UE5蓝图与 C++ 协作Blueprint & C++ InteropEpic 官方建议:多数项目混用蓝图 (Blueprint) 与 C++,以 C++ 作为基础、在其上构建蓝图类。C++ 适合“编程”——定义系统;蓝图适合“脚本”——在已有系统上定义具体行为。两者通过反射说明符互相调用:C++ 用 BlueprintCallable/BlueprintReadWrite 等暴露给蓝图,用 BlueprintImplementableEvent/BlueprintNativeEvent 让蓝图实现或覆盖 C++ 声明的函数。游戏开发多线程渲染与任务系统Multithreaded Rendering & Task SystemsCPU 时钟频率约在 2003 年停在 3.4 GHz 附近后,性能来自多核。应用阶段的优化一是让代码与内存访问更缓存友好,二是利用多核:多处理器流水线(时间并行,提高吞吐但增加延迟)、并行处理(空间并行,延迟更低)以及两者结合的任务系统;图形 API 用命令缓冲让多个线程并行录制绘制命令,新一代 API(DirectX 12、Vulkan、Metal)把更多责任交给应用以降低驱动开销。综合帧时间预算与渲染性能Frame Budget & Rendering Performance本页把 wiki 中分散在 RTR4 第 18~23 章的性能知识串成一条线:一帧的时间预算由最慢的管线阶段决定,先用毫秒帧时间度量并定位瓶颈,再按瓶颈所在位置——CPU/驱动、几何、像素、带宽/内存——选用对应手段;最后从 GPU 硬件机制(占用率、HiZ/early-z、压缩、分块架构)理解这些手段为何有效。各结论的细节与出处见所链接的概念页。游戏开发 · UE5Gameplay 能力系统Gameplay Ability SystemGameplay 能力系统 (Gameplay Ability System, GAS) 是 UE 用来构建「Actor 可拥有并反复触发的能力」的框架:能力系统组件 (Ability System Component, ASC) 是持有者,Gameplay 能力 (Gameplay Ability) 编排执行过程,属性集 (Attribute Set) 存数值,Gameplay 效果 (Gameplay Effect, GE) 改数值和标签,Gameplay 标签 (Gameplay Tags) 做状态与互斥,Gameplay Cue 负责表现。它内建复制与客户端预测,代价是概念多、上手重,游戏开发 · UE5动画蓝图与动画系统Animation Blueprint & Animation SystemUE5 角色动画以动画蓝图 (Animation Blueprint) 为核心:它每帧为骨骼网格体 (Skeletal Mesh) 计算最终姿势。动画蓝图内部用状态机 (State Machine) 组织“何时播什么”,用混合空间 (Blend Space) 按速度、方向等参数连续混合,用蒙太奇 (Animation Montage) 在插槽 (Slot) 中插播攻击、交互等一次性动作,用 Control Rig 做程序化骨骼控制(如贴地)。算出的骨骼姿势最终交给蒙皮 (Skinning) 驱动顶点。游戏开发 · UE5网络复制与 RPCNetwork Replication & RPCUE 采用服务器权威 (Server-Authoritative) 的客户端-服务器模型:游戏真正在服务器上进行,服务器把状态复制 (Replication) 给客户端,客户端通过远程过程调用 (Remote Procedure Call, RPC) 向服务器请求操作。开发者需要显式声明哪些 Actor、属性、函数要走网络,并借助所有权、网络角色、相关性、优先级控制“发给谁、发多少”。UE 5.8 提供通用复制系统、Replication Graph 和实验性的 Iris 三套复制系统。游戏开发 · UE5UE5 性能分析UE5 ProfilingUE5 性能分析的基本流程是:先用 stat unit 判断瓶颈在游戏线程、渲染线程还是 GPU,再用对应的 stat 组、profilegpu/GPU 统计或 Unreal Insights 的 Timing/Memory 等视图向下钻取;渲染特性(RDG、VSM、TSR)各自提供作用域和可视化。测量时要避开会扭曲数据的设置(调试构建、异步计算、可视化模式)。游戏开发 · UE5UE5 渲染架构UE5 Rendering ArchitectureUE5 的渲染器是独立的 Renderer 模块,在渲染线程 (Rendering Thread) 上以「场景代理」镜像游戏线程的组件状态,每帧按固定顺序执行深度预通道、G-buffer 基础通道、光照、雾、半透明和后处理。现代渲染代码用渲染依赖图 (Render Dependency Graph, RDG) 声明通道与资源,由 RDG 自动排屏障、复用瞬态内存、并行录制;最终命令经 RHI 线程翻译成 D3D12/Vulkan 等图形 API 调用。默认走延迟着色 (Deferred Shading),也可切换前向着色 (Forward Shading)。游戏开发 · UE5存档系统SaveGameUE 的存档围绕自定义的 USaveGame 子类进行:保存时把需要持久化的数据从游戏世界拷进 SaveGame 对象,再用 UGameplayStatics 写入某个存档槽(槽名 + 用户索引);读档时反过来。官方推荐用 AsyncSaveGameToSlot 异步保存,避免掉帧,也避免某些平台的认证问题。游戏开发 · UE5任务系统Tasks SystemUE::Tasks 是 UE5 的任务级并行库,是对 TaskGraph 的改进,与 TaskGraph 共用同一套调度器和工作线程。它支持启动任务、等待或取结果、声明前置依赖、嵌套任务、管道 (Pipe) 和任务事件。官方建议用依赖关系把工作连成图,而不是阻塞等待;5.5 起用「超额订阅」取代了容易死锁的忙等待。游戏开发观察者模式Observer Pattern观察者模式 (Observer Pattern) 让一段代码宣布“发生了某件事”,而不必关心谁会收到通知:主体 (Subject) 维护一个观察者列表,在事件发生时逐个同步调用它们。它适合连接彼此几乎无关的子系统(例如物理与成就),但要注意同步阻塞、观察者的注销,以及运行时才能确定的通信关系难以推理。游戏开发游戏循环与更新方法Game Loop & Update Method游戏循环 (Game Loop) 让游戏时间的推进与玩家输入和处理器速度脱钩:每一轮非阻塞地处理输入、更新状态、渲染,并跟踪真实时间来控制节奏;最常推荐的做法是“固定更新步长、可变渲染”。更新方法 (Update Method) 是它的搭档:世界维护一组对象,每个对象实现一个模拟“一帧”行为的 update(),每帧依次调用。两者和组件模式一起常被视为游戏引擎的核心。游戏开发游戏对象的实时更新Updating Game Objects in Real Time游戏对象更新就是根据上一时刻状态 Si(t − Δt) 求出当前状态 Si(t)。"每帧对每个对象调一次虚函数 Update(),并在里面顺带更新它的动画、物理、渲染组件"的简单做法在商业引擎里行不通:底层子系统需要批量更新才能高效,而对象之间、子系统之间又有依赖顺序。GEA 介绍的对策依次是:子系统由游戏循环直接批量更新、对象只操纵子系统中的"代理状态";在帧内多个阶段挂回调;按依赖森林把对象分桶,逐桶运行完整的分阶段更新(Naughty Dog 的做法);并用状态缓存和时间戳对付更新过程中的状态不一致和一帧延迟(GEA4 卷 II 17.6)。游戏开发游戏对象并发更新Concurrent Game Object Updates游戏对象模型要和几乎必然是多线程的底层子系统打交道,所以对象代码必须异步思考:耗时操作用非阻塞请求尽早发起、尽晚取结果,必要时容忍一帧延迟。作业之间的依赖决定了并行度 (DOP),每个依赖都引入同步点,"优化并发设计的秘诀是延迟"。对象模型本身也能并行:Naughty Dog 在把引擎从 PS3 移植到 PS4(《最后生还者 重制版》)时,让每个桶内的对象更新成为作业;桶间访问无需加锁,桶内读取用对象快照,桶内修改尽量避免,或加锁、或放进请求队列、或交给下一桶中的同步作业(GEA4 卷 II 17.7)。游戏开发事件与消息传递Events & Message Passing游戏天然是事件驱动的:爆炸、玩家被敌人发现、血包被捡起都是事件,系统要通知感兴趣的对象并让它们以各自的方式处理。用虚函数 OnExplosion() 通知属于静态类型的后期绑定,迫使基类为所有可能的事件声明虚函数;GEA 主张把事件封装成对象(类型 + 参数),实现动态类型的后期绑定,从而获得单一处理函数、持久性和盲转发。GEA 依次讨论事件类型(枚举 vs 字符串/字符串 id)、参数(派生类、Variant 数组、键值对)、处理器、职责链、兴趣注册、排队与否的利弊,最后是数据驱动的事件系统和以数据通路连接端口的图形化系统(如 UE 蓝图)(GEA4 卷 II 17.8)。综合引擎运行时架构总览Engine Runtime Architecture Overview本页把 wiki 中分散在 GEA 第 1、6、8、17 章及 GPP、UE5 相关页面里的内容串成一条线,回答"一个游戏引擎在运行时是怎样组织起来的":静态结构上是自下而上的分层,上层依赖下层;启动关闭按依赖顺序显式进行;每一帧由主循环驱动各子系统批量更新,游戏对象的逻辑被安排在子系统阶段之间,并按依赖分桶或设先后关系;多核上一帧被拆成成百上千个作业;玩法基础系统(对象模型、事件、脚本、世界加载流送、高层流程)位于引擎与游戏的分界线附近。各结论的细节与出处见所链接的概念页。游戏开发 · UE5UMG、Slate 与 Common UIUMG, Slate & Common UISlate 是 UE 平台无关的 C++ 声明式 UI 框架(编辑器即用它构建),UMG 是在其之上的可视化控件蓝图层(UE 5.8 API:UWidget 是所有暴露给 UObject 的被包装 Slate 控件的基类),Common UI 插件再在 UMG 之上补充多层界面、输入路由、手柄导航、平台按键图标和样式资产。官方 UMG 最佳实践强调事件驱动更新而非绑定/Tick、固定分辨率与 DPI 制作、用 Scale Box 而非 Render Transform 做永久缩放。游戏开发事件队列Event Queue事件队列 (Event Queue) 用一个先进先出队列,把“发送消息或事件”和“处理它”在时间上解耦:发送方入队后立即返回,接收方在自己方便的时候再取出处理。它比观察者和命令模式多做的一件事是时间上的解耦,适合“推”与“拉”两端节奏不同的场合(例如音频请求),代价是全局可见、世界状态可能已经改变,以及可能出现隐蔽的反馈回环。综合游戏编程模式选型:解耦与性能Choosing Game Programming Patterns本页把《Game Programming Patterns》里的 19 个模式放到同一张取舍图上:大多数模式要么为了解耦(降低改代码时需要装进脑子里的知识量),要么为了性能(利用对数据和硬件的具体假设),而这两个目标经常互相拉扯。下面先讲全书的定位和 Nystrom 的架构观,再给出按问题选模式的路线和与 UE 的对照。游戏开发 · UE5资产管理与异步加载Asset Management & Async LoadingUE 资产默认按引用自动加载:硬引用 (Hard Reference) 会让被引用资产随引用者一起加载,不加控制就可能在启动时加载全部内容。软引用 (Soft Reference,TSoftObjectPtr/FSoftObjectPath) 只存路径,配合流式管理器 (Streamable Manager) 异步按需加载;资产管理器 (Asset Manager) 进一步把内容划分为主资产 (Primary Asset) 和次资产 (Secondary Asset),用资产包 (Asset Bundle) 管理加载、卸载、烘焙分块与内存审计。

被这些页面引用1 篇

输入关键词开始搜索

滚轮或双指缩放 · 拖动平移 · 双击放大 / 还原