Preface
If you’ve been working with Unreal Engine for a while, you’ve probably come across nodes that let you call a function now and get the result later — while also continuing execution once the function completes.

But when you try to create your own, you’ll quickly find that you can’t just “drill down” into their logic like with regular Blueprint functions.
So, how do you make your own?
That’s where UBlueprintAsyncActionBase
comes in. It’s the key to creating functions that behave like standalone asynchronous nodes in Blueprints.
Making our own asynchronous nodes
So, for example, let’s say we want to create two nodes — each of them should return a bool
indicating success, and also have an execution output that fires once the function completes.
To do this, we need to create a new C++ class that inherits from UBlueprintAsyncActionBase
.
For this example, we’ll call it UAsyncAction
.
.h file
#pragma once #include "CoreMinimal.h" #include "Kismet/BlueprintAsyncActionBase.h" #include "AsyncAction.generated.h" DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnAsyncDone, const bool, WasSuccessful); /** * */ UCLASS() class YOUR_MODULE_API UAsyncAction : public UBlueprintAsyncActionBase { GENERATED_BODY() public: // Our async execution output UPROPERTY(BlueprintAssignable) FOnAsyncDone OnAsyncDone; // First async node UFUNCTION(BlueprintCallable, BlueprintInternalUseOnly) static UAsyncAction* FirstAsyncFunction(UObject* Object); // Second "async" node UFUNCTION(BlueprintCallable, BlueprintInternalUseOnly) static UAsyncAction* SecondAsyncFunction(const TArray<int32> IntArray); protected: virtual void Activate() override; private: TWeakObjectPtr<> WeakObject; TArray<int32> Array; uint8 bFirstFunction : 1; };
Callable functions
We start by writing two static functions that return a pointer to our own class. But wait — we wanted to return a bool
, right?
Well, that’s the trick — these functions act as our constructors.
As you can see, we marked them with BlueprintInternalUseOnly
. That’s important, because the actual nodes that will be called in Blueprints are going to be generated by the engine. All we need to do is define the correct signatures and let Unreal handle the rest.
Return the result
Alright, but how do we actually return the result we want?
For that, we define a delegate: FOnAsyncDone OnAsyncDone
. Each async node we create will use this delegate as its output.
Thanks to this delegate, we’ll have an OnAsyncDone
execution output in Blueprints — and it will return a bool
value named WasSuccessful
.
Main function
We’ve defined our callable functions and given them the right output.
But since these are essentially constructors — where does the actual logic go?
That’s where UBlueprintAsyncActionBase
comes in. It has just one function that really matters to us: Activate()
. Think of it as the BeginPlay()
for your async node.
This is where the logic for all your custom async functions will live. Yes — all of them.
Cache
Since the implementation will live inside Activate()
, and we’re calling our logic through static functions, we need a way to store the arguments.
So, we’ll add corresponding fields to hold them.
Implementation
.cpp file
#include "AsyncAction.h" UAsyncAction* UAsyncAction::FirstAsyncFunction(UObject* Object) { const auto Action = NewObject<UAsyncAction>(); Action->WeakObject = Object; Action->bFirstFunction = true; return Action; } UAsyncAction* UAsyncAction::SecondAsyncFunction(const TArray<int32> IntArray) { const auto Action = NewObject<UAsyncAction>(); Action->Array = IntArray; Action->bFirstFunction = false; return Action; } void UAsyncAction::Activate() { Super::Activate(); if (bFirstFunction) { AsyncTask(ENamedThreads::AnyBackgroundThreadNormalTask, [&] { FPlatformProcess::Sleep(3.0f); OnAsyncDone.Broadcast(WeakObject.IsValid()); SetReadyToDestroy(); }); } else { FPlatformProcess::Sleep(3.0f); OnAsyncDone.Broadcast(Array.Num() > 0); SetReadyToDestroy(); } }
As you can see, in our static functions we simply create an instance of our class and pass the arguments into it. Since we have two different functions but a single shared implementation, we use a flag — bFirstFunction
— to distinguish between them.
Also, don’t forget to call SetReadyToDestroy()
once your logic is complete.
This is important, because each time you call the function, a new UObject
instance is created — and it needs to be cleaned up by the garbage collector after the logic finishes.
Result
So, what do our two functions actually do?
The first one should return whether the passed object pointer is valid — after a 3-second delay.
The second one should return whether the passed array is empty.


As you can see, the first function works — and it does so asynchronously.
Asynchrony


As you can see, the “async” output of the second node fired before the Function started
print.
That’s exactly why I put “async” in quotes — because the node doesn’t actually run asynchronously.
If your function doesn’t do anything async internally, it will behave just like a regular Blueprint function — meaning it’ll execute its output before moving to the next node.
And that’s it — now you know how to create asynchronous Blueprint nodes in Unreal Engine!
ссылка на оригинал статьи https://habr.com/ru/articles/900142/
Добавить комментарий