I think it’s an awkward solution because it limits options in C++ (you can only bind UFUNCTION
s using MyDelegate.AddDynamic(this, &ThisClass::MyFunc)
). You can see different examples in the engine of ways to deal with this:
AddDynamic
and deal with its limitations. This is what a lot of AActor
delegates do. This is a perfectly fine solution in cases where you don’t often interact with the delegate in C++.UserWidget.h
for example, in the OnVisibilityChanged
and OnNativeVisibilityChanged
delegates.Async actions are UObject
-derived types that typically share the lifetime of the game instance, although it is up to each individual action to determine that. Some destroy themselves after firing once, while some live forever. They are supposed to encapsulate some asynchronous behavior, like downloading data from some server, listening for property changes, or whatever else you may think of. In cases like downloading data, the use of an object that can contain state may be warranted. But in cases where you just want to register a callback, async actions are overkill. They are very verbose. You have to create a new class for every single behavior, and every time you want to use it, you need to spawn a new instance of it. This is inefficient not only for the computer, but also for the developer.
In order to avoid async actions, I like to use non-dynamic delegates, then write a UFUNCTION
that takes a dynamic unicast delegate as parameter to bind to the multicast delegate. This way you get the best of both worlds: you can interact with the delegate in C++ however you want, and at the same time have it accessible in blueprint!
NeatDelegateFunction
metadataIn many cases, that solution is enough. But I do enjoy how clean async actions look in the graph, which is why I created the NeatDelegateFunction
meta tag. Adding that tag will create the appropriate event inline on the node itself, which makes it slightly more convenient to use.
The NeatConstructor
tag is useful in cases where you want to construct objects with a bit more flexibility. In short, it’s like a generic SpawnActor
node. Let’s start out by examining how that works.
SpawnActor
nodeMany nodes in the blueprint graph are just wrappers for other nodes. The SpawnActor
node is one such node. When these nodes get compiled they are expanded into an underlying chain of nodes used to generate the actual blueprint bytecode. If you are familiar with blueprint macros, they can be seen as blueprint macros on steroids, where the programmer has full control over what happens when expanded. The SpawnActor
node has three major stages when it gets expanded:
SpawnActor
function that spawns the actor. Note that it has not called BeginPlay
yet at this point, but the object itself has been constructed and can be referred to.ExposeOnSpawn
properties in the actor’s class, and spawn SetVariable
nodes for each of them.FinishSpawning
function on the actor, which will call BeginPlay
and ensure the actor has completed the spawn sequence.Looking at those three stages, we can derive what we need the generic node to do:
ExposeOnSpawn
properties in the actor’s class, and spawn SetVariable
nodes for each of them.TSubclassOf<AActor>
, default it to FinishSpawning
.Conceptually, it is that simple. But let’s think a bit at how a user might want to work with a concept like this:
This is essentially the way you interact with the NeatConstructor
node.