Unreal Engine 中的 Delegates(委托)详解
Delegates(委托)是 Unreal Engine 中一种强大的事件通知机制,它允许开发者在不直接引用类的情况下调用成员函数。本质上,委托是一种类型安全的函数指针,特别适合用于实现观察者模式、事件系统和回调机制。
基本概念
核心特点
- 类型安全:编译时会检查函数签名匹配
- 动态绑定:可以在运行时绑定/解绑函数
- 多播能力:可以同时通知多个接收者(Multicast Delegates)
- 序列化支持:部分委托类型支持蓝图序列化
委托类型
Unreal Engine 提供了几种主要的委托类型:
1. 单播委托 (Singlecast Delegates)
1 2 3
| DECLARE_DELEGATE(MyDelegate); DECLARE_DELEGATE_OneParam(MyParamDelegate, FString); DECLARE_DELEGATE_TwoParams(MyTwoParamDelegate, int32, bool);
|
2. 多播委托 (Multicast Delegates)
1 2
| DECLARE_MULTICAST_DELEGATE(MyMulticastDelegate); DECLARE_MULTICAST_DELEGATE_OneParam(MyMulticastParamDelegate, float);
|
3. 动态委托 (Dynamic Delegates)
1 2
| DECLARE_DYNAMIC_DELEGATE(FMyDynamicDelegate); DECLARE_DYNAMIC_MULTICAST_DELEGATE(FMyDynamicMulticastDelegate);
|
4. 事件 (Events)
1
| DECLARE_EVENT(MyOwnerClass, MyEvent);
|
使用示例
单播委托示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
|
DECLARE_DELEGATE_OneParam(FScoreChangedDelegate, int32);
class AMyActor : public AActor { public: FScoreChangedDelegate OnScoreChanged; void UpdateScore(int32 NewScore) { OnScoreChanged.ExecuteIfBound(NewScore); } };
class AMyListener { public: void BindToScoreChange(AMyActor* Actor) { Actor->OnScoreChanged.BindUObject(this, &AMyListener::HandleScoreChanged); } void HandleScoreChanged(int32 NewScore) { UE_LOG(LogTemp, Warning, TEXT("Score changed to %d"), NewScore); } };
|
执行流程
sequenceDiagram
participant 调用方 as 调用方(AMyActor)
participant 委托系统 as 委托系统(OnScoreChanged)
participant 监听方 as 监听方(AMyListener)
Note over 监听方: 初始化阶段
监听方->>委托系统: BindUObject(this, &AMyListener::HandleScoreChanged)
Note right of 委托系统: 存储绑定信息:
- 目标对象(this)
- 函数指针(HandleScoreChanged)
Note over 调用方: 执行阶段
调用方->>委托系统: ExecuteIfBound(NewScore)
委托系统->>监听方: 检查目标对象有效性
alt 对象有效
监听方-->>委托系统: 确认可调用
委托系统->>监听方: 调用HandleScoreChanged(NewScore)
监听方->>监听方: 执行日志输出 UE_LOG(...)
else 对象无效
委托系统--x 监听方: 跳过执行(自动清理绑定)
end
流程图有时候网页上无法基于代码绘制显示,再附一张截图

播委托示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| DECLARE_MULTICAST_DELEGATE(FDeathDelegate);
class AMyCharacter { public: FDeathDelegate OnDeath; void Die() { OnDeath.Broadcast(); } };
class AListener1 { void HandleDeath() { } }; class AListener2 { void HandleDeath() { } };
|
动态委托(可用于蓝图)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| DECLARE_DYNAMIC_DELEGATE_OneParam(FBlueprintDelegate, FString, Message);
UCLASS() class UMyBlueprintCallableObject : public UObject { GENERATED_BODY() public: UFUNCTION(BlueprintCallable) void SetupDelegate(FBlueprintDelegate Delegate) { MyDelegate = Delegate; } void TriggerDelegate(FString Message) { MyDelegate.ExecuteIfBound(Message); } private: FBlueprintDelegate MyDelegate; };
|
绑定方式
Unreal 提供了多种绑定方法:
绑定方法 |
描述 |
示例 |
BindUObject |
绑定到UObject成员函数 |
Delegate.BindUObject(this, &MyClass::Func) |
BindStatic |
绑定静态函数 |
Delegate.BindStatic(&MyStaticFunc) |
BindRaw |
绑定原始C++对象 |
Delegate.BindRaw(RawPtr, &RawClass::Func) |
BindLambda |
绑定Lambda表达式 |
Delegate.BindLambda([](){ /*...*/ }) |
BindSP |
绑定共享指针对象 |
Delegate.BindSP(SharedPtr, &MyClass::Func) |
最佳实践
- 选择合适的委托类型:
- 1对1通知 → 单播委托
- 1对多通知 → 多播委托
- 需要蓝图支持 → 动态委托
- 内存管理:
- 使用
BindUObject
或BindSP
而不是BindRaw
,以避免悬空指针
- 在对象销毁前调用
Unbind()
或RemoveAll()
- 性能考虑:
- 调试技巧:
- 使用
ExecuteIfBound
而不是Execute
来避免崩溃
- 使用
IsBound()
检查是否有绑定
与蓝图交互
动态委托特别适合与蓝图交互:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnHealthChanged, float, NewHealth);
UCLASS() class UHealthComponent : public UActorComponent { GENERATED_BODY() public: UPROPERTY(BlueprintAssignable) FOnHealthChanged OnHealthChanged; void TakeDamage(float Amount) { Health -= Amount; OnHealthChanged.Broadcast(Health); } };
|
在蓝图中,你可以直接为这个委托添加事件:
- 创建HealthComponent变量
- 拖出变量引脚 → “Assign OnHealthChanged”
- 连接自定义事件
Unreal Engine 中的 Callbacks(回调)的作用以及与委托的关系
在 Unreal Engine(虚幻引擎)中,委托(Delegates)和回调(Callbacks) 是紧密相关的概念,但它们的设计目的、实现方式和应用场景存在关键差异。以下是两者的深度对比和关系解析:
1. 核心定义
概念 |
委托(Delegates) |
回调(Callbacks) |
本质 |
类型安全的函数包装器,支持多播和动态绑定 |
通常是函数指针或简单函数对象 |
Unreal中的实现 |
通过DECLARE_DELEGATE 等宏定义 |
原生C++函数指针或std::function |
典型用途 |
事件系统、跨模块通信、蓝图交互 |
底层API响应、异步操作完成通知 |
2. 关键关系
委托是回调的增强版
共同点
- 都用于解耦调用方和被调用方
- 都遵循**“订阅-通知”**模式
- 都能处理异步操作完成事件
核心差异
特性 |
委托 |
回调 |
绑定方式 |
支持UObject成员函数、Lambda等 |
通常仅限静态函数或裸函数指针 |
线程安全 |
可配置(ESPMode::ThreadSafe ) |
需手动实现 |
多播支持 |
原生支持(DECLARE_MULTICAST_DELEGATE ) |
需自行维护列表 |
生命周期管理 |
自动处理UObject有效性 |
需手动检查对象存活 |
蓝图集成 |
动态委托可直接暴露给蓝图 |
无法直接使用 |
3. Unreal Engine 中的典型场景
委托的适用场景
1 2 3 4 5 6 7
| DECLARE_MULTICAST_DELEGATE(FOnCharacterDied); FOnCharacterDied OnDeath;
HealthComponent->OnDeath.AddUObject(this, &AAchievementSystem::HandleDeath); UIWidget->OnDeath.AddLambda([](){ ShowGameOverScreen(); });
|
回调的适用场景
1 2 3 4 5 6 7 8 9 10 11 12 13
| void LoadFile(const FString& Path, TFunction<void(bool)> Callback) { AsyncTask([Path, Callback](){ bool bSuccess = FFileHelper::LoadFileToArray(...); Callback(bSuccess); }); }
LoadFile("Save.dat", [](bool bSuccess){ UE_LOG(LogTemp, Warning, TEXT("Load result: %d"), bSuccess); });
|
4. 性能与内存对比
指标 |
委托 |
回调 |
调用开销 |
较高(虚函数+安全性检查) |
较低(直接调用) |
内存占用 |
较大(存储绑定信息) |
较小(仅存储函数指针) |
适用频率 |
推荐用于高频事件(如每帧更新) |
适合低频一次性操作 |
5. 相互转换与协作
将回调封装为委托
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| void ThirdPartyLib_SetCallback(void(*Callback)(int32)) { }
DECLARE_DELEGATE_OneParam(FMyDelegate, int32);
void SetupAdapter(FMyDelegate& Delegate) { ThirdPartyLib_SetCallback([](int32 Val){ Delegate.ExecuteIfBound(Val); }); }
|
委托降级为回调
1 2 3 4 5 6 7 8 9 10
| static FMyDelegate StaticDelegate;
extern "C" void RegisterCallback(void(*Callback)(int32)) { StaticDelegate.BindStatic(Callback); }
StaticDelegate.Broadcast(42);
|
6. 设计选择指南
选择委托 当… |
选择回调 当… |
需要多播通知 |
仅需单次响应 |
要与蓝图交互 |
纯C++模块内部使用 |
涉及UObject生命周期 |
处理底层API或第三方库集成 |
需要线程安全保证 |
性能敏感且无需复杂功能 |
7. 底层实现窥探
委托的内存结构(简化)
1 2 3 4 5 6 7
| struct FDelegateInstance { void* ObjectPtr; void* FunctionPtr; FDelegateHandle Handle; ESPMode ThreadSafety; };
|
回调的典型实现
1 2 3
| typedef TFunction<void(int32)> FSimpleCallback; TArray<FSimpleCallback> CallbackList;
|
总结
在Unreal Engine中:
- 委托 是引擎层面对回调概念的高级封装,提供类型安全、多播、蓝图集成等特性
- 回调 更接近原生C++实现,适合轻量级、高性能场景
- 两者可相互配合,构建从底层到高层的完整事件系统
正确选择的标准:
优先使用委托处理游戏逻辑事件,仅在需要与底层API交互或极端性能优化时使用原生回调。