本記事では、実行ピン付きのブループリントノードを作る方法について説明します。
基本編の内容を理解していることを前提に説明するため、必要に応じて 基本編の記事 を確認してください。
- K2Nodeを継承してブループリントノードを自作する(1) 基本編
- K2Nodeを継承してブループリントノードを自作する(2) 実行ピン編 ★本記事
- K2Nodeを継承してブループリントノードを自作する(3) メニュー編
- K2Nodeを継承してブループリントノードを自作する(4) UI編
ソースコード一式
本記事で説明するソースコード一式は、GitHubで公開しています。
https://github.com/colory-games/techblog-sample-projects/tree/main/nutti/20220626
ソースコードを利用して動作確認する場合は、次の手順に従ってください。
- ディレクトリ
MyCompareInt
を、Unreal EngineのプロジェクトディレクトリにあるPlugins
に配置する .uproject
ファイルを右クリックし、Visual Studio向けのプロジェクトファイルを作成する- プロジェクトファイルを開き、プロジェクトをビルドする
.uproject
ファイルからUnreal Engineを起動し、プラグイン「MyCompareInt」を有効化する
作成するブループリントノードの仕様
本記事で作成するブループリントノード「MyCompareInt」の仕様を次に示します。
- 入力として2つの整数「A」「B」を受け取る
- 入力「A」「B」の大小関係によって実行する出力側の実行ピンを変更する
- 実行ピン「Greater」: A>Bのときに実行する
- 実行ピン「Equal」: A=Bのときに実行する
- 実行ピン「Less」: A<Bのときに実行する
この仕様は、ブループリントで提供されているマクロ「Compare Int」と同様です。
必要に応じてマクロ「Compare Int」の実装も確認してみてください。
モジュールの作成
基本編で説明した内容に従ってモジュールを作成します。
具体的な作成方法については、基本編の記事 を参考にしてください。
ブループリントノードの作成
ブループリントノードは、次の手順で作成します。
- クラス
UK2Node
の継承 - 基本情報の設定
- ピンの追加
- 内部ピンの追加
- 実行ピンの処理定義
なお、手順1と手順2については 基本編の記事 とほぼ同じであるため、ここでは手順3から説明します。
3. ピンの追加
作成するノードの仕様に従って、次のピンを追加します。
ピン名 | 入出力方向 | 意味 |
---|---|---|
入力 | ノードの実行を起動 | |
A | 入力 | 整数の入力 |
B | 入力 | 整数の入力 |
Greater | 出力 | A>Bのときにピンの先につながったノードを実行 |
Equal | 出力 | A=Bのときにピンの先につながったノードを実行 |
Less | 出力 | A<Bのときにピンの先につながったノードを実行 |
ピンの追加処理は、メンバ関数 AllocateDefaultPins
に実装します。
CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Exec, UEdGraphSchema_K2::PN_Execute);
CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Int, APinName);
CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Int, BPinName);
CreatePin(EGPD_Output, UEdGraphSchema_K2::PC_Exec, GreaterPinName);
CreatePin(EGPD_Output, UEdGraphSchema_K2::PC_Exec, EqualPinName);
CreatePin(EGPD_Output, UEdGraphSchema_K2::PC_Exec, LessPinName);
実行ピンを追加する場合、ピンの型を示す第2引数には UEdGraphSchema_K2::PC_Exec
を指定します。
ノード実行のトリガとなる入力側の実行ピンを追加する場合、第3引数には UEdGraphSchema_K2::PN_Execute
を指定するのが一般的です。
出力側にも3つの実行ピンを追加します。
ここでは、名前が「Greater」「Equal」「Less」の実行ピンを追加します。
4. 内部ピンの追加
今回作成するノード「MyCompareInt」は、入力に指定された整数「A」と「B」の値に応じて実行する出力側のピンを決める必要があります。
したがって、次の処理を実現する必要があります。
- 「A>B」のときに、実行ピン「Greater」の先につながるノードを実行する
- 「A=B」のときに、実行ピン「Equal」の先につながるノードを実行する
- 「A<B」のときに、実行ピン「Less」の先につながるノードを実行する
これをブループリントで実現すると、次のようになります。
しかしここでは、実装上の制約を考慮して次のような形で実現します。
- 入力「A」と「B」をライブラリ関数
UKismetMathLibrary::GreaterEqual_IntInt
に渡し、結果がFalse
の場合は実行ピン「Less」の先につながるノードを実行する - 1の結果が
True
の場合、入力「A」と「B」をライブラリ関数UKismetMathLibrary::LessEqual_IntInt
に渡し、結果がFalse
の場合は実行ピン「Greater」の先につながるノードを実行する - 2の結果が
True
の場合、実行ピン「Equal」の先につながるノードを実行する
ブループリントで実現すると次のようになります。
これをノードで実現するためには、次の2つを実現する方法を理解する必要があります。
- ライブラリ関数の呼び出しに必要な、内部ピンを追加する
- 追加したライブラリ関数の出力結果を利用し、実行する実行ピンを決定する
最初に、ライブラリ関数の呼び出しに必要な内部ピンを追加する処理について説明します。
他のピンを追加する場合と同様、メンバ関数 CreatePin
を利用して内部ピンを追加します。
UEdGraphPin* FunctionPin = CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Object, UKismetMathLibrary::StaticClass(), TEXT("GreaterEqual_IntInt"));
FunctionPin->bDefaultValueIsReadOnly = true;
FunctionPin->bNotConnectable = true;
FunctionPin->bHidden = true;
第2引数には、UEdGraphSchema_K2::PC_Object
を指定します。
第3引数にはブループリント関数ライブラリ、第4引数にはライブラリ関数の名前を指定します。
ライブラリ関数 CreatePin
の戻り値を利用し、読み取り専用の内部ピンであることを示すために各種設定を適用します。
ライブラリ関数を実行するピンの作成が完了しましたが、この状態では実行するライブラリ関数が設定されていません。
実行するライブラリ関数を内部ピンに設定する処理を次に示します。
UFunction* Function = FindUField(UKismetMathLibrary::StaticClass(), TEXT("GreaterEqual_IntInt"));
if (Function != nullptr && Function->HasAllFunctionFlags(FUNC_Static))
{
UBlueprint* Blueprint = GetBlueprint();
if (Blueprint != nullptr)
{
UClass* FunctionOwnerClass = Function->GetOuterUClass();
if (!Blueprint->SkeletonGeneratedClass->IsChildOf(FunctionOwnerClass))
{
FunctionPin->DefaultObject = FunctionOwnerClass->GetDefaultObject();
}
}
}
関数 FindUField
を呼び出してライブラリ関数 UKismetMathLibrary::GreaterEqual_IntInt
の UFunction
を取得します。
そして、作成した内部ピン FunctionPin
のメンバ変数 DefaultObject
に取得した UFunction
を設定します。
これによって、内部ピンはライブラリ関数 UKismetMathLibrary::GreaterEqual_IntInt
を実行できるようになります。
ここでは説明を省きますが、同様にしてライブラリ関数 UKismetMathLibrary::LessEqual_IntInt
を実行するための内部ピンを作成します。
5. 実行ピンの処理定義
ライブラリ関数の出力結果を利用し、実行する実行ピンを決定する処理を実装します。
基本編では計算グラフをメンバ関数 ExpandNode
を用いて定義しましたが、メンバ関数 ExpandNode
は特定の条件に基づいて実行先のピンを選択するという処理を実現できません。
これを実現するためには、クラス FNodeHandlingFunctor
を継承したクラスを定義する必要があります。
今回はクラス FKCHandler_MyCompareInt
として定義しています。
class FKCHandler_MyCompareInt : public FNodeHandlingFunctor
{
TMap BoolTermMap;
public:
FKCHandler_MyCompareInt(FKismetCompilerContext& InCompilerContext) : FNodeHandlingFunctor(InCompilerContext)
{
}
virtual void RegisterNets(FKismetFunctionContext& Context, UEdGraphNode* Node) override
{
// (省略)
}
virtual void Compile(FKismetFunctionContext& Context, UEdGraphNode* Node) override
{
// (省略)
}
};
ここからは、クラス FKCHandler_MyCompareInt
の定義について詳しく説明します。
変数の作成
メンバ関数 RegisterNets
には、実行したライブラリ関数の結果を格納する一時変数を作成する処理を定義します。
ここで作成した一時変数は、のちほど利用します。
virtual void RegisterNets(FKismetFunctionContext& Context, UEdGraphNode* Node) override
{
FNodeHandlingFunctor::RegisterNets(Context, Node);
{
// ライブラリ関数「UKismetMathLibrary::GreaterEqual_IntInt」の実行結果を格納する
// Boolean型の一時変数を作成する
FBPTerminal* BoolTerm = Context.CreateLocalTerminal();
BoolTerm->Type.PinCategory = UEdGraphSchema_K2::PC_Boolean;
BoolTerm->Source = Node;
BoolTerm->Name = Context.NetNameMap->MakeValidName(Node, TEXT("IsGreaterEqual"));
BoolTermMap.Add(TEXT("IsGreaterEqual"), BoolTerm);
}
}
メンバ関数 RegisterNets
はオーバーライドして定義したクラスであり、最初に親クラスの RegisterNets
を呼び出す必要があります。
親クラスの RegisterNets
を呼び出したあとに、引数 Context
のメンバ関数 CreateLocalTerminal
を呼び出してBoolean型の変数を作成します。
これを2回繰り返し、ライブラリ関数 UKismetMathLibrary::GreaterEqual_IntInt
と UKismetMathLibrary::LessEqual_IntInt
の実行結果を格納する変数 IsGreaterEqual
と IsLessEqual
を作成します。
作成した変数はブループリントのコンパイル処理で利用するため、メンバ変数 BoolTermMap
に登録しておきます。
ブループリントのコンパイル処理
メンバ関数 Compile
にはブループリントのコンパイル時に呼ばれる処理を定義し、ノードで実現する処理に対応したブループリントのステートメント FKismetCompiledStatement
を追加します。
ここで定義したメンバ関数 Compile
は、Compile Functions のフェーズで呼ばれます。
ブループリントのコンパイルに関する具体的な処理については、公式のドキュメント Blueprint Compiler Overview が参考になります。
ブループリントのステートメントタイプは、EKismetCompiledStatementType から確認できます。
今回利用するステートメントタイプは次の3つです。
ステートメントタイプ | 意味 |
---|---|
KCST_CallFunction |
関数(UFunction )を呼び出す |
KCST_GotoIfNot |
指定した条件が False の場合に特定の実行ピンに実行制御を移す |
KCST_UnconditionalGoto |
無条件で特定の実行ピンに実行制御を移す |
ステートメントは、引数 Context
のメンバ関数 AppendStatementForNode
を呼び出すことによって作成できます。
戻り値は構造体 FBlueprintCompiledStatement
です。
メンバ変数 Type
にステートメントタイプを指定し、LHS
と RHS
にはそれぞれステートメントに渡す変数を指定します。
変数の意味はステートメントタイプによって異なるため、具体的な意味については EKismetCompiledStatementType を参照してください。
ここからは、メンバ関数 Compile
に定義する処理について説明します。
コンパイルに必要な変数の取得
メンバ関数 Compile
は、最初にコンパイル時に必要な情報を取得します。
virtual void Compile(FKismetFunctionContext& Context, UEdGraphNode* Node) override
{
UK2Node_MyCompareInt* MyCompareIntNode = CastChecked(Node);
// 出力側の実行ピンを取得する
UEdGraphPin* GreaterPin = MyCompareIntNode->FindPin(GreaterPinName);
UEdGraphPin* EqualPin = MyCompareIntNode->FindPin(EqualPinName);
UEdGraphPin* LessPin = MyCompareIntNode->FindPin(LessPinName);
// 一時変数を取得する
FBPTerminal* IsGreaterEqualTerm = BoolTermMap.FindRef(TEXT("IsGreaterEqual"));
FBPTerminal* IsLessEqualTerm = BoolTermMap.FindRef(TEXT("IsLessEqual"));
// 入力ピン「A」の変数を取得する
UEdGraphPin* APin = MyCompareIntNode->FindPin(APinName);
UEdGraphPin* ANet = FEdGraphUtilities::GetNetFromPin(APin);
FBPTerminal* ATerm = Context.NetMap.FindRef(ANet);
// 入力ピン「B」の変数を取得する
UEdGraphPin* BPin = MyCompareIntNode->FindPin(BPinName);
UEdGraphPin* BNet = FEdGraphUtilities::GetNetFromPin(BPin);
FBPTerminal* BTerm = Context.NetMap.FindRef(BNet);
}
ステートメントを作成するにあたって、変数に相当するのが構造体 FBPTerminal
です。
実行ピンを表すクラス UEdGraphPin
ではないことに注意しましょう。
ここでは、次に示す変数を取得しています。
変数名 | 意味 |
---|---|
IsGreaterEqualTerm |
ライブラリ関数 UKismetMathLibrary::GreaterEqual_IntInt の結果を保存 |
IsLessEqualTerm |
ライブラリ関数 UKismetMathLibrary::LessEqual_IntInt の結果を保存 |
ATerm |
入力ピン「A」 |
BTerm |
入力ピン「B」 |
ブループリントのステートメント作成
ブループリントのステートメントを作成する処理について説明します。
先ほど紹介したように、指定した条件が True
の場合に特定の実行ピンに実行制御を移すというステートメントタイプがありません。
ここで、手順4で実装上の制約を考慮し実現方法を変更したことが役立ちます。
改めて実現する処理を示します。
- 入力「A」と「B」をライブラリ関数
UKismetMathLibrary::GreaterEqual_IntInt
に渡し、結果がFalse
の場合は実行ピン「Less」の先につながるノードを実行する - 1の結果が
True
の場合、入力「A」と「B」をライブラリ関数UKismetMathLibrary::LessEqual_IntInt
に渡し、結果がFalse
の場合は実行ピン「Greater」の先につながるノードを実行する - 2の結果が
True
の場合、実行ピン「Equal」の先につながるノードを実行する
参考のため、C++で実現する場合の疑似コードを示します。
IsGreaterEqualTerm = UKismetMathLibrary::GreaterEqual_IntInt(ATerm, BTerm);
if (!IsGreaterEqualTerm) {
goto LessPin;
}
IsLessEqualTerm = UKismetMathLibrary::LessEqual_IntInt(ATerm, BTerm);
if (!IsLessEqualTerm) {
goto GreaterPin;
}
goto EqualPin;
これをブループリントのステートメントに書き換えると次のようになります。
KCST_CallFunction IsGreaterEqualTerm [ATerm, BTerm]
KCST_GotoIfNot IsGreaterEqualTerm -> LessPin
KCST_CallFunction IsLessEqualTerm [ATerm, BTerm]
KCST_GotoIfNot IsLessEqualTerm -> GreaterPin
KCST_UnconditionalGoto -> EqualPin
この一連のステートメントを作成する処理をメンバ関数 Compile
に記述していきます。
virtual void Compile(FKismetFunctionContext& Context, UEdGraphNode* Node) override
{
// (省略)
{
// ライブラリ関数「UKismetMathLibrary::GreaterEqual_IntInt」のUFunctionを取得する
UEdGraphPin* FunctionPin = MyCompareIntNode->FindPin(TEXT("GreaterEqual_IntInt"));
FBPTerminal* FunctionContext = Context.NetMap.FindRef(FunctionPin);
UClass* FunctionClass = Cast(FunctionPin->PinType.PinSubCategoryObject.Get());
UFunction* FunctionPtr = FindUField(FunctionClass, FunctionPin->PinName);
// ライブラリ関数を呼び出すステートメントを作成する
FBlueprintCompiledStatement& CallFuncStatement = Context.AppendStatementForNode(MyCompareIntNode);
CallFuncStatement.Type = KCST_CallFunction;
CallFuncStatement.FunctionToCall = FunctionPtr;
CallFuncStatement.FunctionContext = FunctionContext;
CallFuncStatement.bIsParentContext = false;
CallFuncStatement.LHS = IsGreaterEqualTerm;
CallFuncStatement.RHS.Add(ATerm);
CallFuncStatement.RHS.Add(BTerm);
// ライブラリ関数の呼び出し結果ががFalseのときに、実行ピン「LessPin」に実行制御が移るステートメントを作成する
FBlueprintCompiledStatement& GotoStatement = Context.AppendStatementForNode(MyCompareIntNode);
GotoStatement.Type = KCST_GotoIfNot;
GotoStatement.LHS = IsGreaterEqualTerm;
Context.GotoFixupRequestMap.Add(&GotoStatement, LessPin);
}
{
// (省略)
// ライブラリ関数の呼び出し結果がFalseのときに、実行ピン「GreaterPin」に実行制御が移るステートメントを作成する
FBlueprintCompiledStatement& GotoStatement = Context.AppendStatementForNode(MyCompareIntNode);
GotoStatement.Type = KCST_GotoIfNot;
GotoStatement.LHS = IsLessEqualTerm;
Context.GotoFixupRequestMap.Add(&GotoStatement, GreaterPin);
}
// いずれの条件も満たさない場合は、無条件で実行ピン「EqualPin」に実行制御が移るステートメントを作成する
GenerateSimpleThenGoto(Context, *MyCompareIntNode, EqualPin);
}
最初に、ブループリントのステートメント作成時に使用するライブラリ関数 UKismetMathLibrary::GreaterEqual_IntInt
の UFunction
を取得します。
これは、手順4で作成した内部ピンに設定した情報から取得できます。
次に Context
のメンバ関数 AppendStatementForNode
を呼び出し、ライブラリ関数を呼び出すステートメント KCST_CallFunction
を作成します。
構造体 FBlueprintCompiledStatement
のメンバ変数 Type
には、ライブラリ関数を呼び出すステートメントであることを明示するために KCST_CallFunction
を設定します。
メンバ変数 FunctionToCall
には、このステートメントで呼び出すライブラリ関数の UFunction
を設定します。
ここでは、事前に取得しておいた UFunction
を指定すればよいです。
メンバ変数 LHS
には、ライブラリ関数の戻り値を保存する変数 IsGreaterEqualTerm
を設定します。
メンバ変数 RHS
には、ライブラリ関数の引数に渡す値を引数順に設定します。
今回の場合は、ATerm
と BTerm
を引数として設定します。
続いて作成するのは、指定した条件が False
の場合に特定の実行ピンに実行制御を移すステートメント KCST_GotoIfNot
です。
メンバ変数 LHS
に IsGreaterEqualTerm
を指定し、IsGreaterEqualTerm
が False
のとき(「A<B」のとき)に実行制御が移るようにします。
実行制御を移す先は実行ピン LessPin
であるため、Context.GotoFixupRequestMap.Add
を呼び出して実行制御を移す先を登録します。
同様にして、実行ピン GreaterPin
に実行制御を移す処理を作成します。
最後に関数 GenerateSimpleThenGoto
を呼び出し、いずれの条件にも当てはまらなかったとき(「A=B」のとき)に実行ピン EqualPin
へ実行制御を移すステートメントを作成します。
関数 GenerateSimpleThenGoto
は内部でステートメント KCST_UnconditionalGoto
を作成し、Context.GotoFixupRequestMap.Add
を呼び出して実行制御を移す先を登録する便利関数です。
実行ピンの処理を登録
実行ピンの処理を定義したクラス FKCHandler_MyCompareInt
は、ノードのクラス UK2Node_MyCompareInt
と関連付ける必要があります。
メンバ関数 CreateNodeHandler
をオーバーライドし、クラス FKCHandler_MyCompareInt
を戻り値として返すように処理を定義することで関連づけが可能です。
FNodeHandlingFunctor* UK2Node_MyCompareInt::CreateNodeHandler(class FKismetCompilerContext& CompilerContext) const
{
return new FKCHandler_MyCompareInt(CompilerContext);
}
作成したノードの利用
作成したノードを利用してみましょう。
Actorを作成してブループリントエディタを開き、右クリックでメニューを表示して検索ボックスに「mycompareint」を入力します。
カテゴリ「Flow Control」に追加されているノード「MyComponentInt」を選択して配置します。
作成したActorをレベルに配置して実行すると、ノードの入力値によって実行されるピンが変化することが確認できます。
ここでは実行ピン「Greater」を実行したときのものを示していますが、入力ピン「A」「B」の値をいろいろ変えてみてその結果を確認してみてください。