委托有点类似函数指针,UE4定义了一大堆的委托方便我们的使用
在DelegateCombinations.h文件中的定义了各种委托
#define DECLARE_DELEGATE( DelegateName ) FUNC_DECLARE_DELEGATE( DelegateName, void )
#define DECLARE_MULTICAST_DELEGATE( DelegateName ) FUNC_DECLARE_MULTICAST_DELEGATE( DelegateName, void )
#define DECLARE_EVENT( OwningType, EventName ) FUNC_DECLARE_EVENT( OwningType, EventName, void )
#define DECLARE_DYNAMIC_DELEGATE( DelegateName ) BODY_MACRO_COMBINE(CURRENT_FILE_ID,_,__LINE__,_DELEGATE) FUNC_DECLARE_DYNAMIC_DELEGATE( FWeakObjectPtr, DelegateName, DelegateName##_DelegateWrapper, , FUNC_CONCAT( *this ), void )
#define DECLARE_DYNAMIC_MULTICAST_DELEGATE( DelegateName ) BODY_MACRO_COMBINE(CURRENT_FILE_ID,_,__LINE__,_DELEGATE) FUNC_DECLARE_DYNAMIC_MULTICAST_DELEGATE( FWeakObjectPtr, DelegateName, DelegateName##_DelegateWrapper, , FUNC_CONCAT( *this ), void )
#define DECLARE_DELEGATE_RetVal( ReturnValueType, DelegateName ) FUNC_DECLARE_DELEGATE( DelegateName, ReturnValueType )
#define DECLARE_DYNAMIC_DELEGATE_RetVal( ReturnValueType, DelegateName ) BODY_MACRO_COMBINE(CURRENT_FILE_ID,_,__LINE__,_DELEGATE) FUNC_DECLARE_DYNAMIC_DELEGATE_RETVAL( FWeakObjectPtr, DelegateName, DelegateName##_DelegateWrapper, ReturnValueType, , FUNC_CONCAT( *this ), ReturnValueType )
我在项目中经常使用的有两类委托,单播委托和多播委托
单播委托(DECLARE_DELEGATE)
单播委托,指的是只能绑定一个函数指针的委托,实现一对一的通知。
单播委托的定义是没有“MULTICAST”修饰的委托,如下面这些
DECLARE_DELEGATE
DECLARE_DELEGATE_OneParam
DECLARE_DELEGATE_TwoParams
DECLARE_DELEGATE_ThreeParams
UE4在DelegateCombinations.h文件已经预先帮你声明了从无参数到长达9个参数的委托
怎么使用?
BindUObject
(1)声明委托和定义委托变量
//单播无参数的委托
DECLARE_DELEGATE(FSingleDelagateWithNoParam);
FSingleDelagateWithNoParam SingleDelagateWithNoParam;
//单播一个参数的委托
DECLARE_DELEGATE_OneParam(FSingleDelagateWithOneParam, FString);
FSingleDelagateWithOneParam SingleDelagateWithOneParam;
(2)绑定函数指针 BindUObject。 这里我经常用BindUObject,绑定的函数指针为UObject或者继承UObject的对象的函数指针
如我定义一个Actor:
.h :
UCLASS()
class MYPROJECT_API ATestActor2 : public AActor
{
GENERATED_BODY()
public:
// Sets default values for this actor's properties
ATestActor2();
protected:
// Called when the game starts or when spawned
virtual void BeginPlay() override;
public:
// Called every frame
virtual void Tick(float DeltaTime) override;
private:
void Func1();
void Func2(FString param);
};
.cpp :
void ATestActor2::Func1()
{
UE_LOG(LogTemp, Error, TEXT("TestActor2 Func1"));
}
void ATestActor2::Func2(FString param)
{
UE_LOG(LogTemp, Error, TEXT("TestActor2 Func2 param = %s"), *param);
}
绑定委托:
void ATestActor2::BeginPlay()
{
Super::BeginPlay();
APlayerController* playerController = GetWorld()->GetFirstPlayerController();
if (nullptr == playerController)
return;
ACharacter* character = playerController->GetCharacter();
if (nullptr == character)
return;
AMyProjectCharacter* myProjectCharacter = Cast<AMyProjectCharacter>(character);
if (nullptr == myProjectCharacter)
return;
myProjectCharacter->SingleDelagateWithNoParam.BindUObject(this, &ThisClass::Func1);
myProjectCharacter->SingleDelagateWithOneParam.BindUObject(this, &ThisClass::Func2);
}
这里得注意的是,单播委托如果已经绑定过函数指针,在没有UnBind的情况下如果再次绑定函数指针,会报错。所以经常使用
(3)执行委托,触发相应的对象的函数
SingleDelagateWithNoParam.ExecuteIfBound();
SingleDelagateWithOneParam.ExecuteIfBound(FString("PerformSingleDelagateWithOneParam"));
这里呢,得注意的一点单播委托执行委托函数有:Execute和ExecuteIfBound
我一般用ExecuteIfBound而不是Execute,因为ExecuteIfBound更安全,指的是在委托绑定有效函数指针的前提下才能执行,而Execute如果在委托绑定无效的函数指针的情况下就执行会报错。
运行结果:
(4)移除委托Unbind,移除已经绑定的函数指针
(5)IsBound,判断委托是否已经绑定了函数指针
BindStatic
用于绑定于类的静态函数
UCLASS(config=Game)
class AMyProject6Character : public ACharacter
{
static void Test(float a);
DECLARE_DELEGATE_OneParam(FAA, float)
FAA faa;
}
faa.BindStatic(&AMyProject6Character::Test);
BindRaw
BindRaw是用于绑定不继承UObject的类或者结构体的对象的方法
CreateUObject
创建委托变量:
DECLARE_DELEGATE_OneParam(FTestDelegate, float);
void AMyProject3Character::PrintTestInfo(float Value)
{
UE_LOG(LogTemp, Error, TEXT("Value = %f"), Value);
}
FTestDelegate MyDel = FTestDelegate::CreateUObject(this, &AMyProject3Character::PrintTestInfo);
其他CreateRaw,CreateStatic同理。
多播委托(DECLARE_MULTICAST_DELEGATE)
多播委托,指的是能绑定多个函数指针的委托,实现一对多的通知。
多播委托的定义是有“MULTICAST”修饰的委托,如下面这些:
DECLARE_MULTICAST_DELEGATE
DECLARE_MULTICAST_DELEGATE_OneParam
DECLARE_MULTICAST_DELEGATE_TwoParams
DECLARE_MULTICAST_DELEGATE_ThreeParams
怎么使用?
AddUObject
(1)声明委托和定义委托变量
//多播无参数委托
DECLARE_MULTICAST_DELEGATE(FMuitiDelagateWithNoParam);
FMuitiDelagateWithNoParam MuitiDelagateWithNoParam;
//多播一个参数的委托
DECLARE_MULTICAST_DELEGATE_OneParam(FMuitiDelagateWithOneParam, FString);
FMuitiDelagateWithOneParam MuitiDelagateWithOneParam;
(2)绑定函数指针 AddUObject。 这里我经常用BindUObject,这个函数绑定的函数指针为UObject或者继承UObject的类对象顶点函数指针.这里可以添加多个函数指针。
.h
UCLASS()
class MYPROJECT_API ATestActor : public AActor
{
GENERATED_BODY()
public:
// Sets default values for this actor's properties
ATestActor();
protected:
// Called when the game starts or when spawned
virtual void BeginPlay() override;
public:
// Called every frame
virtual void Tick(float DeltaTime) override;
private:
void Func1();
void Func2(FString param);
.cpp
void ATestActor::Func1()
{
UE_LOG(LogTemp, Error, TEXT("TestActor Func1"));
}
void ATestActor::Func2(FString param)
{
UE_LOG(LogTemp, Error, TEXT("TestActor Func2 param = %s"), *param);
}
ATestActor::BeginPlay():
void ATestActor::BeginPlay()
{
Super::BeginPlay();
APlayerController* playerController = GetWorld()->GetFirstPlayerController();
if (nullptr == playerController)
return;
ACharacter* character = playerController->GetCharacter();
if (nullptr == character)
return;
AMyProjectCharacter* myProjectCharacter = Cast<AMyProjectCharacter>(character);
if (nullptr == myProjectCharacter)
return;
myProjectCharacter->MuitiDelagateWithNoParam.AddUObject(this, &ThisClass::Func1);
myProjectCharacter->MuitiDelagateWithOneParam.AddUObject(this, &ThisClass::Func2);
}
ATestActor2::BeginPlay():
void ATestActor2::BeginPlay()
{
Super::BeginPlay();
APlayerController* playerController = GetWorld()->GetFirstPlayerController();
if (nullptr == playerController)
return;
ACharacter* character = playerController->GetCharacter();
if (nullptr == character)
return;
AMyProjectCharacter* myProjectCharacter = Cast<AMyProjectCharacter>(character);
if (nullptr == myProjectCharacter)
return;
myProjectCharacter->MuitiDelagateWithNoParam.AddUObject(this, &ThisClass::Func1);
myProjectCharacter->MuitiDelagateWithOneParam.AddUObject(this, &ThisClass::Func2);
}
同时绑定了ATestActor和ATestActor2的函数指针
(3)执行委托,触发函数
MuitiDelagateWithNoParam.Broadcast();
MuitiDelagateWithOneParam.Broadcast(FString("PerformMultiDelagateWithOneParam"));
(4)移除函数指针Remove和RemoveAll
Remove的参数为委托AdddUObject返回的句柄FDelegateHandle
AddStatic
用法跟上面的BindStatic对应
动态委托(DECLARE_DYNAMIC_DELEGATE)
动态委托在UE4内置的各种类经常可见,如Actor的鼠标点击(OnBeginCursorOver),开始进入Actor碰撞范围(OnActorBeginOverlap)等等
目前我知道关于动态委托主要是可用于蓝图的委托绑定,如下面所示:
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FTestDynamicDelagate, float, Value);
UPROPERTY(BlueprintAssignable, Category = "Test")
FTestDynamicDelagate OnTestDynamicDelagate;
可以看出得加上 UPROPERTY(BlueprintAssignable, Category = “Test”),这里如果不是动态委托的话会报错
一般来说我们在C++代码使用动态委托绑定一个对象的方法的时候比较喜欢用__Internal_BindDynamic和__Internal_AddDynamic,当然UE4为了调用方便封装了对应的宏:
#define BindDynamic( UserObject, FuncName ) __Internal_BindDynamic( UserObject, FuncName, STATIC_FUNCTION_FNAME( TEXT( #FuncName ) ) )
#define AddDynamic( UserObject, FuncName ) __Internal_AddDynamic( UserObject, FuncName, STATIC_FUNCTION_FNAME( TEXT( #FuncName ) ) )
这里比较注意的是用AddDynamic绑定的方法得被“UFUNCTION”标记,否则绑定无效
UFUNCTION()
void OnTestBegin(AActor* OverlappedActor, AActor* OtherActor);
void AMyActor::OnTestBegin(AActor* OverlappedActor, AActor* OtherActor)
{
if (OverlappedActor)
{
UE_LOG(LogTemp, Warning, TEXT("OverlappedActor is %s"), *OverlappedActor->GetName());
}
if (OtherActor)
{
UE_LOG(LogTemp, Warning, TEXT("OtherActor is %s"), *OtherActor->GetName());
}
}
void AMyActor::BeginPlay()
{
Super::BeginPlay();
OnActorBeginOverlap.AddDynamic(this, &AMyActor::OnTestBegin);
}
可返回值委托(DECLARE_DELEGATE_RetVal)
可返回值委托和之前“DECLARE_DELEGATE” “DECLARE_MULTICAST_DELEGATE”差不多,主要的区别是DECLARE_DELEGATE_RetVal这些委托在执行的时候可以返回一个值,如下所示:
DECLARE_DELEGATE_RetVal(float, FTestRetValDelegate);
FTestRetValDelegate TestRetValDelegate;
float Test();
float AMyActor::Test()
{
return 1.0f;
}
float Value = TestRetValDelegate.Execute();
委托的额外传参数
有时候,我们在委托绑定函数指针的时候,就随便传入一个变量值,UE4的委托也是可以办到的。如下面所示:
//单播无参数的委托
DECLARE_MULTICAST_DELEGATE(FMuitiDelagateWithNoParam);
FMuitiDelagateWithNoParam MuitiDelagateWithNoParam;
void ATestActor::Func2(FString param)
{
UE_LOG(LogTemp, Error, TEXT("TestActor Func2 param = %s"), *param);
}
void ATestActor::BeginPlay()
{
Super::BeginPlay();
APlayerController* playerController = GetWorld()->GetFirstPlayerController();
if (nullptr == playerController)
return;
ACharacter* character = playerController->GetCharacter();
if (nullptr == character)
return;
AMyProjectCharacter* myProjectCharacter = Cast<AMyProjectCharacter>(character);
if (nullptr == myProjectCharacter)
return;
myProjectCharacter->MuitiDelagateWithNoParam.AddUObject
(this, &ThisClass::Func2, FString("MuitiDelagateWithNoParam"));
}
委托的使用理念
就项目经验而言,我感觉委托就是软件经典模式中的“观察者模式(Observer)”的具体运用,可以很好的松耦合。
比如:玩家死亡,导致UMGWidget的text数值改变,导致敌人获取经验,导致。。。。。。
按正常实现:
class UMGText
{
void BeginPlay();
void Change();
}
class Fighter
{
void BeginPlay();
void GetExprience();
}
Class Person
{
void BeginPlay();
void Dead();
}
void Person::Dead()
{
umgText->Change();
fighter->GetExperience();
}
毫无疑问,随着角色死亡影响的事情越来越多,以后我们的Person对象里会保存或者需要获取无数诸如umgText或者fighter的乱七八糟的对象,耦合度很高,
但是有了委托,你可以在Person中声明委托,然后UMGText类和Figther类中的开始函数进行获取Person的委托,然后AddUObject“Change和GetExperience函数”。然后玩家死亡的时候调用委托就行了,实现了很棒的松耦合。
class UMGText
{
void BeginPlay();
void Change();
}
void UMGText::BeginPlay()
{
Person person = GetPerson();
person->PeronDead.AddUObject(this, &ThisClass::Change);
}
class Fighter
{
void BeginPlay();
void GetExprience();
}
void Fighter::BeginPlay()
{
Person person = GetPerson();
person->PeronDead.AddUObject(this, &ThisClass::GetExprience);
}
class Person
{
DECLARE_MULTICAST_DELEGATE(FPeronDead);
FPeronDead PeronDead;
void Dead();
}
void Person::Dead()
{
PeronDead.Broadcast();
}