Delegates

Delegates are special events which insted of being called like a function, you bind function to that event. When the event is called, all the binded functions are run.  Major benefit from it is that you can bind any function regardless where they are  and you can bind more then one function and all of them will be called on one call. There are different types of delegates and we will go through all of them here.


Delegate types

Delegates can be Non-Dynamic, Dynamic, Non-Multicasting or Multicasting. You can mix and match any of these as you like. If you want to pass parameters along with the delegates, you need to specify the parameters in the declaration with ’_Param’. Delegates are declared before the UCLASS() macro specifier. Examples of delegates.

DECLARE_MULTICAST_DELEGATE
DECLARE_DYNAMIC_DELEGATE_OneParam
DECLARE_DYNAMIC_MULTICAST_DELEGATE
DECLARE_DYNAMIC_DELEGATE_TwoParams
DECLARE_DYNAMIC_MULTICAST_DELEGATE

We start by creating a C++ class based on Actor. We bind our delegates to that actor later on. Just for the sake of it, we declare our delegates inside our GameMode.


Basic delegate

Let’s start with basic delegate. We go to GameMode C++ class. Inside header file we create our delegate. This delegate takes no params and it is not dynamic and not multicasting. This means that this delegate can be binded to one function only. Other bindings will override the previous one. By not being dynamic, this delegate cannot be used inside blueprints.

// Delegate without parameters. Delegate name if FMyDelegateWithNoParams
DECLARE_DELEGATE(FMyDelegateWithNoParams);

In Public section we create object from the delegate struct.

FMyDelegateWithNoParams DelegateWithNoParameters;

We create a function called BroadCastDelegate() and make it BlueprintCallable. Just so we can call this function in BP.

UFUNCTION(BlueprintCallable)
void BroadCastDelegate();

We declare the function and inside the function we execute our delegate If there is something bound to it. There are multiple ways to execute the delegate.

Execute()
Try to execute no matter what. Might cause crash if there is Nullptr!
ExecuteIfBound()
Only execute if there is something bound to this delegate. Safe!
IsBound()
Check if this delegate is bound to something
void APortFolioPrjGameMode::BroadCastDelegate()
{
DelegateWithNoParameters.ExecuteIfBound();
}

Now go into level blueprint and create logic. Logic is simple. When player press enter, we get our gamemode and run the function we created. Note that this is not the delegate itself. It is just a function that will execute/broadcast the delegate.

a1

Now, go to the class we created in beginning of this tutorial and in header file create function.

UFUNCTION()
void CatchDelegateWithNoParams();

In function declaration

void AC_SecondActor::CatchDelegateWithNoParams()
{
UE_LOG(LogTemp, Warning, TEXT("I am basic Non-Dynamic, Non-Multicasting Delegate With no parameters"))
}

Finally in BeginPlay we bind our delegate to function we created. There are multiple ways to bind the delegate

Bind()
Binds to an existing delegate object.
BindStatic()
Binds a Static Function
BindRaw()
Use these if you have something other than UObject.
BindSP()
Use this with Shared Pointers. For example you can create struct as TSharedPtr.
BindUObject()
Mostly Used. Use this with any normal UObjects.
UnBind()
Unbinds this delegate.
// We have normal AActor class with function. AActor is inherited from UObject. So we use BindUObject.
// Get our game mode
auto Gmode = GetWorld()->GetAuthGameMode<APortFolioPrjGameMode>();
Gmode->DelegateWithNoParameters.BindUObject(this, &AC_SecondActor::CatchDelegateWithNoParams);

That’s it! Now when we go into our game and press Enter, the GameMode will execute the delegate and our listener will call the function immediadely.


Binding Shared Pointer

Let’s try some other bindings. We go back to header file and create a struct. Structs are defined just above the UCLASS() macro.

USTRUCT()
struct FMyOwnStruct
{
GENERATED_BODY()
//Set
public:
void printSmt()
{
UE_LOG(LogTemp, Warning, TEXT("I'm printing inside struct."));
}	
};

Create object of our struct. Let’s do this in public. Notice that we create a pointer that is type of TSharedRef and use that to get reference to our struct object.

TSharedRef<FMyOwnStruct> MyShareableStruct = MakeShareable(new FMyOwnStruct());

Now. In BeginPlay we can bind. We use the struct object as param and then use the printSmt function that is inside the struct. Notice that we use the BindSP because we are using shared pointer.

Gmode->DelegateWithNoParameters.BindSP(MyShareableStruct, &FMyOwnStruct::printSmt);

Thats it! You successfully binded a delegate into a struct!


Binding Static Function

Let’s try static functions. Create a static function in header file.

UFUNCTION()
static void DoSomething();

Declare our function.

void AC_SecondActor::DoSomething()
{
UE_LOG(LogTemp, Warning, TEXT("Static Function run through delegate"));
}

Now in BeginPlay we bind. Notice static function ain’t taking a object as parameter because it is static!

Gmode->DelegateWithNoParameters.BindStatic(&AC_SecondActor::DoSomething);

That is how you bind static function!


Binding Lambda

Next, we will bind Lambda! In this case, we don’t need to create anything. Just bind the lambda.

Gmode->DelegateWithNoParameters.BindLambda([] {
UE_LOG(LogTemp, Warning, TEXT("I am Lambda"))
});

Return value

Delegates can also return a value. If we want delegate to return value we need to add it in declaration. We add _RetVal to do that. First param is the value type. we go basck to our GameMode and declare another delegate.

DECLARE_DELEGATE_RetVal(int32, FMyEventDelegate);

We create the object of our delegate.

FMyEventDelegate ReturnTypeDelegate;

Inside BeginPlay we use return value. After that we just print our value.

int32 value = ReturnTypeDelegate.Execute();
UE_LOG(LogTemp, Warning, TEXT("Delegate returned value %i"), value);

Inside our Class where we want to bind the delegate we create a function and declare it. Notice that it is not void this time.

UFUNCTION()
int32 DelegateWithReturnValue();

In declaration we return value.

int32 AC_SecondActor::DelegateWithReturnValue()
{
return 4;
}

We bind is normally.

Gmode->ReturnTypeDelegate.BindUObject(this, &AC_SecondActor::DelegateWithReturnValue);

And thats it! You have successfully created a delegate that will return value to Executing class!


Now, that we have seen what the basic delegate does. Let’s go to DYNAMIC_MULTICAST delegates. These are delegates that can take multiple functions and broadcast to all functions. Meaning every function will be run simultaneously. Of course, functions doesn’t have to be inside one actor, but many! That is the cool thing about DYNAMIC_MULTICAST_DELEGATE.  One class broadcast and multiple class will listen the broadcast and run their own functions.

 

Notice! We are going to Skip the DECLARE_MULTICAST_DELEGATE, because it is pretty much same as the original delecate. Only difference is that you don’t use .Bind, but instead you Add. This is because Multicast can take many functions and it will broadcast to all of them. You can use these.

 

Add()
Adds a function delegate to this multi-cast delegate’s invocation list.
AddStatic()
Adds a raw C++ pointer global function delegate.
AddRaw()
Adds a raw C++ pointer delegate. Raw pointer does not use any sort of reference, so may be unsafe to call if the object was deleted out from underneath your delegate. Be careful when calling Execute()!
AddSP()
Adds a shared pointer-based (fast, not thread-safe) member function delegate. Shared pointer delegates keep a weak reference to your object.
AddUObject()
Adds a UObject-based member function delegate. UObject delegates keep a weak reference to your object.
Remove()
Removes a function from this multi-cast delegate’s invocation list (performance is O(N)). Note that the order of the delegates may not be preserved!
RemoveAll()
Removes all functions from this multi-cast delegate’s invocation list that are bound to the specified UserObject. Note that the order of the delegates may not be preserved!

As you can see. Pretty much same! With Multicast, you dont use Execute() or ExecuteIfBound(). Instead you use Broadcast().

DelegateWithNoParameters.Broadcast();

Dynamic  Multicast Delegate

Okay! Time to go back to DYNAMIC_MULTICAST_DELEGATE. Dynamic delegates are little slower than normal delegates, but they are much more simplier to use and can be used inside blueprints. Let’s go back to our GameState class. We can declare the delegate above the UCLASS(). This time, lets add one parameter with the delegate. Note that you can add params to any delegate type! First comes the delegate name, then the parameter type and finally the parameter name. Name is useful when using delegates inside blueprint!

DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FMyTestDelegate, FString, MyString);

Go into public section and create object. Note that we add BlueprintAssignable. We can now assign this delegate in blueprints if we like.

UPROPERTY(BlueprintAssignable)
FMyDynamicMulticastDelegate MyDynamicDelegate;

We  use the same function we created earlier (BroadCastDelegate). We add broadcast function there. Remember that we had a FString parameter. We need to add it as parameter. Broadcast is always safe to call. It is the only way to call MULTICAST delegates.

void APortFolioPrjGameMode::BroadCastDelegate()
{
MyDynamicDelegate.Broadcast("Delegate Broadcasted");
}

Now, let’s go back to our C++ class where we bind this delegate. Create a function in header. We do this in private section this time. Note that we need FString parameter.

UFUNCTION()
void CatchDelegate(FString StringInput);

In .CPP we declare the function.

void AC_SecondActor::CatchDelegate(FString StringInput)
{
UE_LOG(LogTemp, Warning, TEXT("%s"), *StringInput)
}

Finally. In BeginPlay() we can bind the delegate. We don’t need to figure out how to add it anymore. We just use .AddDynamic and editor will handle the conversion. Simple!

Gmode->MyDynamicDelegate.AddDynamic(this, &AC_SecondActor::CatchDelegate);

That’s how you do the full DYNAMIC_MULTICAST_DELEGATE that works in C++ and in blueprints. Remember. If you really want to optimize your game and you don’t need to use your delegates in blueprints. Then don’t use dynamic. Normally, using dynamic delegates is perfectly fine.


Events

Final thing to know about delegates are events. Events are used exactly the same way as Delegates, but only the class that declares them can use the Execute/IsBound. Also, events cannot return any value. Inside our GameMode let’s create event with two parameters.

DECLARE_EVENT_TwoParams(APortFolioPrjGameMode, FChangedEvent, FString, int32);

We create object of it in public.

FChangedEvent ChangedEvent;

In out Broadcast delegate function we execute. Notice the parameters!

ChangedEvent.Broadcast("String", 3);

Inside the class where we do binding, we create function.

UFUNCTION()
void CatchEvent(FString MyString, int32 MyInt);

Declare the function.

void AC_SecondActor::CatchEvent(FString MyString, int32 MyInt)
{
UE_LOG(LogTemp, Warning, TEXT("Event delegate with two params"))
}

Finally we do binding in BeginPlay.

Gmode->ChangedEvent.AddUObject(this, &AC_SecondActor::CatchEvent);

Congrats! Now you should have rather good idea what the delegates are and how you can use them! Remember that binding doesn’t have to happen inside BeginPlay(), it can happen anywhere. We just did it in BeginPlay() for the practicing purposes.

 

Vastaa

Sähköpostiosoitettasi ei julkaista. Pakolliset kentät on merkitty *