Async nodes what are they and why to use them.#
Async blueprint nodes enables you to handle additional exec nodes that may fire at some different time. This allows us to do some background logic and even async actions that will fire event somewhere in future.
Note! Those nodes can be only used in Event graphs. Those nodes can’t be created in functions!
Use Case#
It is hard to understand without example so I have one that I needed for my project.
I have one class named Anomaly
it is Actor with collision box, that when player is colliding with it starts playing anomaly action and that action have some duration.
I would love to have easy way to:
- Spawn
Anomaly
that will not take much of performance. - Get Notification when
Anomaly
is spawned - Get Notification when
Anomaly
starts - Get Notification when
Anomaly
ends
To do so I need to:
- Create c++ class that derives from
UBlueprintAsyncActionBase
. - create static function creating our node.
- Create dynamic, multicast delegate without parameters, one for each exec node.
- Override
Activate
function.
Creating out node#
Create task class#
UCLASS()
class ANOMALIES_API ULoadAnomaly : public UBlueprintAsyncActionBase
{
GENERATED_BODY()
}
Create fields that will hold important references#
const UObject* WorldContextObject;
TSoftClassPtr<AAnomaly> AnomalyToLoad;
TObjectPtr<AAnomaly> LoadedAnomaly;
TSharedPtr<FStreamableHandle> LoadingObject;
Create static function used to create this node#
// .h
UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject"), Category = "Flow Control")
static ULoadAnomaly* LoadAnomalyAsync(const UObject* WorldContextObject, TSoftClassPtr<AAnomaly> AnomalyToLoad);
// .cpp
ULoadAnomaly* ULoadAnomaly::LoadAnomalyAsync(const UObject* WorldContextObject, TSoftClassPtr<AAnomaly> AnomalyToLoad)
{
ULoadAnomaly* BlueprintNode = NewObject<ULoadAnomaly>();
BlueprintNode->WorldContextObject = WorldContextObject;
BlueprintNode->AnomalyToLoad = AnomalyToLoad;
return BlueprintNode;
}
Note! that you can have how many arguments you want in this function (WorldContext
is must have) and this is place where you can provide any parameters for our task in blueprint.
Note! task nodes can take a long time to finish and sometimes you want to prolong their life time (they are usually bind to blueprint context, usually lifetime of blueprint it is created in). In case you want task node to life longer then it’s context you can pin it to game instance by adding this line to static function (before return).
BlueprintNode->RegisterWithGameInstance(WorldContextObject);
This will bind this node life time to GameInstance lifetime(whole play session time), if you get to the point that you want to destroy node, and release it’s resources, before it’s planed life time, you can call
SetReadyToDestroy()
it will mark it for destroy and remove in next garbage collection cycle.
Create dynamic, multicast delegate without parameters, one for each exec node#
We will simply create 3 delegates:
// .h
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FLoadAnomalyOutputPin);
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FStartAnomalyOutputPin);
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FEndAnomalyOutputPin);
and then inside of class we create those delegates fields:
// .h
class ANOMALIES_API ULoadAnomaly : public UBlueprintAsyncActionBase
{
GENERATED_BODY()
public:
//...
UPROPERTY(BlueprintAssignable)
FLoadAnomalyOutputPin AnomalyLoaded;
UPROPERTY(BlueprintAssignable)
FStartAnomalyOutputPin StartAnomaly;
UPROPERTY(BlueprintAssignable)
FEndAnomalyOutputPin EndAnomaly;
}
As we later need to bind UFUNCTIONs
we are creating simple delegate functions:
// .h
class ANOMALIES_API ULoadAnomaly : public UBlueprintAsyncActionBase
{
GENERATED_BODY()
public:
//...
UFUNCTION()
void OnAnomalyStarted();
UFUNCTION()
void OnAnomalyEnded();
}
void ULoadAnomaly::OnAnomalyStarted()
{
StartAnomaly.Broadcast();
}
void ULoadAnomaly::OnAnomalyEnded()
{
EndAnomaly.Broadcast();
}
Those delegates are used as output exec pins of our task node.
Override Activate
function#
// .h
virtual void Activate() override;
Firstly we will ask AssetManager
to load our soft class that we provide when we created this blueprint node.
// .cpp
if(UAssetManager* AssetManager = UAssetManager::GetIfInitialized())
{
...
LoadingObject = AssetManager->LoadAssetList({AnomalyToLoad.ToSoftObjectPath()}, Delegate);
}
Then we will create delegate that is called when AssetManager
finishes loading our class into memory.
I will bind it into Lambda that have life time connected to this task node.
Inside lambda we are just binding another delegates of Anomaly
actors to task functions that will simply call our exec nodes.
At the end of this lambda we call AnomalyLoaded.Broadcast()
that is our Exec node.
// .cpp
FStreamableDelegate Delegate{};
Delegate.BindWeakLambda(this, [this]()
{
if(LoadingObject && LoadingObject->HasLoadCompleted())
{
LoadedAnomaly = Cast<AAnomaly>(WorldContextObject->GetWorld()->SpawnActor(AnomalyToLoad.Get()));
if(LoadedAnomaly != nullptr)
{
LoadedAnomaly->OnAnomalyStarted.AddUniqueDynamic(this, &ULoadAnomaly::OnAnomalyStarted);
LoadedAnomaly->OnAnomalyEnded.AddUniqueDynamic(this, &ULoadAnomaly::OnAnomalyEnded);
}
}
AnomalyLoaded.Broadcast();
});
Finished Activate
function in .cpp
file looks like this:
// .cpp
void ULoadAnomaly::Activate()
{
if(UAssetManager* AssetManager = UAssetManager::GetIfInitialized())
{
FStreamableDelegate Delegate{};
Delegate.BindWeakLambda(this, [this]()
{
if(LoadingObject && LoadingObject->HasLoadCompleted())
{
LoadedAnomaly = Cast<AAnomaly>(WorldContextObject->GetWorld()->SpawnActor(AnomalyToLoad.Get()));
if(LoadedAnomaly != nullptr)
{
LoadedAnomaly->OnAnomalyStarted.AddUniqueDynamic(this, &ULoadAnomaly::OnAnomalyStarted);
LoadedAnomaly->OnAnomalyEnded.AddUniqueDynamic(this, &ULoadAnomaly::OnAnomalyEnded);
}
}
AnomalyLoaded.Broadcast();
});
LoadingObject = AssetManager->LoadAssetList({AnomalyToLoad.ToSoftObjectPath()}, Delegate);
}
}
Sources#
- loading async using asset manager - https://www.tomlooman.com/unreal-engine-asset-manager-async-loading/
- async task nodes - https://unrealcommunity.wiki/creating-asynchronous-blueprint-nodes-ctnmtj0q
End#
This is just short post, that I will need into future so I can share with you all.