Pages

sábado, 16 de janeiro de 2021

Creating Delegates in C++

In this article, we will see how to create Delegates in C++. In a simple way, we can define Delegate as a reference to a function.

Unreal Engine has a wide variety of types of Delegates that make it confusing to understand all of these options.

For this reason, in this article, we will focus on a useful functional example that you can adapt to your project.

In the first part of the article, I will present the necessary steps to use a Delegate, showing some excerpts from the example code. In the example usage, you will see the complete example code.

In the example, we are going to create a class called APlatformTrigger. When an Actor overlaps an APlatformTrigger instance, a Delegate will be used to notify that the Platform has been activated. Other classes can bind functions to the Delegate that will be executed when the Platform is activated.

These are the necessary steps for creating the Delegate:

Step 1 - Create a Delegate type

Unreal Engine has several Macros that are used to create a Delegate type. The code below created a delegate type named FDelegateTrigger.

DECLARE_DYNAMIC_MULTICAST_DELEGATE(FDelegateTrigger);

This Macro needs to be in the header file of the APlatformTrigger class above the UCLASS() Macro. This Delegate is DYNAMIC to allow binding in Blueprints and is MULTICAST to allow binding more than one function. 

Step 2 - Define a variable using the created Delegate type

The line below defines a variable of type FDelegateTrigger. Macro UPROPERTY(BlueprintAssignable) is being used to allow binding in Blueprints. 

UPROPERTY(BlueprintAssignable)
FDelegateTrigger OnPlatformTriggered;

Step 3 - Run the Delegate

In our example, the Delegate will be executed when the Platform is overlap. This is done by calling the Broadcast() function of the Delegate variable.

OnPlatformTriggered.Broadcast();


The next steps are done in another C++ class. In this example, it is in a class called AExplosionHandle. 

Step 4 - Define a pointer to the class that has the Delegate

The pointer is defined as UPROPERTY() so that the APlatformTrigger instance can be selected in the level editor. 

UPROPERTY(EditAnywhere, Category = "Delegate Test")
APlatformTrigger* PlatformTriggerInstance;

Step 5 - Define a UFUNCTION() that will be bound to the Delegate

In the example is a function called Explode() that will activate a particle system. 

UFUNCTION()
void Explode();

Step 6 - Bind the function to the Delegate

Binding is done in the BeginPlay() function. The PlatformTriggerInstance pointer is used to call the AddDynamic() function of the Delegate variable OnPlatformTriggered. 

PlatformTriggerInstance->OnPlatformTriggered.AddDynamic(this,
                                                        &AExplosionHandle::Explode);

When the Delegate of the APlatformTrigger class is executed, the Explode() function of the AExplosionHandle class will be called.

In the example usage, we will also see how to bind using Blueprints.


Example usage:

For this example, you will need a C++ project that has the Third Person template with Starter Content.

Create a C++ class with the name PlatformTrigger using the Actor class as the parent class. The PlatformTrigger.h file contains the Macro that creates the type Delegate FDelegateTrigger and a variable defined with this type:

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "PlatformTrigger.generated.h"

DECLARE_DYNAMIC_MULTICAST_DELEGATE(FDelegateTrigger);

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

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

public:	
	// Called every frame
	virtual void Tick(float DeltaTime) override;
	
	virtual void NotifyActorBeginOverlap(AActor* OtherActor) override;
	
	UPROPERTY(VisibleAnywhere)
	USceneComponent* RootScene;

	UPROPERTY(VisibleAnywhere)
	UStaticMeshComponent* StaticMeshComponent;
	
	UPROPERTY(BlueprintAssignable)
	FDelegateTrigger OnPlatformTriggered;
};

The PlatformTrigger class uses the Static Mesh Shape_Cylinder. The height was reduced by changing the scale to 0.1 on the Z axis. The Delegate is executed in the NotifyActorBeginOverlap() function. This is the contents of the PlatformTrigger.cpp file:

#include "PlatformTrigger.h"

// Sets default values
APlatformTrigger::APlatformTrigger()
{
  // Set this actor to call Tick() every frame.
  PrimaryActorTick.bCanEverTick = true;
	
  RootScene = CreateDefaultSubobject<USceneComponent>("RootScene");
  RootComponent = RootScene;

  StaticMeshComponent = CreateDefaultSubobject<UStaticMeshComponent>("StaticMesh");
  StaticMeshComponent->SetupAttachment(RootScene);
  
  ConstructorHelpers::FObjectFinder<UStaticMesh> MeshFile(
    TEXT("/Game/StarterContent/Shapes/Shape_Cylinder.Shape_Cylinder"));

  if (MeshFile.Succeeded())
  {
    StaticMeshComponent->SetStaticMesh(MeshFile.Object);
  }
  
  StaticMeshComponent->SetCollisionResponseToAllChannels(
                                      ECollisionResponse::ECR_Overlap);
  StaticMeshComponent->SetRelativeScale3D(FVector(1.0f, 1.0f, 0.1f));
}


void APlatformTrigger::NotifyActorBeginOverlap(AActor* OtherActor)
{
  Super::NotifyActorBeginOverlap(OtherActor);
	
  OnPlatformTriggered.Broadcast();
}

// Called when the game starts or when spawned
void APlatformTrigger::BeginPlay()
{
  Super::BeginPlay();			
}

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

Create another C++ class with the name ExplosionHandle using the Actor class as the parent class. This class has a pointer to the PlatformTrigger class and has a UFUNCTION() that will be bound to the Delegate. The ExplosionHandle.h file looks like this: 

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "ExplosionHandle.generated.h"

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

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

public:	
	// Called every frame
	virtual void Tick(float DeltaTime) override;
	
	UPROPERTY(VisibleAnywhere)
	USceneComponent* RootScene;
	
	UPROPERTY(VisibleAnywhere)	
	class UParticleSystemComponent* ParticleSystemComponent;
	
	UPROPERTY(EditAnywhere, Category = "Delegate Test")
	class APlatformTrigger* PlatformTriggerInstance;
	
	UFUNCTION()
	void Explode();
};

In the constructor, we have the configuration of the particle system that represents the explosion. In the BeginPlay() function, the Explode() function is bound to the Delegate. Don't forget to add the necessary #include. This is the contents of the ExplosionHandle.cpp file:

#include "ExplosionHandle.h"
#include "PlatformTrigger.h"
#include "Particles/ParticleSystemComponent.h"

// Sets default values
AExplosionHandle::AExplosionHandle()
{
  // Set this actor to call Tick() every frame.
  PrimaryActorTick.bCanEverTick = true;

  // RootComponent initialization 
  RootScene = CreateDefaultSubobject<USceneComponent>("RootScene");
  RootComponent = RootScene;
  
  // ParticleSystemComponent initialization
  ParticleSystemComponent = CreateDefaultSubobject<UParticleSystemComponent>(
                              TEXT("ParticleSystemComponent"));
  ParticleSystemComponent->SetupAttachment(RootScene);
  ParticleSystemComponent->SetAutoActivate(false);
  
  ConstructorHelpers::FObjectFinder<UParticleSystem> ParticleTemplate(
    TEXT("/Game/StarterContent/Particles/P_Explosion.P_Explosion"));

  if (ParticleTemplate.Succeeded())
  {
    ParticleSystemComponent->SetTemplate(ParticleTemplate.Object);
  }
  
}

void AExplosionHandle::BeginPlay()
{
  Super::BeginPlay();
	
  if(PlatformTriggerInstance)
  {
    PlatformTriggerInstance->OnPlatformTriggered.AddDynamic(this,
&AExplosionHandle::Explode);
  }
	
}

void AExplosionHandle::Explode()
{
  ParticleSystemComponent->Activate();	
}

void AExplosionHandle::Tick(float DeltaTime)
{
  Super::Tick(DeltaTime);
}

Compile the C++ code and add an instance of PlatformTrigger at the level. Add an instance of ExplosionHandle near the platform and on the Details tab of the instance, in the Delegate Test category, select an instance of PlatformTrigger.

 

Start the game. When you overlap the platform with the Character, the explosion will be activated.


Now, let's bind a custom Event to the Delegate using Blueprints.

Select the PlatformTrigger instance that is at the level and open the Level Blueprint.


Right click on the Event Graph and select the option Create a Reference to PlatformTrigger.


Drag from the node's blue pin and drop it in the Event Graph to bring up the context menu. Search for platform and choose the Assign On Platform Triggered option. This will add the Bind node and a new Custom Event.


Add the Event BeginPlay and connect to the Bind Event node. Add the Print String action and connect to the Custom Event node. Put a message in the Print String node and compile the Level Blueprint. The final Blueprint looks like this:


Run the game again. When the platform is activated, the Delegate will call the function that activates the explosion and will also call the Custom Event defined in Blueprints: 


Table of Contents C++