天天看點

事件處理(Handling Events)和委托(Delegate)代碼示例(二)【UE4】【C++】

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 的定義(增加 bool 參數)

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)      

其次,建立一個新 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 到場景中,當我們進入觸發區域,隻有最後一個拖進場景的燈會亮,這是因為委托隻綁定了最後一個執行個體函數。

(未完待續)

繼續閱讀