Example of handling events and delegate code [UE4] [C]

Posted by ealderton on Fri, 25 Oct 2019 04:26:10 +0200

This article is from https://blog.csdn.net/panda1234lee/article/details/64123532

3. Create delegation with parameters

We can modify the signature of a delegate to accept parameters.

For example, if we need to accept a parameter, we can declare it in GameMode.

DECLARE_DELEGATE_OneParam(FParamDelegateSignature, FLinearColor)

Note: this macro is slightly different from the previous one. There is an extra ﹐ OneParam suffix, and we also need to specify the type of parameter to be accepted - in this case, FLinearColor.

Then add another FParamDelegateSignature member.

FParamDelegateSignature myParamterDelegate;

As before, create a delegate instance as a GameMode member.

Then create an Actor class called paramtigerlistener.

Add a declaration to the header file

// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "Components/PointLightComponent.h"
#include "ParamTiggerListener.generated.h"

UCLASS()
class DELEGATETEST_API AParamTiggerListener : public AActor
{
	GENERATED_BODY()
	
public:	
	// Sets default values for this actor's properties
	AParamTiggerListener();

protected:
	// Called when the game starts or when spawned
	virtual void BeginPlay() override;

public:	
	// Called every frame
	virtual void Tick(float DeltaTime) override;
	UFUNCTION()
	void SetLightColor(FLinearColor LightColor);
	UPROPERTY()
	UPointLightComponent* PointLight;

};

ParamDelegateListener.cpp 

// Fill out your copyright notice in the Description page of Project Settings.


#include "ParamTiggerListener.h"
#include "GameFramework/WorldSettings.h"
#include "Kismet/GameplayStatics.h"
#include "DelegateTestGameModeBase.h"

// Sets default values
AParamTiggerListener::AParamTiggerListener()
{
 	// 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");
	this->RootComponent = PointLight;
}

// Called when the game starts or when spawned
void AParamTiggerListener::BeginPlay()
{
	Super::BeginPlay();
	UWorld* TheWorld = GetWorld();
	if (TheWorld != nullptr)
	{
		AGameModeBase* GameMode = Cast<AGameModeBase>(UGameplayStatics::GetGameMode(TheWorld));
		ADelegateTestGameModeBase* myGameMode = Cast<ADelegateTestGameModeBase>(GameMode);
		if (myGameMode != nullptr)
		{
			myGameMode->myParamterDelegate.BindUObject(this, &AParamTiggerListener::SetLightColor);
		}
	}
}

// Called every frame
void AParamTiggerListener::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);

}

void AParamTiggerListener::SetLightColor(FLinearColor LightColor)
{
	PointLight->SetLightColor(LightColor);
}

Go back to MyTrigger.cpp and add the following code to the notifyactor beginoverlap function:

myGameMode->myParamterDelegate.ExecuteIfBound(FLinearColor(1, 0, 0, 1));

Unlike before, we need to specify one more parameter, and the parameter type is consistent with our previous delegation declaration.

Obviously, MyTrigger does not need to know the existence of ParamDelegateListener at all, but it can call the functions in ParamDelegateListener through GameMode, which greatly reduces the coupling between classes.

4. Pass load data through delegate binding

With minor modifications, we can pass the extra additional creation-time parameter when we delegate the call, that is, the way we invoke it in MyTrriger is unchanged, and it is ExecuteIfBound(FLinearColor(1, 0, 0, 1)), but it can add some load data and add it to BindUObject in ParamDelegateListener.

First amendment

Aparamdelegatelistener:: BindUObject in beginplay, add a bool load data for it.

myGameMode->myParamterDelegate.BindUObject(this, &AParamTiggerListener::SetLightColor, false);

And modify the definition of SetLightColor

void SetLightColor(FLinearColor LightColor, bool EnableLight);
void AParamTiggerListener::SetLightColor(FLinearColor LightColor, bool EnableLight)
{
	PointLight->SetLightColor(LightColor);
	PointLight->SetVisibility(EnableLight);
}

Note: load data is not limited to delegation with parameters. Other forms of delegation can also be used.

5. Multicast delegate

As mentioned before, delegates are only bound with one function pointer, while multicast delegates are bound with a function pointer set. Each function pointer has a corresponding delegate handle. When broadcasting delegates, they will be activated.

First, add the multicast delegation declaration in GameMode

Need to be explicitly declared multicast

DECLARE_MULTICAST_DELEGATE(FMulticastDelegateSignature)

Then declare a member of FMulticastDelegateSignature in the class.

FMulticastDelegateSignature MyMulticastDelegate;	


Second, create an Actor class called MulticastDelegateListener.

Add a declaration to its header file

	//Delegate handle
	FDelegateHandle MyDelegateHandle;
	UPROPERTY()
	UPointLightComponent* PointLight;

	UFUNCTION()
	void ToggleLight();

	virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override;

Most of them are similar to the previous Listener class, but there is an additional instance of the delegate handle, which will be used to store the reference of the delegate instance. We need it as a parameter for our add (AddUObject) and remove (Remove).

The code of the source file is as follows:

// Fill out your copyright notice in the Description page of Project Settings.


#include "MulticastDelegateListener.h"
#include "GameFramework/WorldSettings.h"
#include "Kismet/GameplayStatics.h"
#include "DelegateTestGameModeBase.h"

// 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)
	{
		AGameModeBase* GameMode = Cast<AGameModeBase>(UGameplayStatics::GetGameMode(TheWorld));
		ADelegateTestGameModeBase* myGameMode = Cast<ADelegateTestGameModeBase>(GameMode);
		if (myGameMode != nullptr)
		{
			// Adds a UObject-based member function delegate. UObject delegates keep a weak reference to your object.
			// Register an object method
			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)
	{
		AGameModeBase* GameMode = Cast<AGameModeBase>(UGameplayStatics::GetGameMode(TheWorld));
		ADelegateTestGameModeBase* myGameMode = Cast<ADelegateTestGameModeBase>(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);
		}
	}
}


The implementation of MyTrigger.cpp is as follows:

myGameMode->MyMulticastDelegate.Broadcast();

The broadcast function is similar to our previous ExecuteIfBound function, but it does not need to check whether there is a function bound to a delegate.

The final effect is that if we drag and drop four or five multicastdelegatelisteners into the scene, when we enter the trigger area, their lights will turn on or off at the same time, because each instance function is added to the delegate collection.

If we drag and drop four or five trigger listeners into the scene, when we enter the trigger area, only the light of the last one will be on, because the delegate only binds the last instance function.