DEV Community

Pavel Zabelin
Pavel Zabelin

Posted on

# Delegates: Observer Pattern Implementation in Unreal Engine

Delegates are an implementation of the Observer pattern in Unreal Engine, a behavioral design pattern that allows an object (the publisher) to inform its subscribed objects (observers) of important changes while maintaining low coupling between objects. This enables changes in the logic of different modules without worrying about unwanted consequences.

It is likely that Unreal Engine developers first encountered delegates when using the UShapeComponent, where they subscribed to the OnComponentBeginOverlap or OnComponentEndOverlap event.

Example with addDynamic subscription to callback

/** Code for when something overlaps this component */
UFUNCTION()
void OnSphereBeginOverlap(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor,
    UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult);

void UTP_PickUpComponent::BeginPlay()
{
    Super::BeginPlay();

    // Register our Overlap Event
    OnComponentBeginOverlap.AddDynamic(this, &UTP_PickUpComponent::OnSphereBeginOverlap);
}
Enter fullscreen mode Exit fullscreen mode

Even if this happens through the Event Dispatcher, which provides the same functionality as delegates but in the Blueprint editor.

Screenshot of Event Dispatcher from OnComponentBeginOverlap

Screenshot of Event Dispatcher

To start using delegates, you need to:

1 Declare it using a macro, at the end of which you specify the number of parameters that the delegate will pass.

Example of delegate with explained parameters

DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnPickUp, AObserverExampleCharacter*, PickUpCharacter);
Enter fullscreen mode Exit fullscreen mode

2 Declare the presence of an instance of this delegate. To make the delegate work in Blueprints, use UPROPERTY(BlueprintAssignable).

Example of the delegate object

/** Delegate to whom anyone can subscribe to receive this event */
UPROPERTY(BlueprintAssignable, Category = "Interaction")
FOnPickUp OnPickUp;
Enter fullscreen mode Exit fullscreen mode

3 Design and implement the logic where the delegate will notify subscribers about an event and use the Broadcast() method at this point.

Example with broadcast

OnPickUp.Broadcast(Character);
Enter fullscreen mode Exit fullscreen mode
  1. Use the AddDynamic() method in all subscribers, passing the subscriber object and callback function. The method must accept the same parameters that the delegate will send. It should also be marked with UFUNCTION.

Example with subscription to the event and callback method declaration

UFUNCTION() 
void HandlePickUp(AObserverExampleCharacter* PickUpCharacter);
Enter fullscreen mode Exit fullscreen mode
void UExampleComponent::BeginPlay()
{
    Super::BeginPlay();

    AActor* Owner = GetOwner();
    if (Owner)
    {
        UTP_PickUpComponent* PickUpComponent = Owner->FindComponentByClass<UTP_PickUpComponent>();
        if (PickUpComponent)
        {
            PickUpComponent->OnPickUp.AddDynamic(this, &UYourComponent::HandlePickUp);
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Now let's repeat it with an example

Let's develop a health system and a response to damage received. We'll start with the health component, which will store variables related to health, handle taking damage, and also notify about the damage received using a delegate.

Delegate declaration

Delegate callback

We also want to spawn a camera shake when damage is taken. Let's delegate this to the DamageVisualizationComponent. In the BeginPlay, we'll find the health component on the owner and subscribe to the OnDamageReceived event.

Subscribe to delegate

Additionally, let's make the damage apply effects to the character. We'll create an Effects Component, do the same as before—subscribe in BeginPlay and handle the OnDamageReceived event.

Subscribe to delegate

For the example, we also need to figure out how to take damage. Let's create a platform, where stepping on it will cause the player to take damage.

Damage event

And here's the result: the health component notifies all of its subscribers, who can then implement the necessary logic.

Delegae usage example

In some cases, using delegates may be excessive, but an architecture where responsibility is divided between modules is always convenient.

Types of Delegates

In this article, only the Dynamic_Multicast delegate was used, but this is not always necessary. Use Dynamic if the delegate will be used in Blueprints. If there will only be one subscriber, use Single instead of Multicast when declaring the delegate.

Conclusion

Why use the Observer pattern:

The Observer pattern is useful in complex applications and games because it enhances modularity and minimizes coupling between components. Instead of interacting directly with each other, components can subscribe to events occurring in other parts of the system. This reduces dependencies between components, making them easier to test and modify since one component can be changed or replaced without rewriting the other parts of the system. In games, this is especially important because the logic often changes based on the game state or player actions, and the Observer pattern allows such changes to be handled easily.

Summary:

Delegates in Unreal Engine are a powerful tool for implementing the Observer pattern, which helps manage notifications between different components of the system with minimal coupling. Although delegates may be unnecessary in some cases, their use promotes a cleaner and more structured architecture, making it easier to maintain and expand the project. Thus, delegates play a key role in simplifying and improving the architecture of gameplay in Unreal Engine, making the system more flexible and easily modifiable.

Top comments (0)