欢迎光临散文网 会员登陆 & 注册

十六、委托

2023-02-10 18:49 作者:爱玩UE5的小哥哥  | 我要投稿

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++绑定的函数与蓝图中绑定的逻辑一块执行,通过Broadcast执行

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

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

蓝图中的OnComponentBeginOverlap

C++中的OnComponentBeginOverlap

动态多播6参数

4.Even(事件)   基本用不到,简单了解一下

Event声明于类内,通常将Event对象设为私有,类外通过公开的访问接口进行绑定,触发,解绑,这种方式起到了保护隐私的作用

(1)Even的定义

当只有一个参数的时候是Param,多个是Params

DECLARE_EVEN_xxxParams(OwningType, EventName, Param1Type)

OwingType:拥有此Event的类

EventName:事件名称

ParamType:参数列表

(2)绑定回调函数写法

(3)绑定回调函数写法

(4)解绑回调函数

以上就是四种常用Delegate的用法和注意事项.

十六、委托的评论 (共 条)

分享到微博请遵守国家法律