What are Delegates?
A delegate is an event that is broadcasted / sent in your game. These can be picked up by other classes / actors. Variables can also be included as parameters to the event, so you can have a copy of the relevant event data. You can have up to 8 parameters in a delegate.
Use Case
If you’ve ever had a situation where you needed one object to send information to another object, delegates are the best practice to satisfy this requirement.
Imagine a mini-golf game with a golf ball pawn, and a hole actor. If the hole actor exposes a multicast delegate, then it can inform any number of other actors when the player has put the ball in the hole. We could bind the UI to show the goal was achieved, bind the game state to record their strokes on the hole, and bind the game mode to change their mode to spectating, all from this single delegate we’ve defined.
Making use of delegates throughout your code base will make your code more performant, and more modular by providing hard edges of where your objects interact with each other.
Delegate Types
- Delegate / Dynamic Delegate
- Multicast Delegate / Dynamic Multicast Delegate
- Event
Dynamic Delegates
Dynamic Delegates can be serialized, their functions can be found by name, and they are slower than regular delegates. Only delegates declared as dynamic may be used in blueprints
Declaring Dynamic Delegates
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// AGolfGameState.h
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(
FRoundStartedEvent,
uint8, HoleNumber,
uint8, HolePar
);
public:
UPROPERTY(BlueprintAssignable, BlueprintCallable)
FRoundStartedEvent OnRoundStarted;
// AGolfGameState.cpp
if (OnRoundStarted.IsBound())
{
OnRoundStarted.Execute(1, 4);
}
// If no output parameters, you can cautiously do
OnRoundStart.ExecuteIfBound(1, 4);
Executing Dynamic Delegates
The function bound to a delegate is executed by calling the delegate’s Execute() function. You must check if delegates are “bound” before executing them. This is to make the code more safe as there may be cases where delegates have return values and output parameters that are uninitialized and subsequently accessed. Executing an unbound delegate could actually scribble over memory in some instances. You can call IsBound() to check if the delegate is safe to execute. Also, for delegates that have no return value, you can call ExecuteIfBound(), but be wary of output parameters that may be left uninitialized.
Multicast Delegates
Multicast delegates have most of the same features as single-cast delegates. They only have weak references to objects, can be used with structs, can be copied around easily, etc.
Just like regular delegates, multi-cast delegates can be loaded/saved, and triggered remotely; however, multicast delegate functions cannot use return values. They are best used to easily pass a collection of delegates around.
Multicast delegates cannot be used with BP either, unless they are made dynamic.
Declaring Multicast Delegates
1
2
3
4
5
6
7
8
// ADelegateActor.h
DECLARE_MULTICAST_DELEGATE(FLaunchCompletedDelegate);
public:
FLaunchCompletedDelegate OnLaunchCompleted;
// ADelegateActor.cpp
OnLaunchCompleted.Broadcast();
Binding Multicast Delegates
Multicast delegates can have multiple functions bound that all get called when the delegate fires. As a result, the binding functions are more array-like in semantics.
Executing Multicast Delegates
Multi-cast delegates allow you to attach multiple function delegates, then execute them all at once by calling the multi-cast delegate’s Broadcast() function. Multi-cast delegate signatures are not allowed to use a return value.
It is always safe to call Broadcast() on a multi-cast delegate, even if nothing is bound. The only time you need to be careful is if you are using a delegate to initialize output variables, which is generally very bad to do.
The execution order of bound functions when calling Broadcast() is not defined. It may not be in the order the functions were added.
Events
Events are very similar to multi-cast delegates. However, while any class can bind events, only the class that declares the event may invoke the event’s Broadcast, IsBound, and Clear functions. This means event objects can be exposed in a public interface without worrying about giving external classes access to these sensitive functions.
Event use cases might be including callbacks in purely abstract classes, and restricting external classes from invoking the Broadcast, IsBound, and Clear functions.
Events are effectively the exact same thing as multicast delegates anyway. There is no difference between an event and a multicast delegate, because DECLARE_EVENT literally just declares a new multicast delegate.
Declaring Events
1
2
3
4
5
6
7
8
9
10
11
12
13
// AHoleActor.h
DECLARE_EVENT_OneParam(AHoleActor, FReachedHoleEvent, AGolfBallPawn*);
private:
FReachedHoleEvent ReachedHoleEvent;
public:
// This event will fire when any actor tagged `GolfBall` overlaps our trigger
FReachedHoleEvent& OnReachedHole() {
return ReachedHoleEvent;
}
// AHoleActor.cpp
ReachedHoleEvent.Broadcast(GolfBallPawn);
Binding Events
Given the public interface nature in which we defined access to this event, any number of class may obtain a reference to our event and bind to it. Therefore it functions very similarly to a multicast event.
Executing Events
Just like multicast delgates, we execute an event by calling the Broadcast() method and supplying the parameters we defined to the event.
Dynamic vs Multicast vs Events
When you don’t know if you need a dynamic delegate, you probably don’t need one and you should go for multicast delegates instead.
- Dynamic delegates are also the slowest of all delegate types. Don’t use them unless you must.
- If you don’t need more than one class to do the broadcast, use an event.
Use with Blueprint
If you have a delegate that you’d like to expose to blueprint, it must be dynamic. Regardless of whether your delegate is a single or multicast delegate, it must be dynamic to be exposed to blueprint.
Declaring a delegate as dynamic allows the blueprint nodes for “Bind” and “Unbind” delegate to be generated automatically based on the signature of the delegate type.
Note that the UPROPERTY specifiers of BlueprintAssignable and BlueprintCallable may only be used with dynamic multicast delegates.