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
Anomalythat will not take much of performance. - Get Notification when
Anomalyis spawned - Get Notification when
Anomalystarts - Get Notification when
Anomalyends
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
Activatefunction.
Creating out node#
Create task class#
UCLASS()
class ANOMALIES_API ULoadAnomaly : public UBlueprintAsyncActionBase
{
GENERATED_BODY()
}
Create#
const UObject* WorldContextObject;
TSoftClassPtr<AAnomaly> AnomalyToLoad;
TObjectPtr<AAnomaly> LoadedAnomaly;
TSharedPtr<FStreamableHandle> LoadingObject;
Create2#
// .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.
Create3#
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.
