一、前言
相比“代理”这个名词我更喜欢叫“委托”,虚幻的委托分为三类,分别为单播、多播和动态多播。单播顾名思义就是一次只能绑定一个函数的委托,多播能一次性绑定多个,动态多播即可以在蓝图中进行动态的绑定且可以绑定多个。
使用的虚幻版本为5.2.1,VS版本为2022。
二、实现
在虚幻中创建一个Actor类,命名为“DelegateActor”类。
1、定义类型
单播可以定义带参数、返回值和参数+返回值三类委托,如下代码所示,分别定义了这三类单播委托,所有的说明见注释部分
DECLARE_DELEGATE(NoParamDelegate);//没有参数没有返回值的委托,委托名称为“NoParamDelegate”
DECLARE_DELEGATE_OneParam(OneParamDelegate, FString);//一个参数没有返回值的委托,名称为“OneParamDelegate”
DECLARE_DELEGATE_TwoParams(TwoParamDelegate, FString, int32);//两个参数没有返回值的委托,名称为“TwoParamDelegate”
DECLARE_DELEGATE_RetVal(FString, OnlyRetDelegate);//仅仅是返回值的委托,名称为“OnlyRetDelegate”
DECLARE_DELEGATE_RetVal_OneParam(FString, RetOneParamDelegate, FString);//定义了带参数和返回值的委托,第一个参数为返回值,第二个为委托名称,第三个为返回值
2、声明委托类型变量
上述定义的可以将其看作是一个类型定义,要真正使用还需要在类中声明该类型的变量,如下所示
//单播代理变量的声明
NoParamDelegate NoParamDelegate;
OneParamDelegate OneParamDelegate;
TwoParamDelegate TwoParamDelegate;
OnlyRetDelegate OnlyRetDelegate;
RetOneParamDelegate RetOneParamDelegate;
注:C++中居然允许变量名称可以和委托名称一样,这样带来的问题是后续想再定义一个同类型的委托变量就会报错。如图2.1.1所示
因此最好还是将变量名称不要定义成和类型名称一样
NoParamDelegate NoParamDelegate1;
NoParamDelegate NoParamDelegate2;
OneParamDelegate OneParamDelegate1;
TwoParamDelegate TwoParamDelegate1;
OnlyRetDelegate OnlyRetDelegate1;
3、定义委托需要绑定的函数
定义的函数要和对应的委托保持一致的返回值和参数。
//单播代理绑定函数定义
void NoParamDelegateFunc1();
void NoParamDelegateFunc2();
void OneParamDelegateFunc(FString strVal);
void TwoParamDelegateFunc(FString strVal, int32 intVal);
FString OnlyRetDelegateFunc();
FString RetOneParamDelegateFunc(FString strVal);
其实现代码如下,注:FString作为返回值时,不能直接返回声明的FString类型变量tempStr,必须返回FString(tempStr)这样的结构。
void ADelegateActor::NoParamDelegateFunc1()
{
GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Blue, FString::Printf(TEXT("NoParamDelegateFunc1")));
}
void ADelegateActor::NoParamDelegateFunc2()
{
GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Blue, FString::Printf(TEXT("NoParamDelegateFunc2")));
}
void ADelegateActor::OneParamDelegateFunc(FString strVal)
{
GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Blue, FString::Printf(TEXT("OneParamDelegateFunc:%s"), *strVal));
}
void ADelegateActor::TwoParamDelegateFunc(FString strVal, int32 intVal)
{
GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Blue, FString::Printf(TEXT("NoParamDelegateFunc:%s,%d"), *strVal, intVal));
}
FString ADelegateActor::OnlyRetDelegateFunc()
{
GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Blue, FString::Printf(TEXT("OnlyRetDelegate")));
return FString();
}
FString ADelegateActor::RetOneParamDelegateFunc(FString strVal)
{
FString tempStr = strVal.Append("RetOneParamDelegateFunc");
return FString(tempStr);
}
4、 最后在构造函数或其他初始函数中进行绑定
单播委托的绑定如下所示
NoParamDelegate1.BindUObject(this, &ADelegateActor::NoParamDelegateFunc1);
NoParamDelegate2.BindUObject(this, &ADelegateActor::NoParamDelegateFunc2);
OneParamDelegate1.BindUObject(this, &ADelegateActor::OneParamDelegateFunc);
TwoParamDelegate1.BindUObject(this, &ADelegateActor::TwoParamDelegateFunc);
OnlyRetDelegate1.BindUObject(this, &ADelegateActor::OnlyRetDelegateFunc);
RetOneParamDelegate1.BindUObject(this, &ADelegateActor::RetOneParamDelegateFunc);
5、执行
通过如下代码进行执行,编译代码通过后,在编辑器中创建该类的蓝图并将其拖放到场景中运行即可看到每个函数的打印消息。
NoParamDelegate1.ExecuteIfBound();
NoParamDelegate2.ExecuteIfBound();
OneParamDelegate1.ExecuteIfBound("TestStr");
TwoParamDelegate1.ExecuteIfBound("TwoParam", 22222);
OnlyRetDelegate1.Execute();//这个和上面的不一样
FString tempStr = RetOneParamDelegate1.Execute("this is:");
GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Blue, tempStr);
注:带返回值的和不带返回值的执行函数是不一样的,这点官方文档也未作详细说明,敲代码的时候这个执行函数也不会提示,也是比较坑。
6、解绑与绑定新的函数
解绑有点坑,官方文档写的是"UnBind",而实际上是“Unbind()"函数,而在使用了番茄助手的情况下又没有提示,敲完”UnBind“后一直报错
NoParamDelegate.Unbind();
绑定新的函数,直接像之前一样添加绑定,绑定后会覆盖掉之前的,在执行的时候只会执行新的绑定函数。
NoParamDelegate.BindUObject(this, &ADelegateActor::NoParamDelegateFunc2);
如图6.1.1所示为官方给定的其他绑定函数,不过官方没有案例说明,只有如图简单的介绍性说明,这点和Unity是没法比,所以针对每一个绑定只能自己去尝试使用一下。
1)Bind
绑定现有的委托对象,提示没有这个函数,暂时还不知道怎么使用。
2)BindStatic
需要定义一个静态函数然后进行绑定,执行方法不变。
//定义
static void StaticNoParamDelegate();
//实现
void StaticNoParamDelegate()
{
GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Blue, FString::Printf(TEXT("BindStatic")));
}
//绑定全局静态函数
NoParamDelegate1.BindStatic(StaticNoParamDelegate);
//绑定类中的静态函数
NoParamDelegate1.BindStatic(类名::静态函数名称);
注意:可以绑定类和全局的静态函数,这里和之前BindUObject中不同的是参数前不用”&“运算符。
3)BindRaw
绑定不是继承虚幻UObject类的函数,这个和BindUObject正好相反。首先需要在虚幻中创建一个不继承任何东西的类
然后在其类中顶一个不带参数和返回值的函数
//函数定义
void Raw_NoParamDelegateFunc();
//函数实现
void MyRawClass::Raw_NoParamDelegateFunc()
{
GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Blue, FString::Printf(TEXT("Raw_NoParamDelegateFunc")));
}
注:在不经常虚幻基础类的其他类中也可以使用虚幻引擎的打印屏幕函数。
绑定这个函数
MyRawClass* tempRawClass = new MyRawClass();
NoParamDelegate1.BindRaw(tempRawClass, &MyRawClass::Raw_NoParamDelegateFunc);
注意绑定的参数第一个是对象指针,如下所示是另一种创建对象方式(这都是C++的基础,(;´༎ຶД༎ຶ`)
MyRawClass tempRawClass;
NoParamDelegate1.BindRaw(&tempRawClass, &MyRawClass::Raw_NoParamDelegateFunc);
4)BindLambda
绑定一个Lambda表达式,Lambda表达式是一个匿名函数片段,即可以在函数内单独定一个一段函数,代码如下,使用关键字auto,其中”auto LambdaDelegateFunc = []()->void“,()里面可以添加参数,返回值可以将void类型改成其他返回值类型。
auto LambdaDelegateFunc = []()->void
{
GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Green, FString::Printf(TEXT("LambdaDelegateFunc")));
};
NoParamDelegate1.BindLambda(LambdaDelegateFunc);
Lambda表达式也可以直接写在要绑定的方法参数里,这个比上面的更简便,完全没有函数名
NoParamDelegate1.BindLambda(
[]()->void
{
GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Green, FString::Printf(TEXT("LambdaDelegateFunc")));
}
);
5)BindSP
绑定SharedPtr(智能指针)指向对象的函数,即纯C++类(非继承虚幻基类的类)的函数。因为纯C++类一般会使用TSharedPtr用于管理内存。
//绑定智指针
TSharedRef<MyRawClass> ObjSP1 = MakeShareable(new MyRawClass());
NoParamDelegate1.BindSP(ObjSP1, &MyRawClass::Raw_NoParamDelegateFunc);
注意,这段绑定函数不要和之前的函数一样放在构造函数中,否则会出现绑定不成功的情况。
6)BindUObject
绑定继承了虚幻引擎基类的类成员函数,前面案例中已经介绍使用,不再赘述。
三、总结
3.1、单播可以定义三类委托,分别为带参数、返回值和参数+返回值。
3.2、声明委托的时候变量名称最好不要喝类型名称一样。
3.3、要绑定的函数和定义的委托在参数和返回值上要一致。
3.4、带返回值的和不带返回值的执行函数是不一样的。
3.5、解绑不是"UnBind",而实际上是“Unbind()"函数。