Asynchrony in blueprints into Unreal Engine

от автора

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.

Built-in asynchronous nodes

Built-in asynchronous nodes

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.

Calling first node

Calling first node
Result of first node

Result of first node

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

Asynchrony

Calling a second node

Calling a second node
Result of second node

Result of second node

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/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *