[UE4 C++入门到进阶] 4.Delegate(委托)
埃罗芒阿Sensal
编辑于 2023年02月03日 13:39
收录于文集
共15篇

UE4 中Delegate(委托)使用频率非常高,常用的委托有单播多播动态多播事件等等.

本篇将讲解这些委托的使用方法,注意事项等知识.

一.Delegate的意义

(1)将实时检测逻辑转换为触发式逻辑

请进行以下思考:

如果要实现物体从A移动到B点,在到达B点时打印一句"到达目标点!&#​34;.

如果使用检测式逻辑:或许你会在使用Timer或者Tick没隔一段时间判断一下是否到达B点,这样会导致判断逻辑会在到达B点之前一直在重复执行,如果判断逻辑较为复杂,则会导致程序会高频率执行一段运算复杂逻辑,导致性能降低.并且如果使用的Timer间隔比较长,可能会导致物体到达时,Timer倒计时还没到,导致判定失准!

如果使用触发式逻辑:在B点放置一个BoxTrigger,当物体到达B点时触发Overlap回调时执行打印,这么做避免了高频率执行判断逻辑,降低了性能的损耗,并且精确度比检测式要更高!

(2)降低耦合性

下面代码是最普通的面向对象式的函数调用

代码块
C++
自动换行
复制代码
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类存在了耦合关系.

下面试绑定委托的伪代码,演示了委托绑定回调函数触发回调函数的过程

代码块
C++
自动换行
复制代码
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)定义写法:

代码块
C++
自动换行
复制代码
//定义无参数单播委托
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作为回调函数的拥有者,用于提供被绑定的回调函数.

代码块
C++
自动换行
复制代码
class CallbackTarget
{
 public: 

  //无参单播委托回调函数
  void SDNoParamCallback();

  //1参单播委托回调函数
  void SDOneParamCallback(FString str);

  //2参单播委托回调函数
  void SDTwoParamsCallback(FString var1,int32 var2);

  //有返回值1参数单播委托回调函数
  FString SDOneParamWithRTVCallback(int32 var);
}
复制成功

①BindUObject

代码块
C++
自动换行
复制代码

/*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

代码块
C++
自动换行
复制代码
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);
}
复制成功

代码块
C++
自动换行
复制代码

/*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可以绑定静态成员函数,也可以绑定全局静态函数(静态非成员函数)

代码块
C++
自动换行
复制代码
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);
}
复制成功

代码块
C++
自动换行
复制代码
//绑定静态成员函数
SDOneParam.BindStatic(&CallbackTarget::StaticCallback);

//绑定全局静态函数
SDOneParam.BindStatic(StaticFunc);
复制成功

④BindLambda

Lambda表达式,不清楚的请跳转到[UE4图文系列]2.Lambda表达式​进行了解

代码块
C++
自动换行
复制代码

//绑定Lambda表达式
SDOneParam.BindLambda(
  [](FString str)
   {
     UE_LOG(LogTemp,Warning,TEXT("Lambda Call,Param Value:%s"),*str);
   }
)
复制成功

⑤CreateUObject

代码块
C++
自动换行
复制代码
CallbackTarget* Target=new CallbackTarget();
//CreateUObject返回一个绑定完成的委托对象,用来初始化SDOneParam
SDOneParam=FSingleDelegateWithOneParam::CreateUObject(Target,&CallbackTarget::SDOneParamCallback);
复制成功

(3)触发回调函数写法:

代码块
C++
自动换行
复制代码
/*单播委托对象使用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)委托解绑写法

代码块
C++
自动换行
复制代码

//单播委托使用Unbind()解绑
SDOneParam.Unbind();
复制成功

2.多播委托

相较于单播委托,多播委托可以绑定多个回调函数,当其触发时,所有绑定的回调函数都会执行

多播委托和单播委托定义方式很相似,除了多播委托没有返回值,这里我们只用 有参数的多播委托进行讲解

(1)定义写法:

代码块
C++
自动换行
复制代码
//多播委托定义 格式 委托定义宏(委托名,参数类型1,参数类型2,参数类型3)
DECLARE_MULTICAST_DELEGATE_OneParam(FMulticastDelegateWithOneParam, FString);

//创建多播委托对象
FMulticastDelegateWithOneParam MDOneParam;
复制成功

(2)绑定回调函数写法:

代码块
C++
自动换行
复制代码
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);
}
复制成功

代码块
C++
自动换行
复制代码
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)触发回调函数写法:

代码块
C++
自动换行
复制代码
//可以使用IsBound()判断是否已经绑定

MDOneParam.Broadcast("Multicast delegate trigger");
复制成功

(4)委托解绑写法:

代码块
C++
自动换行
复制代码
//解绑所有绑定的回调函数
MDOneParam.Clear();

//Remove(FDelegateHandle)移除单个回调函数,handle是绑定函数回调时返回的
MDOneParam.Remove(handle);

CallbackTarget* Target=new CallbackTarget();

//...经过各种绑定回调

/*RemoveAll(InUserObject),假如多播委托绑定了某一个对象的多个函数作为回调,可以传入对象
*把该对象的所有被绑定的成员函数解绑
*/
MDOneParam.RemoveAll(Target);
复制成功

3.动态多播

动态多播可以在蓝图中使用,动态多播对象本质上与蓝图的EventDispatcher是一回事

(1)定义写法:

代码块
C++
自动换行
复制代码
//动态多播 格式: 委托定义宏(委托名,参数类型1,参数名1,参数类型2,参数名2)
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FDynamicMulticastDelegate, FString, str);

//创建动态多播对象
FDynamicMulticastDelegate DMOneParam;
复制成功

(2)绑定回调函数写法:

代码块
C++
自动换行
复制代码
class CallbackTarget
{
 public:
  //动态多播回调函数必须使用UFUNCTION()
  UFUNCTION()
  void FunctionForAddDynamic(FString str);
  
}
复制成功

代码块
C++
自动换行
复制代码

CallbackTarget* Target=new CallbackTarget();
//动态多播使用AddDynamic绑定回调函数
DMOneParam.AddDynamic(Target,&CallbackTarget::FunctionForAddDynamic);
复制成功

(3)触发回调函数写法:

代码块
C++
自动换行
复制代码
//动态多播同样适用IsBound判断是否绑定

DMOneParam.Broadcast("Dynamic Multicast Delegate Callback");
复制成功

(4)委托解绑写法:

代码块
C++
自动换行
复制代码

//动态多播使用Clear全部解绑
DMOneParam.Clear();

//动态多播也支持使用Remove和Removeall,用法与多播一样
//动态多播使用RemoveDynamic()解绑单个
DMOneParam.RemoveDynamic(Target, &CallbackTarget::FunctionForAddDynamic);
复制成功

(5)动态多播蓝图中用法

代码块
C++
自动换行
复制代码
//使用BlueprintAssignable修饰动态多播对象
UPROPERTY(BlueprintAssignable,Category="DelegateActor")
	FDynamicMulticastDelegate DynamicMulticastDelegate;
复制成功

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

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

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

OnComponentBeginOverlap是一个动态多播

4.Event(事件)

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

(1)定义Event写法,事件需要定义在类内:

代码块
C++
自动换行
复制代码
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)绑定回调函数写法:

代码块
C++
自动换行
复制代码
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绑定方法与多播一样

代码块
C++
自动换行
复制代码
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)触发回调函数写法:

代码块
C++
自动换行
复制代码
OnEventTrigger().Broadcast("DELEGATE EVENT Call");
复制成功

(4)解绑回调函数

代码块
C++
自动换行
复制代码
//解绑方法同多播一样

OnEventTrigger().Clear();
复制成功

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