目录
委托 可理解为是一种类型安全的函数指针容器,存储目标对象及其成员函数的地址;
- 委托可动态绑定到任意对象的成员函数,之后在该对象上调用函数,即使调用者不知对象类型;
- 委托可很安全的复制;
- 也可值传递委托,但这样操作需要在堆上分配内存,因此通常并不推荐;请尽量通过引用传递委托;
UE 共支持三种类型的委托:
Single
Multicast
Dynamic (UObject, serializable)
注:
- Event Dispatcher 为蓝图原生的委托,本质上是动态多播委托;
- 蓝图中的内置事件
- 一种是已绑定到动态多播委托上的回调函数(
UFUNCTION),会在触发时调用Broadcasst(),如OnComponentHit/OnComponentBeginOverlap、InputAction/EnhancedInputAction;- 一种是 C++ 虚函数,如
BeginPlay、Tick、EndPlay、Destroyed;如BeginPlay事件对应的是ReceiveBeginPlay();- 蓝图中的自定义事件只是普通函数(
UFUNCTION);
一,Single
Declaring Delegates
使用宏声明委托,根据绑定的函数签名来选择宏,每个宏都包含新委托类型的名称,返回类型(如不是void)及其参数;委托签名声明可存在于全局范围内、命名空间内、甚至类声明内,可能不存在于函数体内;当前,支持以下委托签名:
- 返回一个值的函数;
- 声明为 const 函数;
- 最多4个"payload"变量;
- 最多8个函数参数;
// 无返回值声明
void Function()
---> DECLARE_DELEGATE(DelegateName)
void Function(Param1)
---> DECLARE_DELEGATE_OneParam(DelegateName, Param1Type)
void Function(Param1, Param2)
---> DECLARE_DELEGATE_TwoParams(DelegateName, Param1Type, Param2Type)
void Function(Param1, Param2, ...)
---> DECLARE_DELEGATE_<Num>Params(DelegateName, Param1Type, Param2Type, ...)
// 有返回值声明
<RetValType> Function()
---> DECLARE_DELEGATE_RetVal(RetValType, DelegateName)
<RetValType> Function(Param1)
---> DECLARE_DELEGATE_RetVal_OneParam(RetValType, DelegateName, Param1Type)
<RetValType> Function(Param1, Param2)
---> DECLARE_DELEGATE_RetVal_TwoParams(RetValType, DelegateName, Param1Type, Param2Type)
<RetValType> Function(Param1, Param2, ...)
---> DECLARE_DELEGATE_RetVal_<Num>Params(RetValType, DelegateName, Param1Type, Param2Type, ...)
Binding Delegates
将委托绑定到UObject或共享指针类的成员, 委托系统可保留对该对象的弱引用,因此该对象在代理之外被销毁时,可通过调用 IsBound() 或 ExecuteIfBound() 函数进行判断;
委托绑定:“对象实例 + 成员函数指针”,函数应为公有的;
注意各类受支持对象的特殊绑定语法;
| Function | Description |
| Bind | 绑定到现有委托对象; |
| BindStatic | 绑定原始C++指针全局函数委托; |
| BindRaw | 绑定原始C++指针委托; 由于原始指针不使用任何类型的引用,因此在删除目标对象后调用 Execute 或 ExecuteIfBound 会不安全; |
| BindLambda | 绑定一个functor,通常用于拉姆达函数; |
| BindSP | 绑定基于指针的共享成员函数委托; 共享指针委托会保留对对象的弱引用; 可使用 ExecuteIfBound() 进行调用; |
| BindUObject | 绑定 UObject 的成员函数委托;UObject 委托会保留对对象 UObject 的弱引用;可使用 ExecuteIfBound() 进行调用; |
| UnBind | 取消绑定此委托; |
注,..\Engine\Source\Runtime\Core\Public\Delegates\DelegateSignatureImpl.inl 可了解这些函数的变体、参数和实现;
Payload Data
在绑定到委托时,可传递有效负载数据(可以是任意变量);在绑定函数被调用时,会直接传递给任何绑定函数;所有委托类型(除“dynamic”外)都自动支持有效负载变量;
// 此例将两个自定义变量(一个布尔,一个int32)传递到委托;
// 调用该委托时,参数将被传到绑定函数;额外的变量参数必须始终在委托类型参数之后被接受;
MyDelegate.BindRaw( &MyFunction, true, 20 );
Executing Delegates
通过调用委托的 Execute() 函数执行绑定到委托的函数;执行前须检查委托是否已绑定,以使代码更安全;因为有时委托可能含有未初始化且被后续访问的返回值和输出参数; 执行未绑定的委托在某些情况下确实可能导致内存混乱;
可调用 IsBound() 检查是否可安全执行委托; 同时,对于无返回值的委托,可调用 ExecuteIfBound(),但需注意输出参数可能未初始化;
Execute // 执行一个委托而不检查其绑定情况;
ExecuteIfBound // 检查委托是否已绑定,如是,则调用Execute;
IsBound // 检查委托是否已绑定,通常在包含Execute调用的代码之前进行;
Example Usage
// 假定类拥有可在任何地方随意调用的方法 class FLogWriter { public: void WriteToLog(FString); };// 首先需使用宏声明委托;要调用 WriteToLog 函数,需创建该函数签名的委托类型; // 创建名为 FStringDelegate 的委托类型,使用单个参数 FString ; DECLARE_DELEGATE_OneParam(FStringDelegate, FString); class FMyClass { // 在类中创建 FStringDelegate 对象 FStringDelegate WriteToLogDelegate; };// 委托时只需创建委托类的实例,将拥有该方法的类作为模板参数传递; // 传递对象的实例和实际函数地址; TSharedRef<FLogWriter> LogWriter(new FLogWriter()); WriteToLogDelegate.BindSP(LogWriter, &FLogWriter::WriteToLog);// FMyClass 即可调用 WriteToLog 方法,甚至无需了解 FLogWriter 类的任何信息! WriteToLogDelegate.Execute(TEXT("Delegates are great!")); // 多数情况下,建议使用以下操作 WriteToLogDelegate.ExecuteIfBound(TEXT("Only executes if a function was bound!"));
二,Multicast
多播委托具有与单播委托大多数相同的功能;仅对对象具有弱引用,可与结构体一起使用,可轻松地复制等;与常规委托一样,多播委托可以被加载/保存,并可以远程触发;但多播委托函数不能使用返回值;
// 声明多播委托,与声明标准委托方式上相同
DECLARE_MULTICAST_DELEGATE[_RetVal, ...]\( DelegateName \)
DECLARE_DYNAMIC_MULTICAST_DELEGATE[_RetVal, ...]\( DelegateName \)
| Function | Description |
| Add() | 将函数添加到该多播委托的调用列表中 |
| AddStatic() | 添加原始C++指针全局函数委托; |
| AddRaw() | 添加原始C++指针委托; 原始指针不使用任何类型的引用,因此如果从委托下面删除了对象,则调用此函数可能不安全。调用Execute()时请小心! |
| AddLambda() | 添加一个 functor,通常用于那些不会捕获指针的lambda函数; |
| AddSPLambda() | 添加一个 functor,只有在对目标共享指针的弱引用有效的情况下才会被调用; |
| AddWeakLambda() | 添加一个 functor,只有在对UObject的弱引用有效的情况下才会被调用; |
| AddSP() | 添加基于共享指针的(快速、非线程安全)成员函数委托; 共享指针委托保留对对象的弱引用; |
| AddUObject() | 添加基于UObject的成员函数委托; UObject委托保留对对象的弱引用; |
| Remove() | 从该多播委托的调用列表中删除函数(性能为O(N)); 请注意,委托的顺序可能不会被保留! |
| RemoveAll() | 从该多播委托的调用列表中删除绑定的所有函数; 请注意,委托的顺序可能不会被保留! 删除绑定到所提供指针的所有已注册委托,未绑定到对象指针的原始委托不会被该函数所删除! |
注,..\Engine\Source\Runtime\Core\Public\Delegates\DelegateSignatureImpl.inl 可了解这些函数的变体、参数和实现;
// 将该委托广播给所有绑定的对象;
// 调用该函数总是安全的,即使没有任何绑定时也是如此;
// 唯一需要注意的是,如使用委托来初始化输出变量,通常会带来非常不利的后果;
// 调用时绑定函数的执行顺序尚未定义,执行顺序可能与函数的添加顺序不相同;
Broadcast()
三,Dynamic
动态委托可序列化且支持反射的委托,通过反射来查找函数名称以实现绑定,但其执行速度比常规委托慢;其绑定的函数需使用UFUNCTUION()标记;宏声明的类型名称必须以 F 开头,最多支持 9 个参数(在多使用结构体封装);
// 声明动态委托,与声明标准委托方式上相同
// 动态单播委托
DECLARE_DYNAMIC_DELEGATE[_RetVal, ...]\( DelegateName \)
// 动态多播委托,最常用,支持蓝图;
DECLARE_DYNAMIC_MULTICAST_DELEGATE[_RetVal, ...]\( DelegateName \)
// 委托函数支持与UFunctions相同的说明符,但是使用 UDELEGATE 宏而不是 UFUNCTION;
// 参数禁止引用,宏内必须同时写参数类型和参数名;
UDELEGATE(BlueprintAuthorityOnly)
DECLARE_DYNAMIC_MULTICAST_DELEGATE_FourParams(FInstigatedAnyDamageSignature, float, Damage, const UDamageType*, DamageType, AActor*, DamagedActor, AActor*, DamageCauser);
| Function | Description |
| BindDynamic( UserObject, FuncName ) | 用于在动态委托上调用BindDynamic()的辅助宏,自动生成函数命名字符串; |
| AddDynamic( UserObject, FuncName ) | 用于在动态多播委托上调用AddDynamic()的辅助宏,自动生成函数命名字符串; |
| RemoveDynamic( UserObject, FuncName ) | 用于在动态多播委托上调用RemoveDynamic()的辅助宏,自动生成函数命名字符串; |
// 执行动态委托
Execute // 执行一个委托而不检查其绑定情况;
ExecuteIfBound // 检查委托是否已绑定,如是,则调用Execute;
IsBound // 检查委托是否已绑定,通常在包含Execute调用的代码之前进行;
// Button.h 源代码头文件中声明的动态多播 DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnButtonClickedEvent); DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnButtonPressedEvent); DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnButtonReleasedEvent); DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnButtonHoverEvent);
// UButton 成员变量
UPROPERTY(BlueprintAssignable, Category="Button|Event") // BlueprintAssignable:允许在蓝图中绑定事件
FOnButtonClickedEvent OnClicked;
// .h 文件设置属性
UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (BindWidget))
TObjectPtr<UButton> Start;
UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (BindWidget))
TObjectPtr<UButton> Quit;
// .cpp 文件
bool UMyUserWidget::Initialize() {
if(!Super::Initialize())
return false;
Start->OnClicked.AddDynamic(this, &UMyUserWidget::StartFunc);
Quit->OnClicked.AddDynamic(this, &UMyUserWidget::QuitFunc);
return true;
}
四,稀疏动态多播
稀疏动态多播与标准的动态多播功能基本一致,但核心区别在于它是一种内存优化方案;
- 标准动态多播,包含该委托的对象,无论是否有人监听,都会为其分配存储调用列表的内存,开销较大;
- 适用于高频触发、或绝大多数实例都会被监听的事件;
- 稀疏动态多播,在没有人绑定时,只占用1个字节的内存(仅存储一个
bIsBound标志),只有当第一次有人绑定时,才会在全局存储中创建真正的调用列表;- 适用于低频触发、绝大多数实例不会被监听的事件;

| 蓝图事件节点 | 对应的C++委托类型 | 对应头文件 | 说明 |
On Component Hit | FComponentHitSignature | UPrimitiveComponent | 碰撞命中 |
On Component Begin Overlap | FComponentBeginOverlapSignature | 开始重叠 | |
On Component End Overlap | FComponentEndOverlapSignature | 结束重叠 | |
On Component Wake | FComponentWakeSignature | 物理模拟唤醒 | |
On Component Sleep | FComponentSleepSignature | 物理模拟休眠 | |
On Component Physics State Changed | FComponentPhysicsStateChanged | 物理状态变化 | |
On Begin Cursor Over | FOnBeginCursorOver | 鼠标悬停开始 | |
On End Cursor Over | FOnEndCursorOver | 鼠标悬停结束 | |
On Clicked | FOnClicked | 鼠标点击 | |
On Released | FOnReleased | 鼠标释放 | |
On Input Touch Begin/End/Enter/Leave | FOnInputTouchBegin | 触摸输入事件 | |
Physics Volume Changed | FPhysicsVolumeChanged | SceneComponent.h | 物理体积改变 |
On Component Activated | FActorComponentActivatedSignature | ActorComponent.h | 组件激活 |
On Component Deactivated | FActorComponentDeactivateSignature | 组件失活 |
// PrimitiveComponent.h 源代码头文件内声明的稀疏动态多播 DECLARE_DYNAMIC_MULTICAST_SPARSE_DELEGATE_FiveParams( FComponentHitSignature, UPrimitiveComponent, OnComponentHit, UPrimitiveComponent*, HitComponent, AActor*, OtherActor, UPrimitiveComponent*, OtherComp, FVector, NormalImpulse, const FHitResult&, Hit ); DECLARE_DYNAMIC_MULTICAST_SPARSE_DELEGATE_SixParams( FComponentBeginOverlapSignature, UPrimitiveComponent, OnComponentBeginOverlap, UPrimitiveComponent*, OverlappedComponent, AActor*, OtherActor, UPrimitiveComponent*, OtherComp, int32, OtherBodyIndex, bool, bFromSweep, const FHitResult &, SweepResult); DECLARE_DYNAMIC_MULTICAST_SPARSE_DELEGATE_FourParams( FComponentEndOverlapSignature, UPrimitiveComponent, OnComponentEndOverlap, UPrimitiveComponent*, OverlappedComponent, AActor*, OtherActor, UPrimitiveComponent*, OtherComp, int32, OtherBodyIndex); DECLARE_DYNAMIC_MULTICAST_SPARSE_DELEGATE_TwoParams(FComponentWakeSignature, UPrimitiveComponent, OnComponentWake, UPrimitiveComponent*, WakingComponent, FName, BoneName); DECLARE_DYNAMIC_MULTICAST_SPARSE_DELEGATE_TwoParams(FComponentSleepSignature, UPrimitiveComponent, OnComponentSleep, UPrimitiveComponent*, SleepingComponent, FName, BoneName); DECLARE_DYNAMIC_MULTICAST_SPARSE_DELEGATE_OneParam(FComponentCollisionSettingsChangedSignature, UPrimitiveComponent, OnComponentCollisionSettingsChangedEvent, UPrimitiveComponent*, ChangedComponent); DECLARE_DYNAMIC_MULTICAST_SPARSE_DELEGATE_TwoParams(FComponentPhysicsStateChanged, UPrimitiveComponent, OnComponentPhysicsStateChanged, UPrimitiveComponent*, ChangedComponent, EComponentPhysicsStateChange, StateChange); DECLARE_DYNAMIC_MULTICAST_SPARSE_DELEGATE_OneParam( FComponentBeginCursorOverSignature, UPrimitiveComponent, OnBeginCursorOver, UPrimitiveComponent*, TouchedComponent ); DECLARE_DYNAMIC_MULTICAST_SPARSE_DELEGATE_OneParam( FComponentEndCursorOverSignature, UPrimitiveComponent, OnEndCursorOver, UPrimitiveComponent*, TouchedComponent ); DECLARE_DYNAMIC_MULTICAST_SPARSE_DELEGATE_TwoParams( FComponentOnClickedSignature, UPrimitiveComponent, OnClicked, UPrimitiveComponent*, TouchedComponent , FKey, ButtonPressed); DECLARE_DYNAMIC_MULTICAST_SPARSE_DELEGATE_TwoParams( FComponentOnReleasedSignature, UPrimitiveComponent, OnReleased, UPrimitiveComponent*, TouchedComponent, FKey, ButtonReleased); DECLARE_DYNAMIC_MULTICAST_SPARSE_DELEGATE_TwoParams( FComponentOnInputTouchBeginSignature, UPrimitiveComponent, OnInputTouchBegin, ETouchIndex::Type, FingerIndex, UPrimitiveComponent*, TouchedComponent ); DECLARE_DYNAMIC_MULTICAST_SPARSE_DELEGATE_TwoParams( FComponentOnInputTouchEndSignature, UPrimitiveComponent, OnInputTouchEnd, ETouchIndex::Type, FingerIndex, UPrimitiveComponent*, TouchedComponent ); DECLARE_DYNAMIC_MULTICAST_SPARSE_DELEGATE_TwoParams( FComponentBeginTouchOverSignature, UPrimitiveComponent, OnInputTouchEnter, ETouchIndex::Type, FingerIndex, UPrimitiveComponent*, TouchedComponent ); DECLARE_DYNAMIC_MULTICAST_SPARSE_DELEGATE_TwoParams( FComponentEndTouchOverSignature, UPrimitiveComponent, OnInputTouchLeave, ETouchIndex::Type, FingerIndex, UPrimitiveComponent*, TouchedComponent );
// UPrimitiveComponent 成员变量,BlueprintAssignable允许蓝图内Bind Event节点绑定
UPROPERTY(BlueprintAssignable, Category="Collision")
FComponentBeginOverlapSignature OnComponentBeginOverlap;
// 调用 AddDynamic 绑定组件的重叠事件
// 触发重叠事件还需:双方都开启Generate Overlap Events,碰撞预设为Overlap,静态网格体要有碰撞体
// AddDynamic()辅助宏,用于动态多播委托(实际调用__Internal_AddDynamic)
myCube->OnComponentBeginOverlap.AddDynamic(this, &AMyActor::OnOverlapBegin);
// 回调函数必须标记UFUNCTION(),且参数完全一致
UFUNCTION()
void OnOverlapBegin(UPrimitiveComponent* OverlappedComponent,
AActor* OtherActor,
UPrimitiveComponent* OtherComp,
int32 OtherBodyIndex,
bool bFromSweep,
const FHitResult& SweepResult);
注意:BeginPlay 的执行顺序(先是所有Actor C++的BeginPlay -> 所有Actor 蓝图的BeginPlay -> 关卡蓝图的BeginPlay);
363



被折叠的 条评论
为什么被折叠?



