はじめに
Latent ノードというものをご存じでしょうか?
そのノードが実行されたら、そのノードの処理が完了するまで待ってから次の処理を実行するというものをシンプルで視覚的にも非常に分かりやすく作れるノードです!
代表的なものだと Delay() や AsyncLoadAsset() などがあります。

マクロを使えばそれっぽいものが作れますが、マクロはその中の処理をインライン展開しているだけです。
マクロライブラリを用いたとしても、マクロライブラリの親クラスによってそのマクロを使えるクラスが限られてしまったり、マクロの中で使える Latent ノードの選択肢が減ってしまったりします。

例えばこの画像のように Google Cloud の API を叩くのに必要なアクセストークンを取得したり、Slack にファイルを送信したりといった、HTTP リクエストで外部の API を叩くような処理を Latent ノードに綺麗にまとめたい!といった場合に自分は良く C++ で Latent ノードを作っています。

現状、Latent ノードを作るには C++ が必要であり、手順?というか作法?のようなものがあるので忘れないうちにまとめておきます。
手順
では早速、簡単な Latent ノードを作っていきます。
スクリプトの作成
手順1
エディタ上部の「Tools > New C++ Class…」を押して C++ クラスを作成します。

手順2
All Classes のタブに切り替えて、BlueprintAsyncActionBase を選択し、下の「Next>」を押します。

ブループリントで呼び出せる Latent ノードを作成するときは基本的にこのクラスを継承したクラスを自作することになります。
手順3
スクリプトのファイル名とパスを決めて、右下の「Create Class」を押します。

コーディング
ここからは実際にコードを書いていきます。
手順4
作成されたヘッダーファイルを開いて、「DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam({デリゲート名}, {戻り値の型名}, {戻り値名});」と「UPROPERTY(BlueprintAssignable) {デリゲート名} {変数名};」と書いてデリゲートを宣言します。
#pragma once
#include "CoreMinimal.h"
#include "Kismet/BlueprintAsyncActionBase.h"
#include "AsyncActionDoSomething.generated.h"
//追加
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnCompletedSomething, FString&, OutputValue);
UCLASS()
class SAMPLE_API UAsyncActionDoSomething : public UBlueprintAsyncActionBase
{
GENERATED_BODY()
public:
//追加
UPROPERTY(BlueprintAssignable)
FOnCompletedSomething Completed;
};
手順5
「UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = “true”, WorldContext = “WorldContextObject”)) static {作成したクラス名}* {関数名}(UObject* WorldContextObject, {引数の型名} {引数名});」といった感じで静的関数を宣言して、引数を保持する用のメンバ変数も宣言しておきます。
#pragma once
#include "CoreMinimal.h"
#include "Kismet/BlueprintAsyncActionBase.h"
#include "AsyncActionDoSomething.generated.h"
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnCompletedSomething, const FString&, OutputValue);
UCLASS()
class SAMPLE_API UAsyncActionDoSomething : public UBlueprintAsyncActionBase
{
GENERATED_BODY()
public:
UPROPERTY(BlueprintAssignable)
FOnCompletedSomething Completed;
//追加
UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", WorldContext = "WorldContextObject"))
static UAsyncActionDoSomething* AsyncDoSomething(UObject* WorldContextObject, const FString& InputValue);
private:
//受け取った引数を保持しておく用のメンバ変数
FString InputValue;
};
手順6
先ほど宣言した関数の定義をソースファイルに書いていきます。
処理の内容は以下の通りです。
- 自身のクラスをインスタンス化する
- 受け取った引数をそのインスタンスに渡す
- そのインスタンスをゲームインスタンスに登録して参照関係を作り、ガベージコレクションによって削除されないようにする
- そのインスタンスを返す
具体的なコードは以下の通りです。
UAsyncActionDoSomething* UAsyncActionDoSomething::AsyncDoSomething(UObject* WorldContextObject, const FString& InputValue)
{
//自身のクラスをインスタンス化する
UAsyncActionDoSomething* Action = NewObject<UAsyncActionDoSomething>();
//受け取った引数をそのインスタンスに渡す
Action->InputValue = InputValue;
//そのインスタンスをゲームインスタンスに登録して参照関係を作り、
//ガベージコレクションによって削除されないようにする
Action->RegisterWithGameInstance(WorldContextObject);
//そのインスタンスを返す
return Action;
}
手順7
OnCompleted() のような名前の関数をヘッダーファイルで宣言して、その関数の定義をソースファイルに書きます。
このとき、必要に応じて引数も設定します。
関数の内容はデリゲートの呼び出しと、自身をガベージコレクションの回収対象に戻すといったものです。
void UAsyncActionDoSomething::OnCompleted(const FString& OutputValue)
{
//デリゲートを呼び出す
Completed.Broadcast(OutputValue);
//ガベージコレクションの対象に戻す
SetReadyToDestroy();
}
手順8
ヘッダーファイルで「public: void Activate() override;」と書いて、ソースファイルで親クラスの Activate() を上書きします。
ここに非同期で実行したい処理を書きます。
この関数の最後に OnCompleted() を呼びだす感じです。
void UAsyncActionDoSomething::Activate()
{
//ラムダ式などを用いて非同期で OnCompleted() を呼び出す処理
}
これでコンパイルしてブループリントを開き、そのノードを検索すると自作の Latent ノードが出てくるはずです。

最後に
参考記事
- [UE4]簡単なLatentノードの作り方
- 【UE4 C++】デリゲートの使い方まとめ
- [UE4] meta情報を活用しエディタでの動作を制御しよう!
- WorldContextObject についての理解