Blog / November 1, 2024 / 5 mins read / By Suneet Agrawal

Delegates in Unreal

Delegates in Unreal Engine play an essential role in creating modular, decoupled, and event-driven code. They allow one object to call a function on another object without hard-wiring the dependency between them, which is perfect for implementing event-driven behavior. In Unreal, delegates are especially useful in game development to handle events, respond to actions, or manage callbacks.

In this blog, we’ll go through the different types of delegates in Unreal Engine, explaining what they are, how they differ, and providing code examples for each type.

What is a Delegate?

A delegate in Unreal is a type-safe callback mechanism that allows one part of code to “delegate” functionality to another. Delegates are powerful tools to manage responses to actions, such as when a player triggers a specific action or when a game object completes an animation or reaches a target location.

In Unreal Engine, there are four main types of delegates:

  1. Single-cast Delegates
  2. Multi-cast Delegates
  3. Dynamic Single-cast Delegates
  4. Dynamic Multi-cast Delegates

Let’s take a deeper look into each type.

1. Single-cast Delegates

A single-cast delegate is a delegate that can have only one binding at any given time. This is useful for situations where only one object needs to respond to a particular event.

How to Declare and Use Single-cast Delegates
// Declare a Single-cast Delegate
DECLARE_DELEGATE(FOnHealthChanged);

// A class using Single-cast Delegate
class APlayerCharacter : public ACharacter
{
public:
    // The single-cast delegate instance
    FOnHealthChanged OnHealthChangedDelegate;

    void TakeDamage(float DamageAmount)
    {
        Health -= DamageAmount;
        OnHealthChangedDelegate.ExecuteIfBound(); // Trigger the delegate
    }
};
Binding and Executing a Single-cast Delegate
APlayerCharacter* Player = GetPlayerCharacter();

Player->OnHealthChangedDelegate.BindLambda([]()
{
    UE_LOG(LogTemp, Warning, TEXT("Player's health has changed!"));
});

Player->TakeDamage(50.0f); // This will call the delegate if health changes

2. Multi-cast Delegates

A multi-cast delegate can bind multiple functions to a single event, allowing multiple listeners to respond when the event occurs. Multi-cast delegates are perfect when multiple systems or actors need to respond to the same event, such as a global game event that should notify various UI elements.

How to Declare and Use Multi-cast Delegates
// Declare a Multi-cast Delegate
DECLARE_MULTICAST_DELEGATE(FOnPlayerDied);

// A class using Multi-cast Delegate
class AGameModeBase : public AGameMode
{
public:
    // The multi-cast delegate instance
    FOnPlayerDied OnPlayerDiedDelegate;

    void PlayerDeath()
    {
        OnPlayerDiedDelegate.Broadcast(); // Notify all listeners
    }
};
Binding and Executing a Multi-cast Delegate
AGameModeBase* GameMode = GetWorld()->GetAuthGameMode<AGameModeBase>();

// Binding multiple listeners
GameMode->OnPlayerDiedDelegate.AddLambda([]()
{
    UE_LOG(LogTemp, Warning, TEXT("Listener 1: Player has died!"));
});

GameMode->OnPlayerDiedDelegate.AddLambda([]()
{
    UE_LOG(LogTemp, Warning, TEXT("Listener 2: Player has died!"));
});

// Trigger the delegate
GameMode->PlayerDeath();

When PlayerDeath() is called, both listeners will log a message to the console.

3. Dynamic Single-cast Delegates

Dynamic single-cast delegates can be serialized and exposed to Blueprints. They are useful when you need to bind a function that can be called in both C++ and Blueprints. This is ideal when creating modular systems that may be extended by designers in the Unreal Editor.

How to Declare and Use Dynamic Single-cast Delegates
// Declare a Dynamic Single-cast Delegate
DECLARE_DYNAMIC_DELEGATE(FOnHealthRestored);

class AHealingStation : public AActor
{
    GENERATED_BODY()

public:
    // The dynamic single-cast delegate instance
    FOnHealthRestored OnHealthRestoredDelegate;

    void HealPlayer()
    {
        OnHealthRestoredDelegate.ExecuteIfBound();
    }
};
Binding a Dynamic Single-cast Delegate
AHealingStation* HealingStation = GetWorld()->SpawnActor<AHealingStation>();

HealingStation->OnHealthRestoredDelegate.BindDynamic(this, &AMyClass::OnPlayerHealed);

void AMyClass::OnPlayerHealed()
{
    UE_LOG(LogTemp, Warning, TEXT("Player healed through a dynamic delegate!"));
}

HealingStation->HealPlayer(); // This will trigger OnPlayerHealed

4. Dynamic Multi-cast Delegates

Dynamic multi-cast delegates can bind multiple functions and are also exposed to Blueprints. They are useful for managing events where both C++ and Blueprint classes might need to respond, making them ideal for gameplay events such as starting or ending a game round.

How to Declare and Use Dynamic Multi-cast Delegates
// Declare a Dynamic Multi-cast Delegate
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnRoundStarted);

class AGameModeBase : public AGameMode
{
    GENERATED_BODY()

public:
    // The dynamic multi-cast delegate instance
    UPROPERTY(BlueprintAssignable)
    FOnRoundStarted OnRoundStartedDelegate;

    void StartRound()
    {
        OnRoundStartedDelegate.Broadcast(); // Notify all bound listeners
    }
};
Binding a Dynamic Multi-cast Delegate in Blueprints

Dynamic multi-cast delegates can be bound directly in Blueprints by dragging the OnRoundStarted event into the Blueprint editor. This makes it easy to integrate C++ and Blueprint functionality for events that multiple Blueprint scripts need to react to.

Using Parameters and Return Types with Delegates

Delegates can be defined to accept parameters and, in some cases, a return type, which allows data to be passed directly through the delegate. Here is a breakdown of what is supported by each type:

  • Single-cast delegates: Support parameters and return types.
  • Multi-cast delegates: Support parameters but not return types, as multiple listeners could return conflicting values.
  • Dynamic single-cast delegates: Support parameters but not return types.
  • Dynamic multi-cast delegates: Support parameters but not return types.

To define delegates with parameters, you can use Unreal’s parameterized macros, such as DECLARE_DELEGATE_OneParam or DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam. To add a return type, use macros like DECLARE_DELEGATE_RetVal.

For example, here’s a single-cast delegate with a float parameter and a bool return type:

// Declare a Single-cast Delegate with Parameter and Return Type
DECLARE_DELEGATE_RetVal_OneParam(bool, FOnHealthCheck, float);

class APlayerCharacter : public ACharacter
{
public:
    // The single-cast delegate instance with a float parameter and a bool return type
    FOnHealthCheck OnHealthCheckDelegate;

    bool CheckHealth(float Threshold)
    {
        if (OnHealthCheckDelegate.IsBound())
        {
            return OnHealthCheckDelegate.Execute(Threshold);
        }
        return false;
    }
};

You could then bind a function or lambda to this delegate that returns true or false based on the health threshold.

Comparison Table

Delegate Type Supports Parameters Supports Return Type Blueprint Accessible
Single-cast Delegate Yes Yes No
Multi-cast Delegate Yes No No
Dynamic Single-cast Delegate Yes No Yes
Dynamic Multi-cast Delegate Yes No Yes

Conclusion

Delegates are essential for creating event-driven systems in Unreal Engine, providing flexibility, modularity, and ease of use. Understanding when to use each type of delegate (single-cast vs. multi-cast, dynamic vs. non-dynamic) helps in designing scalable and responsive game systems. Using the right delegate type ensures that your code is maintainable, whether working in C++ or Blueprint, and opens up creative ways to trigger events across your game’s actors and components.

Comments