十六、委托
UE4 中Delegate(委托)使用频率非常高,常用的委托有单播、多播、动态多播、事件等等.
本篇将讲解这些委托的使用方法,注意事项等知识.
一.什么是Delegate(委托)?
Delegate委托,又称代理,本质是一个特殊类的对象,它内部可以储存(一个或多个)函数指针、调用参数和返回值;委托的触发者不与监听者有直接关联,两者通过委托对象间接地建立联系;监听者通过将响应函数绑定到委托上,使得委托触发时立即收到通知,并进行相关逻辑处理。委托的作用如同函数指针,但它更安全(支持编译期类型安全检查),并且更易于使用。
声明委托——顾客点外卖指定某个餐馆(委托),绑定委托——餐馆(委托)指名(绑定到)某个员工(对象)去送餐(对象的成员函数),执行委托——员工送餐(调用对象的成员函数)
委托的声明绑定可以在回调函数的类身上;委托的声明绑定也可以在执行回调函数的类身上;
二.Delegate的意义
1.将实时监测逻辑转换为触发式逻辑
一般我们不会一直在每帧调用的方法里面写一些触发判断,毕竟这太蠢了。
描述一个简单容易理解的小例子:
如果要实现物体从A移动到B点,在到达B点时打印一句"到达目标点!".
如果使用检测式逻辑:或许你会在使用Timer或者Tick每隔一段时间判断一下是否到达B点,这样会导致判断逻辑会在到达B点之前一直在重复执行,如果判断逻辑较为复杂,则会导致程序会高频率执行一段运算复杂逻辑,导致性能降低.并且如果使用的Timer间隔比较长(Tick及时事件间隔短但是还是会有延迟),可能会导致物体到达时,Timer倒计时还没到,导致判定失准!
如果使用触发式逻辑:在B点放置一个BoxTrigger,当物体到达B点时触发Overlap回调时执行打印,这么做避免了高频率执行判断逻辑,降低了性能的损耗,并且精确度比检测式要更高!(box trigger也是委托的一种形式)
2.降低耦合性
可以看到,如果我们想要在B中调用A类中的Call函数,我们必须在B中创建一个A类型的指针a,然后通过指针a去调用Call函数。这样就会导致B类跟A类存在了耦合关系.
如果C++看不出关系我们就用蓝图来解释耦合性



B想要执行A里面的函数,那么首先要得到A类,再去执行A里面的函数PrintA

3.Delegate伪代码演示AB之间不存在耦合性
不一定要看懂这串伪代码,但是要看清B类中没有A类指针,依然执行A类中的函数
三.使用委托的大致流程
①使用DECLARE_*宏声明一个自定义delegate类型FDelegateXXX,通过声明的FDelegateXXX类型创建一个委托对象
② 绑定需要执行的函数指针到代理对象上(this或者类的指针)
③ 执行代理对象中的函数指针会立即执行
④ 不需要某个函数指针时,可将其从代理对象中解绑
四.Delegate的讲解
1.单播委托(不支持蓝图)
单播委托只能绑定一个回调函数,新绑定的会覆盖旧的,因此被称为单播
三种单播:无参数无返回值,有参数无返回值,有参数有返回值
(1)定义委托创建委托对象,定义委托以F开头
(2)绑定回调函数的几种方式
①BindUObject

BindUObject 参数讲解:
InUserObject:绑定的回调函数属于那个类就传入这个类的指针变量;委托跟回调函数在同一个类就用this
InFunc:被绑定的回调函数,注意,InFunc是InUserObject的成员函数
注意理解!! InUserObject是InFunc的拥有者,传入这两个参数的目的是为了确认委托要绑定函数(InFunc)属于那个类(InUserObject)
CallbackTarget类(自己定义的类)作为回调函数的拥有者,用于提供被绑定的回调函数(说人话就是委托触发后,执行那个类里面的那个函数)
其实这个CallBackTarget这个类里存在着我们需要触发后执行的函数的函数,这个类可以是任意的类;
②BindUFunction
BindUFunction 参数讲解:
InUserObject:绑定的回调函数属于那个类就传入这个类的指针变量;在同一个类就用this
InFunctionName:回调函数的函数名.
注意!! ue4之所以仅根据函数名就能正确绑定回调函数,是通过ue4的反射机制,这就要求被绑定的函数必须要被UFUNCTION()宏包住,否则无法正确的找到对应的函数导致绑定失败.
注意理解!! InUserObject是InFunc的拥有者,传入这两个参数的目的是为了确认委托要绑定函数(InFunc)属于那个类(InUserObject)
③BindStatic
BindStatic可以绑定静态成员函数,也可以绑定全局静态函数(静态非成员函数)
④BindLambda
Lambda表达式,不清楚的请跳转到下面链接进行了解

(3)触发(执行)回调函数的写法
重点解释一下:无反回值回调是ExecuteIfBound()而有返回值的要用Execute()


(4)委托解绑写法

单播委托的小案例
委托/回调函数/执行回调函数都在同一个类中的案例(传this):BindObject
委托声明绑定在回调函数的类身上

在UE中新建一个继承与Actor的C++类:CPP_TestBindUObject_FunClass
.h文件
.CPP文件
委托与执行回调函数一个类,回调函数一个类中的案例(传类的指针):BindObject

回调函数的类的代码
.h文件
.cpp文件
委托及执行回调函数的类的代码
.h文件
.Cpp文件
利用委托开关门的案例:走到触发盒子里面开门,离开触发盒子关门
Trigger.h文件
Trigger.Cpp文件
Door.h文件
Door.Cpp文件
重点讲一下案例2.3中的创建类的指针:由于绑定余姚类的指针所以在这里讲一下
案例2:在场景中没有此actor用spawn实例化一个actor
案例3:在场景中有door的一个actor了,所以我们用get actor of class
get actor of class最终返回AActor*,需要得到AMyDoor还需要cast一下

延伸一下:当场景中有多个door的actor,只需要里面几扇门打开
需要传进去一个AActor*的引用,是空的,填充作用

当场景中有多个door的actor,每扇门都需要打开
利用getallactorofclass填充数组,在将数组进行遍历

2.多播委托(不支持蓝图)
相较于单播委托,多播委托可以绑定多个回调函数,当其触发时,所有绑定的回调函数都会执行;多播委托和单播委托定义方式很相似,除了多播委托没有返回值,这里我们只用 有参数的多播委托进行讲解
(1)定义委托
(2)绑定回调函数
(3)执行多播委托
(4)解绑多播委托
3.动态多播委托(支持蓝图)
与蓝图的事件分发器是一回事
(1)定义委托
(2)绑定回调函数
(3)执行多播委托
(4)解绑多播委托
(5)动态多播蓝图中的用法
创建一个Test_Dynamic_Multicast_Delegate的c++类
.h文件中声明委托与创建委托对象
创建一个BP_DynMulDelegate的蓝图类继承Test_Dynamic_Multicast_DelegateC++类

可以看到我们C++中定义的动态多播对象在蓝图中以EventDispatcher形式存在

动态多播委托与蓝图交互
新建一个c++类:Test_Dynamic_Multicast_Delegate
.h文件(委托声明 创建委托对象 回调函数声明)
.cpp文件 (委托绑定回调函数 执行委托 回调函数的实现)
创建一个BP_DynMulDelegate的蓝图类继承Test_Dynamic_Multicast_DelegateC++类


也可以在C++中不绑定,只在蓝图中绑定

(6)TriggerBox本质也是一个C++的动态多播委托

C++中的OnComponentBeginOverlap



4.Even(事件) 基本用不到,简单了解一下
Event声明于类内,通常将Event对象设为私有,类外通过公开的访问接口进行绑定,触发,解绑,这种方式起到了保护隐私的作用
(1)Even的定义

DECLARE_EVEN_xxxParams(OwningType, EventName, Param1Type)
OwingType:拥有此Event的类
EventName:事件名称
ParamType:参数列表
(2)绑定回调函数写法
(3)绑定回调函数写法
(4)解绑回调函数
以上就是四种常用Delegate的用法和注意事项.