Delegates and Events

Imagine the following scenario: We have a Death Match Shooter Game with some players and want to implement a restart functionality. The game should restart when only one player is remaining alive.

A naive approach would be to continuously ask (e.g., every five seconds or every tick), how many players are alive and restart the game if necessary. This approach is problematic because we run the code a lot more often than necessary which is bad for performance.

This is where Delegates and Events come into play. We want to send an event only if the thing we want to be notified about actually happens. So every time a player dies, we want to fire the “PlayerDiedEvent” and everybody that is interested in this event has subscribed to the PlayerDiedDelegate and will be notified. Everybody who gets notified is free to decide how to react to the event. In our case, we want to check if only one player is remaining and restart the game.

If you are familiar with the Observer Pattern or with the https://reactivex.io/ library you know what Delegates are in Unreal Engine.

Now that we have covered the motivation for Event-driven programming, let’s get into an example in UE.

How to use Delegates in UE

For this example, we want to implement an attribute component, which stores a character’s attributes, like health and stamina. We want the attribute component to fire an event whenever its health value changes. Additionally, we want a health bar UI widget, which updates its appearance, whenever the health of the player’s character (which owns the attribute component) changes. This will be implemented in Blueprint. To show also how to bind and react to events in Cpp, we want to trigger a hit-flash material on the character’s mesh, whenever it takes damage.

Let’s start with the attribute component:

DECLARE_DYNAMIC_MULTICAST_DELEGATE_FourParams(FOnHealthChanged, USAttributeComponent*, OwningComp, AActor*, InstigatorActor, float, HealthNew, float, HealthDelta);

UCLASS(ClassGroup=(Custom), meta=(BlueprintSpawnableComponent))
class ACTIONROGUELIKE_API USAttributeComponent : public UActorComponent
{
	GENERATED_BODY()

public:
	USAttributeComponent();

	UPROPERTY(BlueprintAssignable)
	FOnHealthChanged OnHealthChanged;

	UFUNCTION(BlueprintCallable)
	bool ApplyHealthChange(float Delta);

protected:
	UPROPERTY(EditAnywhere, BlueprintReadOnly)
	float Health;
	UPROPERTY(EditAnywhere, BlueprintReadOnly)
	float HealthMax;

	virtual void BeginPlay() override;
};

Let’s walk through the code. First, we use the macro DECLARE_DYNAMIC_MULTICAST_DELEGATE_FourParams a delegate named FOnHealthChanged with four parameters. For each parameter we want the delegate to exhibit, we add its type and name comma-separated as input to the macro.

Now that we have declared our delegate type, we can use it anywhere we want. In this case, we assign it to our USAttributeComponent in line 12. It should have the UPROPERTY() assigned such that UE handles null-checks on the delegate automatically, in case of garbage collection. BlueprintAssignable means that we can use the delegate in Blueprints.

For now, we have only created a delegate, but we still need two things: trigger and listen to events on the delegate. We trigger events in the component’s implementation, like so:

bool USAttributeComponent::ApplyHealthChange(AActor* InstigatorActor, float Delta)
{
	AActor* OwnerActor = GetOwner();
	float HealthBefore = Health;
	Health += Delta;
	Health = FMath::Clamp(Health, 0.f, HealthMax);
	float ActualDelta = Health - HealthBefore;
	OnHealthChanged.Broadcast(this, InstigatorActor, Health, Delta);
	return true;
}

As we can see, we apply the Delta to our Health variable and broadcast an event on the OnHealthChanged delegate. We can see, that we provide four values to the broadcast call, which are of the types we have defined in the DECLARE_DYNAMIC_MULTICAST_DELEGATE_FourParams macro. Broadcasting an event in this way will notify all observers that have subscribed to it.

Finally, in our BP health bar widget, in our event graph, we bind (subscribe) to the delegate like so:

Binding to the OnHealthChanged Delegate in BP

As we can see, we get a reference to the AttributeComponent and then use the BP node “Bind Event to …” to bind to the delegate via event. We see, that the event (the red node) has the parameters we defined on the delegate as output pins. Whenever we call broadcast() on the delegate in the attribute component, the code starting from the event node will run and update the health bar to represent the remaining health.

Of course, we can also react to events in Cpp directly, like so:

void ASCharacterBase::PostInitializeComponents()
{
	Super::PostInitializeComponents();

	AttributeComp->OnHealthChanged.AddDynamic(this, &ASCharacterBase::HandleHealthChanged);
}

void ASCharacterBase::HandleHealthChanged(USAttributeComponent* OwningComp, AActor* InstigatorActor, float HealthNew,
	float HealthDelta)
{
	if (HealthNew <= 0.f && HealthDelta < 0.f)
	{
		OnPawnDied.Broadcast(InstigatorActor);
	}
}

Here we see two functions of the ASCharacterBase, which owns our AttributeComponent. In the PostInitializeComponents, we bind to the delegate, by calling the macro AddDynamic on it. This macro function expects two parameters: A pointer to a class instance and a function pointer to the function that should be called (on provided class instance) when the OnHealthChanged delegate fires an event. In our case, we bind to the function HandleHealthChanged on the this-pointer. The HandleHealthChanged checks if the health decreased to or below zero, in which case we fire another event to notify everyone that the Character has died. We then could use this event to restart the game if only one character is remaining.

Leave a comment

Your email address will not be published.