UEC++ 代理/委托
代理:
- 代理可以帮助我们解决一对一或是一对多的任务分配工作。主要可以帮助我们解决通知问题。我们可以通过代理完成调用某一个对象的一个函数,而不直接持有该对象的任何指针。
- 代理就是为你跑腿送信的,你可以不用关心给送信的目标人具体是谁,只要按照约定好的信件格式进行送信即可
- 更简单理解,想去调用某个函数,但并不是直接去调用,而是通过另一个入口去调用(代理)
分类:
- 单播代理 只能进行通知一个人
- 多播代理 可以进行多人通知
- 动态代理 可以被序列化(这体现在于蓝图进行交互,C++中可以将通知事件进行蓝图广播)
单播代理:
通过宏进行构建,单播代理只能绑定一个通知对象,无法进行多个对象通知、
单播代理分为有返回值与无返回值两种
代理可使用声明宏
函数签名 |
声明宏 |
---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
常用绑定函数:
- BindUObject 绑定UObject类型对象成员函数的代理
- BindSP 绑定基于共享引用的成员函数代理
- BindRaw 绑定原始自定义对象成员函数的代理,操作调用需要注意执行需要检查
- IsBound BindStatic 绑定全局函数成为代理
- UnBind 解除绑定代理关系
注意:绑定中传递的对象类型必须和函数指针所属类的类型相同否则绑定会报错
调用执行:
- 为了保证调用的安全性,执行Execute函数之前需要检查是否存在有效绑定使用函数、
- IsBound Execute 调用代理通知,不安全,需要注意
- ExecuteIfBound 调用代理通知,安全,但是有返回类型的回调函数无法使用此函数执行回调
- IsBound 检查当前是否存在有效代理绑定
构建步骤:
- 通过宏进行声明代理对象类型(根据回调函数选择不同的宏)
- 使用代理类型进行构建代理对象
- 绑定回调对象,和操作函数
- 执行代理对象回调
// Actor1.h // 头文件下 DECLARE_DELEGATE(DelegateOne) DECLARE_DELEGATE_RetVal_OneParam(int32 ,DelegateTwo, int32) // 变量声明 class AActor2* ac2; DelegateOne DegOne; DelegateTwo DegTwo; // Actor1.cpp // 这里将代码写在了BeginPlay中,方便测试 ac2 = GetWorld()->SpawnActor<AActor2>(AActor2::StaticClass()); // 绑定无参无返回值单播代理 DegOne.BindUObject(ac2, &AActor2::CallBackNone); DegOne.ExecuteIfBound(); // 绑定有参有返回值单播代理 DegTwo.BindUObject(ac2, &AActor2::CallBackRes); int32 num = 0; num = DegTwo.Execute(100); UKismetSystemLibrary::PrintString(this, FString::Printf(TEXT("%d"),num)); /////////////////////////////////////////////////////// // Actor2.h //声明两个被用来绑定的的函数 void CallBackNone(); int32 CallBackRes(int32 num); // Actor2.cpp void AActor2::CallBackNone() { UKismetSystemLibrary::PrintString(this, TEXT("无返回值无参数函数调用!")); } int32 AActor2::CallBackRes(int32 num) { UKismetSystemLibrary::PrintString(this, TEXT("有返回值有参数函数调用!")); return num; }
测试结果:
多播代理:
无法构建具有返回值的多播代理——多播代理无返回值
DECLARE_MULTICAST_DELEGATE[_Const, _RetVal, _etc.] (DelegateName)
函数 |
说明 |
---|---|
“Add()” |
将函数委托添加到该多播委托的调用列表中。 |
“AddStatic()” |
添加原始C++指针全局函数委托。 |
“AddRaw()” |
添加原始C++指针委托。原始指针不使用任何类型的引用,因此如果从委托下面删除了对象,则调用此函数可能不安全。调用Execute()时请小心! |
“AddSP()” |
添加基于共享指针的(快速、非线程安全)成员函数委托。共享指针委托保留对对象的弱引用。 |
“AddUObject()” |
添加基于UObject的成员函数委托。UObject委托保留对对象的弱引用。 |
“Remove()” |
从该多播委托的调用列表中删除函数(性能为O(N))。请注意,委托的顺序可能不会被保留! |
“RemoveAll()” |
从该多播委托的调用列表中删除绑定到指定UserObject的所有函数。请注意,委托的顺序可能不会被保留! |
广博: 调用函数Broadcast,但是调用不保证执行顺序的正确性
构建步骤:
- 使用宏构建代理类型
- 使用代理类型构建多播代理对象
- 添加绑定代理
- 执行调用
多播代理执行使用的是 Broadcast() 进行执行函数
动态代理:
- 允许被序列化的数据结构,这将使得代理可以被数据化提供给蓝图进行使用,达到在CPP中调用代理广播,事件通知到蓝图中。
- 动态代理和普通代理基本相同,分为单向和多向,动态代理无法使用带有返回值的函数进行构建(动态单播除外,并且单播无法在蓝图中绑定无法使用宏BlueprintAssignable修饰)
- UE中的大部分通知事件均使用动态代理(方便蓝图操作),如碰撞通知
动态单播代理:
- DECLARE_DYNAMIC_DELEGATE[_Const, _RetVal, etc.]( DelegateName )
动态多播代理:
- DECLARE_DYNAMIC_MULTICAST_DELEGATE[_Const, _RetVal, etc.]( DelegateName )
操作函数:
- BindDynamic( UserObject, FuncName ) 在动态代理上调用BindDynamic()的辅助宏。
- AddDynamic( UserObject, FuncName ) 在动态多播代理上调用AddDynamic()的辅助宏。
- RemoveDynamic( UserObject, FuncName ) 在动态多播代理上调用RemoveDynamic()的辅助宏。
与单播多播区别:
- 动态代理构建类型名称需要用 F 开头(动态代理实现机制构建了类)
- 动态代理对象类型可以使用UPROPERTY标记,其他代理均无法使用(不加编译可过,调用出错)
- 动态代理绑定对象的函数需要使用UFUNCTION进行描述(因为需要跟随代理被序列化)
构建:
// Actor1.h DECLARE_DYNAMIC_DELEGATE(FDelegateTree); // 注意分号 // 变量定义 class AActor2* ac2; FDelegateTree DegTree; // Actor1.cpp ac2 = GetWorld()->SpawnActor<AActor2>(AActor2::StaticClass()); DegTree.BindDynamic(ac2, &AActor2::CallBackNone); if (DegTree.IsBound()) { DegTree.ExecuteIfBound(); } ////////////////////////////////////////////////////////// // Actor2.h UFUNCTION() void CallBackNone(); // Actor2.cpp void AActor2::CallBackNone() { UKismetSystemLibrary::PrintString(this, TEXT("无返回值无参数函数调用!")); }
测试结果:
动态代理用于蓝图:
在构建动态代理提供蓝图使用时,需要在代理上增加标记宏UPROPERTY(BlueprintAssignable)