UE4 代理(Delegate)的使用(1)

本文章只是我个人在学习虚幻智能指针过程中的理解,不一定正确,若有说的不对的地方,欢迎指正。

在学生时代,我们写程序的时,想要在一个A类中调用B类的方法函数的时候,我们一般会A类内保存一个B类的引用。通过引用来调用B类的函数,就像这样:

#include <iostream>

class B
{
public:
	//目标函数
	void Func() 
	{
		printf("Call Func\n");
	}
};

//A内保存对B类一个实例的引用
class A
{
public:
	//目标函数所在对象
	B BInstance;
};

int main()
{
	//创建一个A对象
	A* AInstance = new A;

	//通过B实例类调用Func
	AInstance->BInstance.Func();
}

但这样做会造成A类与B类的耦合(A类要知道B类的类型才能获得引用),造成某些不好的影响。而在虚幻引擎里写复杂代码的过程中为了减少类似的情况发生,也为了代码便于后期维护,引擎提供了代理

那么什么是代理?

代理就是夹在A类和B类中间的类型,A类需要调用B类的某个函数,它可以通过保存在类内的代理对象来调用这个函数。而由于代理是通用的,只需要知道函数的返回值和参数类型就可以绑定不同的方法,A类并不需要函数类的具体消息,从而实现了解耦
在这里插入图片描述

一.虚幻代理的分类

虚幻代理按照可以绑定的函数个数分为单播多播,又按照是否暴露给蓝图分为静态动态,最后再加一个委托类型——事件

1.单播代理:

代理和函数提供者之间是一对一的关系,调用者执行代理,只有一个函数提供者会受到通知然后执行。单播代理被重复绑定时,后一个绑定的函数会覆盖前一个函数。

2.多播代理:

代理和函数提供者之间是一对多的关系,调用者执行代理,会有多个函数提供者会受到通知然后执行。因为是一对多的关系,所以多播代理不会出现后面绑定的函数覆盖前一个函数的现象。

3.动态单播和动态多播:

通过名字可以知道,也是一对一和一对多的关系,但是这两种代理可以暴露给蓝图,绑定函数可以在蓝图中进行。

二.虚幻的代理

我们可以根据要绑定函数的返回值类型和参数的个数和类型,在虚幻引擎提供的代理中选正确的代理,虚幻提供的代理最终都是用宏来封装的(挖坑:代理的源码后面会写篇文章来讲讲)。目前虚幻的代理最多支持绑定九个参数的函数,为了方便使用,各个代理都是根据参数和返回值来命名的。

最简单的一参数静态单播代理:DECLARE_DELEGATE_OneParam,前面的DECLARE_DELEGATE是普遍使用的,后面的OneParam表示此代理可绑定携带一个参数的函数。当需要绑定有返回值的函数,只需要再在**_OneParam前加上_RetVal**即可。

静态多播则增加一个**_MULTICAST**,例如DECLARE_MULTICAST_DELEGATE_OneParam(携带一参数静态多播代理)。

若是要使用动态代理就在DECLAREDELEGATE之间加上DYNAMIC,例如DECLARE_DYNAMIC_DELEGATE_OneParam(携带一参数动态单播代理)。

动态多播与静态多播相似,例如DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(携带一参数动态多播代理)。

现在我们来看看虚幻引擎提供的代理的用法。虚幻代理的使用大致可分为三个步骤:1.声明代理;2.绑定函数;3.执行代理,这里用DECLARE_DELEGATE_OneParam来举例。

I.代理声明:

DECLARE_DELEGATE_OneParam可以传入DelegateName和Param1Type两个宏参数。顾名思义,DelegateName是代理名,实际上就是用该宏来生成一个类型是DelegateName的类。至于Param1Type,很简单,就是参数的类型。

接着用这个类生成一个代理对象,使用对象来绑定函数。下面来看一下实际代码:

//声明一个带int32类型参数无返回值的代理
DECLARE_DELEGATE_OneParam(MyDelegateName, int32);
//生成代理对象
MyDelegateName MyDelegateInstance;

当函数有返回值时就使用DECLARE_DELEGATE_RetVal_OneParam,然后在宏参数列表的最前面多传入一个RetValType即可:

//声明一个带int32类型参数返回int32类型的代理
DECLARE_DELEGATE_RetVal_OneParam(int32, MyRetDelegateName, int32);
//生成代理对象
MyRetDelegateName MyRetDelegateInstance;

注意:动态代理声明时传入的DelegateName要在前面写一个’F’,这是虚幻引擎反射系统的要求

II.绑定函数:

1.单播代理:

单播代理绑定函数有多种方法,我们来演示一下常用的,这里先引用一张虚幻官方文档的图片:
在这里插入图片描述

绑定Lambda表达式:

MyDelegateInstance.BindLambda([=](int32 InElem)
{
	//Do Something
});

注:Lambda可以当成是匿名函数

绑定C++原生类的函数:

//生成原生类实例
MyClass ClassInstance;
MyDelegateInstance.BindRaw(&ClassInstance, &MyClass::Func);

绑定智能指针对象函数:

绑定智能指针有两种,一种是线程安全,一种是非线程安全

//生成一个智能指针
TSharedPtr<MyClass> MyClassSP(new MyClass);
//非线程安全
MyDelegateInstance.BindSP(MyClassSP.ToSharedRef(), &MyClass::Func);

//生成一个智能指针
TSharedPtr<MyClass, ESPMode::ThreadSafe> MyClassThreadSafeSP(new MyClass);
//线程安全
MyDelegateInstance.BindThreadSafeSP(MyClassThreadSafeSP.ToSharedRef(), &MyClass::Func);

绑定静态函数:

//静态函数
static void StatFunc(int32 InElem)
{
	//Do Something
}
MyDelegateInstance.BindStatic(StatFunc);

绑定UObject函数:

UCLASS()
class TESTPROJECT_API ATestProjectGameModeBase : public AGameModeBase
{
        //……
        //声明一个函数
	void Func(int32 InElem) {}
};
MyDelegateInstance.BindUObject(this, &ATestProjectGameModeBase::Func);

注:ATestProjectGameModeBase 是继承自UObject

绑定反射函数

MyDelegateInstance.BindUFunction(this, "Func");

注:BindUFunction是通过函数名来绑定

解除绑定只需要调用一下UnBind:

MyDelegateInstance.Unbind();

2.多播代理:

多播代理绑定方法与单播的绑定一一对应,把Bind字样改成Add就好,这里就不一一列出。

这里来看看官方文档提供的图片,大概也是单播那几种:
在这里插入图片描述

解除多播的所有绑定,只要用代理对象调用一下RemoveAll,传入使用对象(这里用个this代替一下)即可。

MyMultDelegateInstance.RemoveAll(this);

III.执行代理

1.单播代理:

不判断直接执行,需要传入给执行函数的参数

MyDelegateInstance.Execute(321);

判断是否绑定

MyDelegateInstance.IsBound();

判断是否绑定,若已经绑定就调用Execute,需要传入给执行函数的参数

MyDelegateInstance.ExecuteIfBound(321);

2.多播代理:

直接调用Broadcast,之前绑定的所有函数都会执行,需要传入给执行函数的参数

MyMultDelegateInstance.Broadcast(321);

好了,虚幻引擎使用代理的三个步骤已经说完了,虚幻所有的代理基本都是这么使用的。剩下的动态代理和事件的使用会在《UE4 代理(Delegate)的使用(2)》里面讲,其实看完这篇,另外三个就简单了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值