UE4 中Delegate(委托)使用频率非常高,常用的委托有单播、多播、动态多播、事件等等.
本篇将讲解这些委托的使用方法,注意事项等知识.
一.Delegate的意义
(1)将实时检测逻辑转换为触发式逻辑
请进行以下思考:
如果要实现物体从A移动到B点,在到达B点时打印一句"到达目标点!".
如果使用检测式逻辑:或许你会在使用Timer或者Tick没隔一段时间判断一下是否到达B点,这样会导致判断逻辑会在到达B点之前一直在重复执行,如果判断逻辑较为复杂,则会导致程序会高频率执行一段运算复杂逻辑,导致性能降低.并且如果使用的Timer间隔比较长,可能会导致物体到达时,Timer倒计时还没到,导致判定失准!
如果使用触发式逻辑:在B点放置一个BoxTrigger,当物体到达B点时触发Overlap回调时执行打印,这么做避免了高频率执行判断逻辑,降低了性能的损耗,并且精确度比检测式要更高!
(2)降低耦合性
下面代码是最普通的面向对象式的函数调用
class A
{
public:
void Call();
}
class B
{
public:
A* a;
void CallTestFunction()
{
if(a!=nullptr)
{
a->Call();
}
}
}
void main()
{
B* b=new B();
b->a=new A();
b->a->Call();
} 可以看到,如果我们想要在B中调用A类中的Call函数,我们必须在B中创建一个A类型的指针a,然后通过指针a去调用Call函数.
这样就会导致B类跟A类存在了耦合关系.
下面试绑定委托的伪代码,演示了委托绑定回调函数和触发回调函数的过程
class A
{
public:
void Call();
}
//定义委托
DECLARE_DELEGATE(FOnCallback);
class B
{
public:
//创建委托实例
FOnCallback OnCallback;
//通过BindUFunction绑定回调函数
void BindDelegate(A* CallbackTarget,FName FunctionName)
{
//使用传入的A类型实例,绑定回调函数
OnCallback.BindUFunction(CallbackTarget,FunctionName);
}
//触发回调函数
void Trigger()
{
OnCallback.ExecuteIfBound();
}
}
void main()
{
//创建B类型实例b
B* b=new B();
//创建A类型实例a
A* a=new A();
//绑定委托和回调函数
b->BindDelegate(a,FName(TEXT("Call")));
//触发回调
b->Trigger();
} 根据代码我们可以看到,B类并不需要存储一个A类型指针,这就解除了A和B之间的耦合

二.委托讲解开始
1.单播委托
单播委托只能绑定一个回调函数,新绑定的会覆盖旧的,因此被称为单播
三种单播:无参数无返回值,有参数无返回值,有参数有返回值
(1)定义写法:
//定义无参数单播委托
DECLARE_DELEGATE(FSingleDelegateWithNoParam);
//创建无参数单播委托对象
FSingleDelegateWithNoParam SDNoParam;
/*定义有参数单播委托,最多支持9个参数.注意!! 绑定的回调函数要与委托参数列表一致
*格式: 定义委托宏 (委托名,参数类型1,参数类型2)
*/
//定义一个参数的单播委托,注意是OneParam 没有s!!!!! DECLARE_DELEGATE_OneParam
DECLARE_DELEGATE_OneParam(FSingleDelegateWithOneParam, FString);
//创建一个参数单播委托对象
FSingleDelegateWithOneParam SDOneParam;
//定义多个参数的单播委托,注意!!当参数数量大于1时是Params!!! DECLARE_DELEGATE_TwoParams
DECLARE_DELEGATE_TwoParams(FSingleDelegateWithTwoParams, FString,int32);
//创建2个参数单播委托对象
FSingleDelegateWithTwoParams SDTwoParams;
/*定义带有返回值的单播委托,注意!!! 返回值只能有一个,但是参数也是最多9个!!
*格式: 定义委托宏(返回值类型,委托名,参数类型1,参数类型2)
*/
//定义返回值类型为FString,一个参数的单播委托
DECLARE_DELEGATE_RetVal_OneParam(FString,FSingleDelegateWithReturnValue,int32);
//创建1个参数带返回值单播委托对象
FSingleDelegateWithReturnValue SDOneParamWithRTV;
(2)绑定回调函数写法:
CallbackTarget作为回调函数的拥有者,用于提供被绑定的回调函数.
class CallbackTarget
{
public:
//无参单播委托回调函数
void SDNoParamCallback();
//1参单播委托回调函数
void SDOneParamCallback(FString str);
//2参单播委托回调函数
void SDTwoParamsCallback(FString var1,int32 var2);
//有返回值1参数单播委托回调函数
FString SDOneParamWithRTVCallback(int32 var);
}
①BindUObject
/*BindUObject 参数讲解:
*InUserObject:被绑定委托的对象
*InFunc:被绑定的回调函数,注意,InFunc是InUserObject的成员函数
*
*注意理解!! InUserObject是InFunc的拥有者,传入这两个参数的目的是为了确认
*委托要绑定 谁(InUserObject)的函数(InFunc)
*/
//创建一个CallbackTarget实例,用于提供回调函数.
CallbackTarget* Target=new CallbackTarget();
//无参单播对象绑定回调函数,InUserObject:Target; InFunc:SDNoParamCallback
SDNoParam.BindUObject(Target,&CallbackTarget::SDNoParamCallback);
//1参单播对象绑定回调函数
SDOneParam.BindUObject(Target,&CallbackTarget::SDOneParamCallback);
//1参单播对象绑定回调函数
SDTwoParams.BindUObject(Target,&CallbackTarget::SDTwoParamsCallback);
//有返回值1参单薄对象绑定回调函数
SDOneParamWithRTV.BindUObject(Target,&CallbackTarget::SDOneParamWithRTVCallback); ②BingUFunction
class CallbackTarget
{
public:
/*BindUFunction需要用到UE4的反射机制,因此回调函数需要用UFUNCTION()宏包住
*否则ue无法通过FunctionName查找到对应的函数,会导致绑定失败
*/
//无参单播委托回调函数
UFUNCTION()
void SDNoParamCallback();
//1参单播委托回调函数
UFUNCTION()
void SDOneParamCallback(FString str);
//2参单播委托回调函数
UFUNCTION()
void SDTwoParamsCallback(FString var1,int32 var2);
//有返回值1参数单播委托回调函数
UFUNCTION()
FString SDOneParamWithRTVCallback(int32 var);
}
/*BindUFunction 参数讲解:
*InUserObject:被绑定委托的对象
*InFunctionName:被绑定的回调函数名.
*注意!! ue4之所以仅根据函数名就能正确绑定回调函数,是通过ue4的反射机制,
*这就要求被绑定的函数必须要被UFUNCTION()宏包住,否则无法正确的找到对应的函数
*导致绑定失败.
*
*注意理解!! InUserObject是InFunctionName的拥有者,传入这两个参数的目的是为了确认
*委托要绑定 谁(InUserObject)的函数(InFunctionName)
*/
//创建一个CallbackTarget实例,用于提供回调函数.
CallbackTarget* Target=new CallbackTarget();
//无参单播对象绑定回调函数
SDNoParam.BindUFunction(Target,FName("SDNoParamCallback"));
//1参单播对象绑定回调函数
SDOneParam.BindUFunction(Target,FName("SDOneParamCallback"));
//1参单播对象绑定回调函数
SDTwoParams.BindUFunction(Target,FName("SDTwoParamsCallback"));
//有返回值1参单薄对象绑定回调函数
SDOneParamWithRTV.BindUFunction(Target,FName("SDOneParamWithRTVCallback")); ③BindStatic
BindStatic可以绑定静态成员函数,也可以绑定全局静态函数(静态非成员函数)
class CallbackTarget
{
public:
//静态成员函数
static StaticCallback(FString str)
{
UE_LOG(LogTemp,Warning,TEXT("StaticCallback Call,Param Value:%s"),*str);
}
}
//全局静态函数(静态非成员函数)
void StaticFunc(FString str)
{
UE_LOG(LogTemp,Warning,TEXT("StaticFunc Call,Param Value:%s"),*str);
}
//绑定静态成员函数
SDOneParam.BindStatic(&CallbackTarget::StaticCallback);
//绑定全局静态函数
SDOneParam.BindStatic(StaticFunc);
④BindLambda
Lambda表达式,不清楚的请跳转到[UE4图文系列]2.Lambda表达式进行了解
//绑定Lambda表达式
SDOneParam.BindLambda(
[](FString str)
{
UE_LOG(LogTemp,Warning,TEXT("Lambda Call,Param Value:%s"),*str);
}
) ⑤CreateUObject
CallbackTarget* Target=new CallbackTarget();
//CreateUObject返回一个绑定完成的委托对象,用来初始化SDOneParam
SDOneParam=FSingleDelegateWithOneParam::CreateUObject(Target,&CallbackTarget::SDOneParamCallback); (3)触发回调函数写法:
/*单播委托对象使用ExecuteIfBound()触发回调
*可以使用IsBound()判断是否已经绑定
*/
//单播无参委托触发回调
SDNoParam.ExecuteIfBound();
//单播有参委托触发回调
SDOneParam.ExecuteIfBound("test single delegate");
//单播有返回值有参委托触发回调
//ps:之所以不用ExecuteIfBound是因为该函数固定返回一个bool值来表示调用是否成功,已经有固定返回值了,无法再返回委托定义的返回值,因此只能用Execute
if(SDOneParamWithRTV.IsBound())
{
FString rtvstr=SDOneParamWithRTV.Execute("test single delegate with rtv");
}
(4)委托解绑写法
//单播委托使用Unbind()解绑
SDOneParam.Unbind(); 2.多播委托
相较于单播委托,多播委托可以绑定多个回调函数,当其触发时,所有绑定的回调函数都会执行
多播委托和单播委托定义方式很相似,除了多播委托没有返回值,这里我们只用 有参数的多播委托进行讲解
(1)定义写法:
//多播委托定义 格式 委托定义宏(委托名,参数类型1,参数类型2,参数类型3)
DECLARE_MULTICAST_DELEGATE_OneParam(FMulticastDelegateWithOneParam, FString);
//创建多播委托对象
FMulticastDelegateWithOneParam MDOneParam; (2)绑定回调函数写法:
class CallbackTarget
{
public:
void FunctionForAddUObject(FString str);
UFUNCTION()
void FunctionForAddUFunction(FString str);
//静态成员函数
static StaticCallback(FString str)
{
UE_LOG(LogTemp,Warning,TEXT("StaticCallback Call,Param Value:%s"),*str);
}
}
//全局静态函数(静态非成员函数)
void StaticFunc(FString str)
{
UE_LOG(LogTemp,Warning,TEXT("StaticFunc Call,Param Value:%s"),*str);
}
CallbackTarget* Target=new CallbackTarget();
/*多播绑定回调函数以Add开头*/
//delegatehandle
FDelegateHandle Handle;
//AddUObject 绑定多播
Handle=MDOneParam.AddUObject(Target,&CallbackTarget::FunctionForAddUObject);
//AddUFunction 绑定多播
Handle=MDOneParam.AddUFunction(Target,FName(TEXT("FunctionForAddUFunction")));
//AddStatic 绑定全局静态函数
Handle=MDOneParam.AddStatic(StaticFunc);
//AddStatic 绑定静态成员函数
Handle=MDOneParam.AddStatic(&CallbackTarget::StaticCallback);
//AddLambda 绑定Lambda表达式
Handle=MDOneParam.AddLambda(
[](FString str)
{
UE_LOG(LogTemp,Warning,TEXT("Lambda Call,Param Value:%s"),*str);
}
)
(3)触发回调函数写法:
//可以使用IsBound()判断是否已经绑定
MDOneParam.Broadcast("Multicast delegate trigger"); (4)委托解绑写法:
//解绑所有绑定的回调函数
MDOneParam.Clear();
//Remove(FDelegateHandle)移除单个回调函数,handle是绑定函数回调时返回的
MDOneParam.Remove(handle);
CallbackTarget* Target=new CallbackTarget();
//...经过各种绑定回调
/*RemoveAll(InUserObject),假如多播委托绑定了某一个对象的多个函数作为回调,可以传入对象
*把该对象的所有被绑定的成员函数解绑
*/
MDOneParam.RemoveAll(Target); 3.动态多播
动态多播可以在蓝图中使用,动态多播对象本质上与蓝图的EventDispatcher是一回事
(1)定义写法:
//动态多播 格式: 委托定义宏(委托名,参数类型1,参数名1,参数类型2,参数名2)
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FDynamicMulticastDelegate, FString, str);
//创建动态多播对象
FDynamicMulticastDelegate DMOneParam; (2)绑定回调函数写法:
class CallbackTarget
{
public:
//动态多播回调函数必须使用UFUNCTION()
UFUNCTION()
void FunctionForAddDynamic(FString str);
}
CallbackTarget* Target=new CallbackTarget();
//动态多播使用AddDynamic绑定回调函数
DMOneParam.AddDynamic(Target,&CallbackTarget::FunctionForAddDynamic); (3)触发回调函数写法:
//动态多播同样适用IsBound判断是否绑定
DMOneParam.Broadcast("Dynamic Multicast Delegate Callback"); (4)委托解绑写法:
//动态多播使用Clear全部解绑
DMOneParam.Clear();
//动态多播也支持使用Remove和Removeall,用法与多播一样
//动态多播使用RemoveDynamic()解绑单个
DMOneParam.RemoveDynamic(Target, &CallbackTarget::FunctionForAddDynamic); (5)动态多播蓝图中用法
//使用BlueprintAssignable修饰动态多播对象
UPROPERTY(BlueprintAssignable,Category="DelegateActor")
FDynamicMulticastDelegate DynamicMulticastDelegate;

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

用法跟蓝图Dispatcher一样,我们可以直接Assign


我们常用的碰撞事件,本质也是在C++定义的动态多播

OnComponentBeginOverlap是一个动态多播
4.Event(事件)
Event定义于类内,通常将Event对象设为私有,类外通过公开的访问接口进行绑定,触发,解绑,这种方式起到了保护隐私的作用
(1)定义Event写法,事件需要定义在类内:
class ADelegateActor:public AActor
{
GENERATED_BODY()
public:
/*
*OwingType:拥有此Event的类,本例中使用本类:ADelegateActor
*EventName:事件名称
*ParamType:参数列表
*/
DECLARE_EVENT_OneParam(ADelegateActor, MyDelegateEvent, FString);
//公开对Event对象的访问接口
MyDelegateEvent& OnEventTrigger(){return DelegateEvent;}
private:
//将Event设为私有,防止类外直接访问到,起到安全作用
MyDelegateEvent DelegateEvent;
} (2)绑定回调函数写法:
class CallbackTarget
{
public:
void FunctionForAddUObject(FString str);
UFUNCTION()
void FunctionForAddUFunction(FString str);
//静态成员函数
static StaticCallback(FString str)
{
UE_LOG(LogTemp,Warning,TEXT("StaticCallback Call,Param Value:%s"),*str);
}
}
//全局静态函数(静态非成员函数)
void StaticFunc(FString str)
{
UE_LOG(LogTemp,Warning,TEXT("StaticFunc Call,Param Value:%s"),*str);
} Event绑定方法与多播一样
CallbackTarget* Target=new CallbackTarget();
/*多播绑定回调函数以Add开头*/
//delegatehandle
FDelegateHandle Handle;
//AddUObject 绑定多播
Handle=OnEventTrigger().AddUObject(Target,&CallbackTarget::FunctionForAddUObject);
//AddUFunction 绑定多播
Handle=OnEventTrigger().AddUFunction(Target,FName(TEXT("FunctionForAddUFunction")));
//AddStatic 绑定全局静态函数
Handle=OnEventTrigger().AddStatic(StaticFunc);
//AddStatic 绑定静态成员函数
Handle=OnEventTrigger().AddStatic(&CallbackTarget::StaticCallback);
//AddLambda 绑定Lambda表达式
Handle=OnEventTrigger().AddLambda(
[](FString str)
{
UE_LOG(LogTemp,Warning,TEXT("Lambda Call,Param Value:%s"),*str);
}
)
(3)触发回调函数写法:
OnEventTrigger().Broadcast("DELEGATE EVENT Call"); (4)解绑回调函数
//解绑方法同多播一样
OnEventTrigger().Clear(); 以上就是四种常用Delegate的用法和注意事项.