アンリアる! C++入門編 ~対話形式で学ぶUnreal Engine~
BOOTHでUnreal Engine C++入門書を販売していますUnreal Engine上でC++を使って開発するために必要となる基礎知識を一冊の本にまとめた本です。 対話形式によるわかりやすい説明を目指しました。無料の試し読みも可能ですので、ぜひ読んでみてください!
[UE5] K2Nodeを継承してブループリントノードを自作する(1) 基本編

Unreal Engineで独自のブループリントノードを作る場合、すぐに思いつくものとしてはライブラリ関数を作るというものです。
しかしライブラリ関数で作れるブループリントノードには限界があり、たとえば「Switch on Int」のような実行制御を伴う複雑なノードを作ることはできません。

本記事では、クラスUK2Nodeを継承してブループリントノードを作る方法について、具体的にブループリントノードを作りながら説明します。
ブループリントノードの作り方自体は深い内容であるため、トピックに分けて説明します。

ソースコード一式

本記事で説明するソースコード一式は、GitHubで公開しています。

https://github.com/colory-games/techblog-sample-projects/tree/main/nutti/20220622

ソースコードを利用して動作確認する場合は、次の手順に従ってください。

  1. ディレクトリ MyDivide を、Unreal Engineのプロジェクトディレクトリにある Plugins に配置する
  2. .uproject ファイルを右クリックし、Visual Studio向けのプロジェクトファイルを作成する
  3. プロジェクトファイルを開き、プロジェクトをビルドする
  4. .uproject ファイルからUnreal Engineを起動し、プラグイン「MyDivide」を有効化する

作成するブループリントノードの仕様

本記事で作成するブループリントノード「MyDivide」の仕様を次に示します。

  • 入力として2つの整数「A」「B」を受け取り、A÷Bの商「Quotient」と余り「Remainder」を出力する
  • 実行ピンなし(Pure関数)

今回作成するノードは、ブループリントエディタを使って作成可能なノードです。
しかしここではあえて、クラスUK2Nodeを利用して作成することにします。

モジュールの作成

ここでは、ブループリントノード「MyDivide」をプラグイン形式で提供することを考えたソースコード構成とします。

最初に Build.cs ファイルの内容を示します。
ブループリントノードを作成するために必要なモジュールが設定されています。

using UnrealBuildTool;

public class MyDivide : ModuleRules
{
    public MyDivide(ReadOnlyTargetRules Target) : base(Target)
    {
        PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs;

        PublicDependencyModuleNames.AddRange(new string[]{
            "Core",
            "CoreUObject",
            "Engine"
        });

        PrivateDependencyModuleNames.AddRange(new string[]{
            "BlueprintGraph",
            "GraphEditor",
            "KismetCompiler",
            "SlateCore",
            "UnrealEd"
        });
    }
}

また、.uplugin ファイルの Modules に次のエントリを追加します。

{
    "Name": "MyDivide",
    "Type": "UncookedOnly",
    "LoadingPhase": "PreLoadingScreen"
}

ブループリントノードの作成

ブループリントノードは、次の手順で作成します。

  1. クラス UK2Node の継承
  2. 基本情報の設定
  3. ピンの追加
  4. Pure関数化
  5. データピンの処理を定義

クラスUK2Nodeの継承

ブループリントノードは、クラス UK2Node を継承して定義します。
ヘッダファイル K2Node_MyDivide.h を作成し、クラス UK2Node_MyDivide を定義します。

UCLASS(meta = (Keywords = "MyDivide Divide"))
class UK2Node_MyDivide : public UK2Node
{
    GENERATED_BODY()

    // クラスUEdGraphNodeからオーバーライドしたメンバ関数
    virtual void AllocateDefaultPins() override;
    virtual FText GetTooltipText() const override;
    virtual FLinearColor GetNodeTitleColor() const override;
    virtual FText GetNodeTitle(ENodeTitleType::Type TitleType) const override;
    virtual FSlateIcon GetIconAndTint(FLinearColor& OutColor) const override;

    // クラスUK2Nodeからオーバーライドしたメンバ関数
    virtual bool IsNodePure() const override;
    virtual void GetMenuActions(FBlueprintActionDatabaseRegistrar& ActionRegistrar) const override;
    virtual FText GetMenuCategory() const override;
    void ExpandNode(FKismetCompilerContext& CompilerContext, UEdGraph* SourceGraph) override;
};

クラス UK2Node_MyDivide には、次に示すメンバ関数が宣言されています。

メンバ関数 役割
AllocateDefaultPins デフォルトのピンを割り当てる
GetTooltipText Tooltipのテキストを設定する
GetNodeTitleColor タイトル部の色を設定する
GetNodeTitle タイトル部に表示するテキストを設定する
GetIconAndTint タイトル部に表示するアイコンを設定する
IsNodePure true を返した場合は、Pure関数化する
GetMenuActions ブループリントエディタからノードを利用可能にする
GetMenuCategory ノード検索メニューのカテゴリを設定する
ExpandNode コンパイル時にノードを追加/削除する

基本情報の設定

宣言したメンバ関数の定義を作成します。 ソースファイル K2Node_MyDivide.cpp を作成し、各メンバ関数を定義します。 処理の詳細は、各関数内のコメントを参考にしてください。

FText UK2Node_MyDivide::GetTooltipText() const
{
    // Tooltipで表示するテキストを返す
    return LOCTEXT("MyDivide_Tooltip", "MyDivide\nGet quotient and remainder from dividing two integer");
}

FLinearColor UK2Node_MyDivide::GetNodeTitleColor() const
{
    // 整数型(Integer)のピンと同じ色をタイトル部の色とする
    return GetDefault()->IntPinTypeColor;
}

FText UK2Node_MyDivide::GetNodeTitle(ENodeTitleType::Type TitleType) const
{
    // タイトル部に表示するテキストを返す
    return LOCTEXT("MyDivide", "MyDivide");
}

FSlateIcon UK2Node_MyDivide::GetIconAndTint(FLinearColor& OutColor) const
{
    // 関数アイコンをタイトル部に表示するアイコンとする
    static FSlateIcon Icon("EditorStyle", "Kismet.AllClasses.FunctionIcon");
    return Icon;
}

void UK2Node_MyDivide::GetMenuActions(FBlueprintActionDatabaseRegistrar& ActionRegistrar) const
{
    UClass* ActionKey = GetClass();
    if (ActionRegistrar.IsOpenForRegistration(ActionKey))
    {
        UBlueprintNodeSpawner* NodeSpawner = UBlueprintNodeSpawner::Create(GetClass());
        check(NodeSpawner != nullptr);

        ActionRegistrar.AddBlueprintAction(ActionKey, NodeSpawner);
    }
}

FText UK2Node_MyDivide::GetMenuCategory() const
{
    // カテゴリ「Math」に登録する
    return FEditorCategoryUtils::GetCommonCategory(FCommonEditorCategory::Math);
}

メンバ関数 GetMenuActions には、ブループリントエディタ上で右クリックしたメニューにノード「MyDivide」を追加する処理です。
メンバ関数 GetMenuActions の実装を忘れると、メニューにノード「MyDivide」が表示されず配置できないため注意が必要です。
なおブループリントノードを作る場合は同じ処理で対応可能なため、そのまま実装を流用して問題ありません。
また、メンバ関数 GetMenuCategory を呼び出すことで、カテゴリ「Math」にノード「MyDivide」が登録されます。

他のメンバ関数の役割については、ブループリントエディタに完成版のノード「MyDivide」を配置したときのUIと関連付けて理解するとよいでしょう。

基本情報の設定

ピンの追加

作成するノードの仕様に従って、次のピンを追加します。

ピン名 入出力方向 意味
A 入力 整数の入力
B 入力 整数の入力
Quotient 出力 A÷Bの商
Remainder 出力 A÷Bの余り

今回作成するノードでは、上記のピンが追加されている必要があります。
メンバ関数 AllocateDefaultPins は、ノード配置時にピンを追加する処理を定義するメンバ関数です。

namespace
{
static const FName APinName(TEXT("A"));
static const FName BPinName(TEXT("B"));
static const FName QuotientPinName(TEXT("Quotient"));
static const FName RemainderPinName(TEXT("Remainder"));
}

void UK2Node_MyDivide::AllocateDefaultPins()
{
    CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Int, APinName);
    CreatePin(EGPD_Input, UEdGraphSchema_K2::PC_Int, BPinName);
    CreatePin(EGPD_Output, UEdGraphSchema_K2::PC_Int, QuotientPinName);
    CreatePin(EGPD_Output, UEdGraphSchema_K2::PC_Int, RemainderPinName);
}

ピンは、メンバ関数 CreatePin を呼び出して追加できます。
第1引数にピンの入出力方向を指定します。
入力の場合は EGPD_Input、出力の場合は EGPD_Output を指定します。

第2引数にはピンの型を指定します。
すべてのピンは整数型(Integer)であるため、UEdGraphSchema_K2::PC_Int を指定します。

第3引数にはピン名を指定します。

Pure関数化

今回作成するノードは実行ピンを持たないPure関数化されたノードであるため、メンバ関数 IsNodePuretrue を返すようにします。

bool UK2Node_MyDivide::IsNodePure() const
{
    return true;
}

データピンの処理を定義

最後にデータピンの処理を定義します。
データピンの処理は、計算グラフとして定義する必要があります。

今回作成するノードで実行する処理は、入力として2つの整数「A」「B」を受け取り、A÷Bの商「Quotient」と余り「Remainder」を出力するというものでした。
これをブループリントで実現する場合は、次のようにします。

ブループリントでの実装

  • 入力ピン「A」と「B」を「/」ノードの入力に接続し、その出力を出力ピン「Quotient」に接続する
  • 入力ピン「A」と「B」を「%」ノードの入力に接続し、その出力を出力ピン「Remainder」に接続する

「/」ノードは、C++のブループリント関数ライブラリ「UKismetMathLibrary」のライブラリ関数「Divide_IntInt」の呼び出しに対応します。
「%」ノードは、ブループリント関数ライブラリ「UKismetMathLibrary」のライブラリ関数「Percent_IntInt」の呼び出しに対応します。
したがってメンバ関数 ExpandNode には、次の処理を実現するための処理を定義すればよいことになります。

  • 入力ピン「A」と「B」を入力としてライブラリ関数「Divide_IntInt」を呼び出し、その結果を出力ピン「Quotient」に接続する
  • 入力ピン「A」と「B」を入力としてライブラリ関数「Percent_IntInt」を呼び出し、その結果を出力ピン「Remainder」に接続する

メンバ関数 ExpandNode のソースコードを次に示します。

void UK2Node_MyDivide::ExpandNode(FKismetCompilerContext& CompilerContext, UEdGraph* SourceGraph)
{
    // 「/」ノードの配置
    UK2Node_CallFunction* CallDivideFunction = CompilerContext.SpawnIntermediateNode(this, SourceGraph);
    CallDivideFunction->SetFromFunction(UKismetMathLibrary::StaticClass()->FindFunctionByName(GET_MEMBER_NAME_CHECKED(UKismetMathLibrary, Divide_IntInt)));
    CallDivideFunction->AllocateDefaultPins();
    CompilerContext.MovePinLinksToIntermediate(*FindPinChecked(QuotientPinName), *CallDivideFunction->GetReturnValuePin());
    CompilerContext.MovePinLinksToIntermediate(*FindPinChecked(APinName), *CallDivideFunction->FindPinChecked(TEXT("A")));
    CompilerContext.MovePinLinksToIntermediate(*FindPinChecked(BPinName), *CallDivideFunction->FindPinChecked(TEXT("B")));

    // 「%」ノードの配置
    UK2Node_CallFunction* CallPercentFunction = CompilerContext.SpawnIntermediateNode(this, SourceGraph);
    CallPercentFunction->SetFromFunction(UKismetMathLibrary::StaticClass()->FindFunctionByName(GET_MEMBER_NAME_CHECKED(UKismetMathLibrary, Percent_IntInt)));
    CallPercentFunction->AllocateDefaultPins();
    CompilerContext.MovePinLinksToIntermediate(*FindPinChecked(RemainderPinName), *CallPercentFunction->GetReturnValuePin());
    CompilerContext.MovePinLinksToIntermediate(*FindPinChecked(APinName), *CallPercentFunction->FindPinChecked(TEXT("A")));
    CompilerContext.MovePinLinksToIntermediate(*FindPinChecked(BPinName), *CallPercentFunction->FindPinChecked(TEXT("B")));
}

引数の CompilerContext のメンバ関数 SpawnIntermediateNode を呼び出し、テンプレートパラメータに UK2Node_CallFunction を指定することで、ライブラリ関数を呼び出すためのノードを追加できます。

UK2Node_CallFunction のメンバ関数 SetFromFunction は、呼び出すライブラリ関数を指定するためのものです。
指定された引数より、追加された1つ目のノード(CallDivideFunction)が「/」ノードを追加するためのものであることがわかります。
ただし、この状態では「/」ノードのピンが追加されていないため、メンバ関数 AllocateDefaultPins を呼び出してピンを追加します。

CompilerContext のメンバ関数 MovePinLinksToIntermediate は、指定された2つのピンを接続します。
ここでは、次のピン同士を接続します。

  • 出力ピン「Quotient」と CallDivideFunction の出力ピン
  • 入力ピン「A」と CallDivideFunction の入力ピン「A」
  • 入力ピン「B」と CallDivideFunction の入力ピン「B」

同様にして「%」ノードを追加します。
これでノードを作成できたので、ビルドできることを確認します。

作成したノードの利用

作成したノードを利用してみましょう。 ブループリントエディタを開いて右クリックでメニューを表示し、検索ボックスに「mydivide」を入力します。

ノード「MyDivide」の検索

カテゴリ「Math」に追加されているノード「MyDivide」を選択して配置します。

入力の値を適当に設定したActorを作成します。

Actorの作成

作成したActorをレベルに配置して実行すると、ノードの入力値を使って割り算で求めた商と余りが出力されることが確認できます。

ノード「MyDivide」の実行