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

一般的なブループリントノードのUIはある程度型が決まっており、作成者が意識することなくきれいなUIが表示されます。
しかし一部のブループリントノードは、ユーザがより理解しやすい独自のUIを提供しています。
例えば、ブループリントノード「Switch on Int」はデフォルト実行の上に横棒が追加されているため、条件実行の場合と区別しやすくなっています。

ブループリントノード「Switch on Int」のUI

本記事では、メニュー編 で作成したブループリントノードのUIを変更します。
メニュー編の内容を理解する必要はないですが、気になる方は軽く目を通しておきましょう。

ソースコード一式

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

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

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

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

変更前後のUI

ブループリントノード「MyManualSwitch」について、変更後のUIを示します。

ブループリントノード「MyManualSwitch」のUI

入力側の実行ピンの下に、横棒と実行先を表すテキストが新たに追加されていることが分かります。
実行先を表すテキストは、実行先の変更にあわせて変化することにも注目しましょう。

モジュールの作成

基本編で説明した内容に従ってモジュールを作成します。
具体的な作成方法については、基本編の記事 を参考にしてください。

ブループリントノードのUI変更手順

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

  1. クラス SGraphNodeK2Base の継承
  2. 基本情報の設定
  3. UI構築処理の定義
  4. UIとブループリントノードの関連づけ

1. クラス SGraphNodeK2Base の継承

最初にクラス SGraphNodeK2Base を継承したクラス SGraphNodeMyManualSwitch を定義します。

class SGraphNodeMyManualSwitch : public SGraphNodeK2Base
{
    SLATE_BEGIN_ARGS(SGraphNodeMyManualSwitch)
    {
    }
    SLATE_END_ARGS()

    void Construct(const FArguments& InArgs, UK2Node_MyManualSwitch* InNode);

    virtual void CreatePinWidgets() override;
};

マクロ SLATE_BEGIN_ARGSSLATE_END_ARGS は、インスタンス化するときに渡すことができるパラメータの仕様を定義するものですが、今回の場合は使用しません。

クラス SGraphNodeMyManualSwitch には、2つのメンバ関数があります。

メンバ関数名 意味
Construct インスタンス化時に呼ばれる。
基本情報をここで設定する。
CreatePinWidgets 入出力のピンを描画するときに呼ばれる。
UI構築処理をここで定義する。

2. 基本情報の設定

基本情報の設定は、クラス SGraphNodeMyManualSwitch のメンバ関数 Construct に設定の処理を定義することで行えます。

void SGraphNodeMyManualSwitch::Construct(const FArguments& InArgs, UK2Node_MyManualSwitch* InNode)
{
    GraphNode = InNode;

    SetCursor(EMouseCursor::GrabHand);

    UpdateGraphNode();
}

GraphNode は親クラスに定義されているメンバ変数で、のちほどUIを構築する処理を定義するために利用します。
ここでは、引数の InNode をそのまま代入するのが一般的です。

次にメンバ関数 SetCursor を呼び出し、ブループリントノード上にマウスオーバーしたときにカーソルが手のアイコンに変更されるようにします。
メンバ関数 SetCursor に渡せる列挙型 EMouseCursor の値によって、表示するカーソルのアイコンを変更できる点に注意しましょう。

最後にメンバ関数 UpdateGraphNode を呼び出し、ブループリントノードのUIに変更があったことを通知します。

3. UI構築処理の定義

クラス SGraphNode のメンバ関数 CreatePinWidgets をオーバーライドしてUIを構築する処理を定義します。
UIの構築は、Unreal Engineの スレートUIフレームワーク にしたがって構築します。

void SGraphNodeMyManualSwitch::CreatePinWidgets()
{
    UK2Node_MyManualSwitch* MyManualSwitch = CastChecked(GraphNode);

    // ピンのUIを追加
    for (auto It = GraphNode->Pins.CreateConstIterator(); It; ++It)
    {
        UEdGraphPin* Pin = *It;
        if (!Pin->bHidden)
        {
            TSharedPtr NewPin = FNodeFactory::CreatePinWidget(Pin);
            check(NewPin.IsValid());

            AddPin(NewPin.ToSharedRef());
        }
    }

    bool bExecuteA = MyManualSwitch->GetSwitchValue();

    // 横棒を追加
    LeftNodeBox->AddSlot()
        .AutoHeight()
        .HAlign(HAlign_Left)
        .VAlign(VAlign_Center)
        [
            SNew(SImage)
            .Image(FEditorStyle::GetBrush("Graph.Pin.DefaultPinSeparator"))
        ];
    // 実行先のテキストを追加
    LeftNodeBox->AddSlot()
        .AutoHeight()
        .HAlign(HAlign_Left)
        .VAlign(VAlign_Center)
        .Padding(10.0f, 5.0f)
        [
            SNew(STextBlock)
            .Text(FText::FromString(bExecuteA ? "Execute A" : "Execute B"))
        ];
}

スレートUIフレームワークについてはここでは詳しく説明しませんが、ブループリントノードのUIを構築する処理で2つ補足します。

ピンの追加

クラス FNodeFactory のメンバ関数 CreatePinWidget で作成し、メンバ関数 AddPin を呼び出すことでブループリントノードのUIに追加できます。
独自にピンのUIを作ることもできますが、デフォルトのUIを利用するのであればこれらのメンバ関数を利用するのが手っ取り早く実装できるでしょう。
ピンには入出力の方向があるため追加するUIの場所がそれぞれ異なりますが、メンバ関数 AddPin はピンの入出力によって追加するUIの場所を自動的に判断してくれます。

LeftNodeBox

今回はブループリントノードの左側にUIを追加するため、メンバ変数 LeftNodeBox に対して横棒と実行先のテキストを追加しています。
ブループリントノードの右側にUIを追加する場合は、メンバ変数 RightNodeBox に対して操作を行います。
このようにブループリントノードに対してUIを変更する場合は、LeftNodeBox もしくは RightNodeBox に対して操作を行うことになります。

4. UIとブループリントノードの関連づけ

最後にクラス SGraphNodeK2Base で定義したUIとブループリントノード(クラス UK2Node_MyManualSwitch)を関連づけます。
モジュールロード時に実行されるメンバ関数 FMyManualSwitchModule::StartupModule に該当の処理を追加することで関連づけが可能です。

void FMyManualSwitchModule::StartupModule()
{
    GraphPanelNodeFactory_MyManualSwitch = MakeShareable(new FGraphPanelNodeFactory_MyManualSwitch());
    FEdGraphUtilities::RegisterVisualNodeFactory(GraphPanelNodeFactory_MyManualSwitch);
}

今回はクラス FGraphPanelNodeFactory_MyManualSwitch を定義し、そのインスタンスをメンバ関数 FEdGraphUtilities::RegisterVisualNodeFactory に渡すことで実現しています。
クラス FGraphPanelNodeFactory_MyManualSwitch の定義を次に示します。

class FGraphPanelNodeFactory_MyManualSwitch : public FGraphPanelNodeFactory
{
    virtual TSharedPtr CreateNode(UEdGraphNode* Node) const override
    {
        if (UK2Node_MyManualSwitch* MyManualSwitch = Cast(Node))
        {
            return SNew(SGraphNodeMyManualSwitch, MyManualSwitch);
        }

        return nullptr;
    }
};

クラス FGraphPanelNodeFactory を継承し、メンバ関数 CreateNode をオーバーライドします。
メンバ関数 CreateNode の引数にはクラス UEdGraphNode から継承されたブループリントノードが渡されてくるため、そのブループリントノードが UK2Node_MyManualSwitch であるか否かを判定します。
UK2Node_MyManualSwitch の場合は、SNew を呼び出して SGraphNodeMyManualSwitch をインスタンス化して返します。
これによって、ユーザが UK2Node_MyManualSwitch を追加した時に SGraphNodeMyManualSwitch が呼ばれて専用のUIが表示されるようになります。

関連づけたUIは、モジュールアンロード時に関連づけを解除する必要があります。

void FMyManualSwitchModule::ShutdownModule()
{
    if (GraphPanelNodeFactory_MyManualSwitch.IsValid())
    {
        FEdGraphUtilities::UnregisterVisualNodeFactory(GraphPanelNodeFactory_MyManualSwitch);
        GraphPanelNodeFactory_MyManualSwitch.Reset();
    }
}

作成したノードの利用

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

ノード「MyManualSwitch」の検索

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

Actorの作成

この状態でブループリントノードを右クリックして実行先を変更すると、現在の実行先に応じて表示されるテキストが変化します。