UE4中的delegate(委托)常用于解耦不同对象之间的关联:委托的触发者不与监听者有直接关联,两者通过委托对象间接地建立联系
监听者通过将响应函数绑定到委托上,使得委托触发时立即收到通知,并进行相关逻辑处理
委托,又称代理,本质是一个特殊类的对象,它内部可以储存(一个或多个)函数指针、调用参数和返回值
委托的作用如同函数指针,但它更安全(支持编译期类型检查),而且更易于使用
UE4在DelegateCombinations.h提供了一些宏来方便开发者快速来自定义一个delegate类型
大致地使用流程如下:
① 使用DECLARE_*宏声明一个自定义delegate类型FDelegateXXX
② 声明一个FDelegateXXX类型的代理对象
③ 绑定需要执行的函数指针到代理对象上
④ 触发代理对象中的函数指针会立即执行
⑤ 不需要某个函数指针时,可将其从代理对象中解绑
UE4提供了五类Delegate:
名称
宏
是否支持反射
以及序列化
能否有返回值
是否支持
Payload参数
触发代理
绑定个数
支持的绑定实例类型
单播代理
DECLARE_DELEGATE_*
DECLARE_DELEGATE_RetVal_*
否
可以有
是
① 无返回值情况
bool ExecuteIfBound(...)
void Execute(...)
② 有返回值情况
RetValue Execute(...)
注:RetValue为绑定函数
的返回值
1个
① Static函数 // BindStatic
② 普通C++对象的成员函数 // BindRaw
③ Lambda表达式 // BindLambda
// BindWeakLambda
④ 与某个UObject对象关联的Lambda表达式
⑤ 共享引用包裹的普通C++对象的成员函数 // BindSP
// BindThreadSafeSP
⑥ 线程安全的共享引用包裹的普通C++对象的成员函数
⑦ UObject对象的成员函数 // BindUObject
⑧ UObject对象的UFunction成员函数 // BindUFunction
多播代理
(组播代理)
DECLARE_MULTICAST_DELEGATE_*
否
无
是
void Broadcast(...)
多个
① Static函数 // AddStatic
② 普通C++对象的成员函数 // AddRaw
③ Lambda表达式 // AddLambda
// AddWeakLambda
④ 与某个UObject对象关联的Lambda表达式
⑤ 共享引用包裹的普通C++对象的成员函数 // AddSP
// AddThreadSafeSP
⑥ 线程安全的共享引用包裹的普通C++对象的成员函数
⑦ UObject对象的成员函数 // AddUObject
⑧ UObject对象的UFunction成员函数 // AddUFunction
--------------------------------------
另外,可以创建FDelegate对象,然后调用Add函数
事件
DECLARE_EVENT_*
否
无
是
void Broadcast(...)
多个
同上
动态代理
DECLARE_DYNAMIC_DELEGATE_*
DECLARE_DYNAMIC_DELEGATE_RetVal_*
是
可以有
否
① 无返回值情况
bool ExecuteIfBound(...)
void Execute(...)
② 有返回值情况
RetValue Execute(...)
注:RetValue为绑定函数
的返回值
1个
// BindUFunction
UObject对象的UFunction成员函数
--------------------------------------
另外,可以使用BindDynamic宏来绑定
动态多播代理
DECLARE_DYNAMIC_MULTICAST_DELEGATE_*
是
蓝图中可使用
无
否
void Broadcast(...)
多个
// Add函数、AddDynamic宏
// AddUnique函数、AddUniqueDynamic宏
UObject对象的UFunction成员函数
注1:Playload为代理绑定时传递的额外参数变量列表,这些参数会存储在代理对象内部;在触发代理时,Playload会紧跟着Execute、ExecuteInBound或Broadcast传入的参数之后,填充到绑定函数指针的参数列表中,然后执行
DECLARE_DELEGATE_OneParam(FTestDelegate, int);static void StaticDelegateProc(intnCode)
{
UE_LOG(LogTemp, Log, TEXT("StaticDelegateProc : %d"), nCode);
}static void StaticDelegateProcTest(int nCode, floatdis, FString txt)
{
UE_LOG(LogTemp, Log, TEXT("StaticDelegateProcTest : %d %f %s"), nCode, dis, *txt);
}
FTestDelegate DelegateObj1;
DelegateObj1.BindStatic(StaticDelegateProc);
DelegateObj1.ExecuteIfBound(1);//Playload示例FTestDelegate DelegateObj2;
DelegateObj2.BindStatic(StaticDelegateProcTest,12.0f, FString(TEXT("Hello"))); //12.0f、FString(TEXT("Hello"))为绑定时传入的Playload参数
DelegateObj2.ExecuteIfBound(2);
注2:动态代理、动态多播代理不支持Playload,因此,绑定函数与代理对象的参数、返回值必须完全一致
下面为一些类型示例:
① 单播代理
DECLARE_DELEGATE( FSimpleDelegate ); // 无参、无返回值
DECLARE_DELEGATE_OneParam(FPakEncryptionKeyDelegate, uint8[32]); // 1个参数、无返回值
DECLARE_DELEGATE_TwoParams(FPakSigningKeysDelegate, TArray&, TArray&); // 2个参数、无返回值
DECLARE_DELEGATE_RetVal_ThreeParams(bool, FOnMountPak, const FString&, int32, IPlatformFile::FDirectoryVisitor*); // 3个参数、bool返回值
② 多播代理
DECLARE_MULTICAST_DELEGATE( FSimpleMulticastDelegate ); // 无参
DECLARE_MULTICAST_DELEGATE_TwoParams(FOnTreeStarted, const UBehaviorTreeComponent&, const UBehaviorTree& ); // 2个参数
DECLARE_MULTICAST_DELEGATE_FourParams(FOnOpenURL, UIApplication*, NSURL*, NSString*, id); // 4个参数
③ 事件
DECLARE_EVENT(UWorld, FOnTickFlushEvent); // 无参
DECLARE_EVENT_OneParam(IWebBrowserWindow, FOnTitleChanged, FString); // 1个参数
④ 动态代理
DECLARE_DYNAMIC_DELEGATE(FOnGameWindowCloseButtonClickedDelegate); // 无参、无返回值
DECLARE_DYNAMIC_DELEGATE_OneParam(FOnAssetLoaded, class UObject*, Loaded); // 1个参数、无返回值
DECLARE_DYNAMIC_DELEGATE_RetVal(EMouseCursor::Type, FGetMouseCursor); // 无参、EMouseCursor::Type返回值
DECLARE_DYNAMIC_DELEGATE_RetVal_OneParam(UWidget*, FGenerateWidgetForObject, UObject*, Item); // 1个参数、UWidget*返回值
DECLARE_DYNAMIC_DELEGATE_RetVal_TwoParams(FEventReply, FOnPointerEvent, FGeometry, MyGeometry, const FPointerEvent&, MouseEvent); // 2个参数、FEventReply返回值
⑤ 动态多播代理
DECLARE_DYNAMIC_MULTICAST_DELEGATE( FLevelStreamingLoadedStatus ); // 无参
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FLandedSignature, const FHitResult&, Hit); // 1个参数
DECLARE_DYNAMIC_MULTICAST_DELEGATE_ThreeParams(FMovementModeChangedSignature, class ACharacter*, Character, EMovementMode, PrevMovementMode, uint8, PreviousCustomMode); // 3个参数
单播代理
1. 定义代理类型
① 定义返回值为void含1个int类型参数的单播代理类型FCharacterDelegate
DECLARE_DELEGATE_OneParam(FCharacterDelegate, int); // TBaseDelegate
② 定义返回值为bool含1个int类型参数的单播代理类型FCharacterDelegate_RetVal
DECLARE_DELEGATE_RetVal_OneParam(bool, FCharacterDelegate_RetVal, int); // TBaseDelegate
2. 声明代理对象
FCharacterDelegate CharacterDelegate;
FCharacterDelegate_RetVal CharacterDelegateRetVal;
以FCharacterDelegate为例,讲述单播代理的绑定、触发执行及存在的风险
/*** TPSProjectCharacter.h ***/UCLASS()class UDelegatepTestClass : publicUObject
{
GENERATED_BODY()protected:int m_nValue = 0;public:void DelegateProc1(intnCode)
{this->m_nValue =nCode;
UE_LOG(LogTemp, Log, TEXT("DelegateProc1 : %d"), nCode);
}
UFUNCTION()void DelegateUFunctionProc1(intnCode)
{this->m_nValue =nCode;
UE_LOG(LogTemp, Log, TEXT("DelegateUFunctionProc1 : %d"), nCode);
}
};classDelegateCppTestClass
{int m_nValue = 0;public:void CppDelegateProc(intnCode)
{this->m_nValue =nCode;
UE_LOG(LogTemp, Log, TEXT("CppDelegateProc : %d"), nCode);
}void CppDelegateProc2(intnCode)
{this->m_nValue =nCode;
UE_LOG(LogTemp, Log, TEXT("CppDelegateProc2 : %d"), nCode);
}void CppDelegateProc3(intnCode)
{this->m_nValue =nCode;
UE_LOG(LogTemp, Log, TEXT("CppDelegateProc3 : %d"), nCode);
}void CppDelegateProc4(intnCode)
{this->m_nValue =nCode;
UE_LOG(LogTemp, Log, TEXT("CppDelegateProc4 : %d"), nCode);
}void CppDelegateProc5(intnCode)
{this->m_nValue =nCode;
UE_LOG(LogTemp, Log, TEXT("CppDelegateProc5 : %d"), nCode);
}
};
UCLASS(config=Game)class ATPSProjectCharacter : publicACharacter
{
GENERATED_BODY()public:static void StaticCharacterDelegateProc(intnCode);voidOnBind();voidOnExecute();voidOnUnbind();
FCharacterDelegate CharacterDelegate1;
FCharacterDelegate CharacterDelegate2;
FCharacterDelegate CharacterDelegate3;
FCharacterDelegate CharacterDelegate4;
FCharacterDelegate CharacterDelegate5;
FCharacterDelegate CharacterDelegate6;
FCharacterDelegate CharacterDelegate7;
FCharacterDelegate CharacterDelegate8;
FCharacterDelegate CharacterDelegate9;
FCharacterDelegate CharacterDelegate10;
FCharacterDelegate CharacterDelegate11;
FCharacterDelegate CharacterDelegate12;
FCharacterDelegate CharacterDelegate13;
};/*** TPSProjectCharacter.cpp ***/
static void StaticDelegateProc(intnCode)
{
UE_LOG(LogTemp, Log, TEXT("StaticDelegateProc : %d"), nCode);
}void ATPSProjectCharacter::StaticCharacterDelegateProc(intnCode)
{
UE_LOG(LogTemp, Log, TEXT("StaticCharacterDelegateProc : %d"), nCode);
}voidATPSProjectCharacter::OnBind()
{//Bind Static
CharacterDelegate1.BindStatic(StaticDelegateProc);
CharacterDelegate2.BindStatic(ATPSProjectCharacter::StaticCharacterDelegateProc);//Bind Raw
DelegateCppTestClass Obj1;
CharacterDelegate3.BindRaw(&Obj1, &DelegateCppTestClass::CppDelegateProc);//Bind Lambda
auto LambdaDelegateProc = [](int nCode)->void{
UE_LOG(LogTemp, Log, TEXT("LambdaDelegateProc : %d"), nCode);
};
CharacterDelegate4.BindLambda(LambdaDelegateProc);
CharacterDelegate5.BindLambda(
[](int nCode)->void{
UE_LOG(LogTemp, Log, TEXT("LambdaDelegateProc2 : %d"), nCode);
}
);//Bind Weak Lambda
auto WeakLambdaDelegateProc = [](int nCode)->void{
UE_LOG(LogTemp, Log, TEXT("WeakLambdaDelegateProc : %d"), nCode);
};
UDelegatepTestClass* UObj1 = NewObject(this, UDelegatepTestClass::StaticClass());
CharacterDelegate6.BindWeakLambda(UObj1, WeakLambdaDelegateProc);
UDelegatepTestClass* UObj2 = NewObject(this, UDelegatepTestClass::StaticClass());
CharacterDelegate7.BindWeakLambda(
UObj2, [](int nCode)->void{
UE_LOG(LogTemp, Log, TEXT("WeakLambdaDelegateProc2 : %d"), nCode);
}
);//Bind SP(Shared Pointer)
TSharedRef ObjSP1 = MakeShareable(newDelegateCppTestClass());
CharacterDelegate8.BindSP(ObjSP1,&DelegateCppTestClass::CppDelegateProc2);
TSharedRef ObjSP2 = MakeShared();
CharacterDelegate9.BindSP(ObjSP2,&DelegateCppTestClass::CppDelegateProc3);//Bind Thread Safe SP(Shared Pointer)
TSharedRef ObjSafeSP1 = MakeShareable(newDelegateCppTestClass());
CharacterDelegate10.BindThreadSafeSP(ObjSafeSP1,&DelegateCppTestClass::CppDelegateProc4);
TSharedRef ObjSafeSP2 = MakeShared();
CharacterDelegate11.BindThreadSafeSP(ObjSafeSP2,&DelegateCppTestClass::CppDelegateProc5);//Bind UObject
UDelegatepTestClass* UObj3 = NewObject(this, UDelegatepTestClass::StaticClass());
CharacterDelegate12.BindUObject(UObj3,&UDelegatepTestClass::DelegateProc1);//Bind UFunction
UDelegatepTestClass* UObj4 = NewObject(this, UDelegatepTestClass::StaticClass());
CharacterDelegate13.BindUFunction(UObj4, STATIC_FUNCTION_FNAME(TEXT("UDelegatepTestClass::DelegateUFunctionProc1")));
}voidATPSProjectCharacter::OnExecute()
{
CharacterDelegate1.ExecuteIfBound(1);
CharacterDelegate2.ExecuteIfBound(2);
CharacterDelegate3.ExecuteIfBound(3);
CharacterDelegate4.ExecuteIfBound(4);
CharacterDelegate5.ExecuteIfBound(5);
CharacterDelegate6.ExecuteIfBound(6);
CharacterDelegate7.ExecuteIfBound(7);
CharacterDelegate8.ExecuteIfBound(8);
CharacterDelegate9.ExecuteIfBound(9);
CharacterDelegate10.ExecuteIfBound(10);
CharacterDelegate11.ExecuteIfBound(11);
CharacterDelegate12.ExecuteIfBound(12);if(CharacterDelegate13.IsBound())
{
CharacterDelegate13.Execute(13);
}
}voidATPSProjectCharacter::OnUnbind()
{
CharacterDelegate1.Unbind();
CharacterDelegate2.Unbind();
CharacterDelegate3.Unbind();
CharacterDelegate4.Unbind();
CharacterDelegate5.Unbind();
CharacterDelegate6.Unbind();
CharacterDelegate7.Unbind();
CharacterDelegate8.Unbind();
CharacterDelegate9.Unbind();
CharacterDelegate10.Unbind();
CharacterDelegate11.Unbind();
CharacterDelegate12.Unbind();
CharacterDelegate13.Unbind();
}
注1:BindRaw函数用于绑定普通c++对象的成员函数,若该c++对象已被销毁,触发代理执行该对象的成员函数,将会导致内存违规操作
注2:BindLambda函数用于绑定lambda表达式,若lambda表达式捕获外部变量已被销毁,触发代理执行lambda表达式,将会导致内存违规操作
注3:BindWeakLambda、BindUObject、BindUFunction绑定时会与一个UObject对象进行弱引用关联(不影响该对象被gc回收)
若UObject对象被gc回收,直接调用Execute触发代理,将会导致内存违规操作;可先调用IsBound或调用ExecuteIfBound触发代理来检查该UObject的有效性,再来执行代理
注4:BindSP、BindThreadSafeSP绑定时会与一个智能指针对象进行弱引用关联(不影响该对象的内存回收)
若智能指针对象的内存被回收,直接调用Execute触发代理,将会导致内存违规操作;可先调用IsBound或调用ExecuteIfBound触发代理来检查该智能指针对象的有效性,再来执行代理
注5:单播代理对象在被销毁时,会在其析构函数中调用Unbind进行解绑操作
注6:不同的绑定实例类型定义在:DelegateInstancesImpl.h
BindStatic(绑定实例类型为:TBaseStaticDelegateInstance)
BindRaw(绑定实例类型为:TBaseRawMethodDelegateInstance)