3. 創建帶參數的委托
我們可以通過修改委托的簽名來使其接受參數
比如我們需要接受一個參數的話,可以在 GameMode 中這樣聲明:
DECLARE_DELEGATE_OneParam(FParamDelegateSignature, FLinearColor)
注意:這個宏與之前稍有不同,后綴多出了一個 _OneParam ,而且我們還需要指定接受參數的類型——本例為 FLinearColor
接着再添加一個 FParamDelegateSignature 成員
FParamDelegateSignature MyParameterDelegate;
這和之前一樣,創建一個委托實例作為 GameMode 成員
然后創建一個 Actor 類,取名為 ParamDelegateListener,
在頭文件中添加以下聲明
UFUNCTION() void SetLightColor(FLinearColor LightColor); UPROPERTY() UPointLightComponent* PointLight;
ParamDelegateListener.cpp
#include "Test.h" #include "UE4TestGameMode.h" #include "ParamDelegateListener.h" // Sets default values AParamDelegateListener::AParamDelegateListener() { // Set this actor to call Tick() every frame. You can turn this off to improve performance if you don't need it. PrimaryActorTick.bCanEverTick = true; PointLight = CreateDefaultSubobject<UPointLightComponent>("PointLight"); RootComponent = PointLight; } // Called when the game starts or when spawned void AParamDelegateListener::BeginPlay() { Super::BeginPlay(); UWorld* TheWorld = GetWorld(); if (TheWorld != nullptr) { AGameMode* GameMode = Cast<AGameMode>(UGameplayStatics::GetGameMode(TheWorld)); AUE4TestGameMode * MyGameMode = Cast<AUE4TestGameMode>(GameMode); if (MyGameMode != nullptr) { // Binds a UObject-based member function delegate. UObject delegates keep a weak reference to your object. You can use ExecuteIfBound() to call them.(注意綁定的還是 UFUNCTION) MyGameMode->MyParameterDelegate.BindUObject(this, &AParamDelegateListener::SetLightColor); } } } // Called every frame void AParamDelegateListener::Tick( float DeltaTime ) { Super::Tick( DeltaTime ); } // 1個參數 void AParamDelegateListener::SetLightColor(FLinearColor LightColor) { PointLight->SetLightColor(LightColor); }
回到 MyTriggerVollume.cpp,在 NotifyActorBeginOverlap 函數中添加以下代碼:
MyGameMode->MyParameterDelegate.ExecuteIfBound(FLinearColor(1, 0, 0, 1)); // 帶一個參數
與之前不同的是,我們需要多指定一個參數,參數類型和我們之前的委托聲明一致。
顯然,MyTriggerVolume 壓根就無需知道 ParamDelegateListener 的存在,卻通過 GameMode 就可以調用 ParamDelegateListener 的函數了,很大程度上降低了類間的耦合度。
解綁委托方式與之前相同,不再贅述。
4.通過委托綁定傳遞負載數據(Payload Data)
稍加修改,我們就可以在委托被調用時傳遞額外創建時的參數(additional creation-time parameter),即我們在 MyTriggerVolume 中的調用方式不變,仍然是 ExecuteIfBound(FLinearColor(1, 0, 0, 1)),但可以額外添加一些負載數據,在 ParamDelegateListener 中的 BindUObject 上添加。
首先修改 AParamDelegateListener::BeginPlay 中的 BindUObject,為其添加一個 bool 負載數據
MyGameMode->MyParameterDelegate.BindUObject(this, &AParamDelegateListener::SetLightColor, false);
並修改 SetLightColor 的定義
UFUNCTION() void SetLightColor(FLinearColor LightColor, bool EnableLight);
// 2個參數 void AParamDelegateListener::SetLightColor(FLinearColor LightColor, bool EnableLight) { PointLight->SetLightColor(LightColor); PointLight->SetVisibility(EnableLight); }
注意:負載數據並不局限於帶參數的委托,其他的委托形式也可以使用
5. 多播委托(Multicast Delegate)
之前說的委托,都是只綁定了一個函數指針,而多播委托綁定的是一個函數指針集合,每個函數指針都有對應的一個委托句柄,當廣播(Broadcast)委托的時候,他們將會被激活。
首先在 GameMode 中添加多播的委托聲明
需要明確聲明為多播
DECLARE_MULTICAST_DELEGATE(FMulticastDelegateSignature)
接着在類中聲明一個 FMulticastDelegateSignature 成員
FMulticastDelegateSignature MyMulticastDelegate;
其次,創建一個新 Actor 類,命名為 MulticastDelegateListener
在其頭文件中添加以下聲明:
FDelegateHandle MyDelegateHandle; UPROPERTY() UPointLightComponent* PointLight; UFUNCTION() void ToggleLight(); virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override;
大部分和之前的 Listener 類很相似,但是多一個 委托句柄實例,將用它來存儲委托實例的引用,我們的添加(AddUObject)和移除(Remove)都需要它作為參數
源文件的代碼如下:
// Sets default values AMulticastDelegateListener::AMulticastDelegateListener() { // Set this actor to call Tick() every frame. You can turn this off to improve performance if you don't need it. PrimaryActorTick.bCanEverTick = true; PointLight = CreateDefaultSubobject<UPointLightComponent>("PointLight"); RootComponent = PointLight; } // Called when the game starts or when spawned void AMulticastDelegateListener::BeginPlay() { Super::BeginPlay(); UWorld* TheWorld = GetWorld(); if (TheWorld != nullptr) { AGameMode* GameMode = Cast<AGameMode>(UGameplayStatics::GetGameMode(TheWorld)); AUE4TestGameMode * MyGameMode = Cast<AUE4TestGameMode>(GameMode); if (MyGameMode != nullptr) { // Adds a UObject-based member function delegate. UObject delegates keep a weak reference to your object. // 注冊一個對象方法 MyDelegateHandle = MyGameMode->MyMulticastDelegate.AddUObject(this, &AMulticastDelegateListener::ToggleLight); } } } // Called every frame void AMulticastDelegateListener::Tick( float DeltaTime ) { Super::Tick( DeltaTime ); } void AMulticastDelegateListener::ToggleLight() { PointLight->ToggleVisibility(); } void AMulticastDelegateListener::EndPlay(const EEndPlayReason::Type EndPlayReason) { Super::EndPlay(EndPlayReason); UWorld* TheWorld = GetWorld(); if (TheWorld != nullptr) { AGameMode* GameMode = Cast<AGameMode>(UGameplayStatics::GetGameMode(TheWorld)); AUE4TestGameMode * MyGameMode = Cast<AUE4TestGameMode>(GameMode); if (MyGameMode != nullptr) { // Removes a function from this multi-cast delegate's invocation list (performance is O(N)). Note that the order of the delegates may not be preserved! MyGameMode->MyMulticastDelegate.Remove(MyDelegateHandle); } } }
MyTriggerVolume.cpp 的實現為:
// Broadcasts this delegate to all bound objects, except to those that may have expired. MyGameMode->MyMulticastDelegate.Broadcast();
廣播函數很像我們之前的 ExecuteIfBound函數,但有一點不同,它不需要檢查是否有函數綁定在委托上。
最后的效果是,如果我們往場景中拖放了四五個MulticastDelegateListener,當我們進入觸發區域,它們的燈會同時打開或關閉,因為每個實例函數都被添加到委托集合當中;
如果拖放了四五個DelegateListener 到場景中,當我們進入觸發區域,只有最后一個拖進場景的燈會亮,這是因為委托只綁定了最后一個實例函數。
