こんにちは、株式会社Leon Gameworksの遠藤です。
今回は、前回紹介した「ブループリントに含まれるグラフを取得する機能」を応用し、ブループリントエディタのツールバーに未使用ノードを一覧表示するメニューを追加してみます。本稿ではEditor Utility Widgetは使用せず、機能の実装は全てC++で行います。
0:動作環境
本記事はUE5.8.1を基に執筆しており、画面のスクリーンショットはエディタの言語設定を「英語」として撮影しています。
なお、本記事で作成するプロジェクト一式は、以下よりダウンロード可能です。
1:エディタ専用モジュールの追加
まずは .uproject にエディタ専用モジュールを追加します。
{
"FileVersion": 3,
"EngineAssociation": "5.8",
"Category": "",
"Description": "",
"Modules": [
{
"Name": "EUTools23",
"Type": "Runtime",
"LoadingPhase": "Default",
"AdditionalDependencies": [
"Engine"
]
},
{
"Name": "EUTools23Editor",
"Type": "Editor",
"LoadingPhase": "PostEngineInit",
"AdditionalDependencies": [
"Engine",
"CoreUObject",
"UnrealEd",
"Kismet",
"BlueprintGraph",
"BlueprintEditorLibrary",
"ToolMenus"
]
}
]
}
▲ エディタ専用モジュールを追加
次に、Build.csを設定します。ブループリント内のグラフを取得するためにBlueprintEditorLibrary、ブループリントノードを扱うためにBlueprintGraphを依存モジュールとして追加します。
using UnrealBuildTool;
public class EUTools23Editor : ModuleRules
{
public EUTools23Editor(ReadOnlyTargetRules Target)
: base(Target)
{
PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;
PublicDependencyModuleNames.AddRange(
new string[]
{
"Core"
});
PrivateDependencyModuleNames.AddRange(
new string[]
{
"CoreUObject",
"Engine",
"Slate",
"SlateCore",
"UnrealEd",
"ToolMenus",
"Kismet",
"BlueprintGraph",
"BlueprintEditorLibrary"
});
}
}
▲ Build.csを追加
2:ブループリントエディタのツールバーを拡張
続いて、ブループリントエディタのツールバーを拡張し、独自メニューを追加します。まずは、その処理を定義するEUTools23Editor.hを作成します。
#pragma once
#include "CoreMinimal.h"
#include "Modules/ModuleManager.h"
class SWidget;
class UEdGraphNode;
struct FToolMenuContext;
class FEUTools23EditorModule : public IModuleInterface
{
public:
//~ Begin IModuleInterface Interface
virtual void StartupModule() override;
virtual void ShutdownModule() override;
//~ End IModuleInterface Interface
private:
// ブループリントエディタへメニューを登録
void RegisterMenus();
// [Unused Nodes]を開いた際のメニューを生成
static TSharedRef<SWidget> GenerateUnusedNodesMenu(const FToolMenuContext& MenuContext);
// ノードが未使用か判定
static bool IsUnusedNode(UEdGraphNode* Node);
// 指定したノードへ移動
static void JumpToNode(TWeakObjectPtr<UEdGraphNode> Node);
};
▲ EUTools23Editor.h を追加
主に、以下の3つの処理を実装します。
・RegisterMenus:ブループリントエディタのツールバーに[Unused Nodes]メニューを追加します。
・GenerateUnusedNodesMenu:現在開いているブループリントから未使用ノードを検索し、その結果を基にメニュー項目を動的に生成します。
・JumpToNode:メニューで選択したノードへ移動します。
続いて、EUTools23Editor.cppを作成します。まずは、ツールバーへのメニュー登録処理から実装します。
#include "EUTools23Editor.h"
#include "BlueprintEditorContext.h"
#include "BlueprintEditorLibrary.h"
#include "EdGraph/EdGraph.h"
#include "EdGraph/EdGraphNode.h"
#include "EdGraph/EdGraphPin.h"
#include "Engine/Blueprint.h"
#include "Framework/MultiBox/MultiBoxBuilder.h"
#include "K2Node.h"
#include "Kismet2/KismetEditorUtilities.h"
#include "ToolMenu.h"
#include "ToolMenuContext.h"
#include "ToolMenuDelegates.h"
#include "ToolMenuEntry.h"
#include "ToolMenus.h"
#define LOCTEXT_NAMESPACE "FEUTools23EditorModule"
void FEUTools23EditorModule::StartupModule()
{
UToolMenus::RegisterStartupCallback(
FSimpleMulticastDelegate::FDelegate::CreateRaw(
this,
&FEUTools23EditorModule::RegisterMenus));
}
void FEUTools23EditorModule::ShutdownModule()
{
UToolMenus::UnRegisterStartupCallback(this);
UToolMenus::UnregisterOwner(this);
}
void FEUTools23EditorModule::RegisterMenus()
{
FToolMenuOwnerScoped OwnerScoped(this);
UToolMenu* ToolbarMenu =
UToolMenus::Get()->ExtendMenu(
"AssetEditor.BlueprintEditor.ToolBar");
FToolMenuSection& Section =
ToolbarMenu->FindOrAddSection(
"EUTools23");
FToolMenuEntry Entry =
FToolMenuEntry::InitComboButton(
"UnusedNodes",
FToolUIActionChoice(),
FNewToolMenuChoice(
FNewToolMenuWidget::CreateStatic(
&FEUTools23EditorModule::
GenerateUnusedNodesMenu)),
LOCTEXT(
"UnusedNodes_Label",
"Unused Nodes"),
LOCTEXT(
"UnusedNodes_Tooltip",
"Lists nodes with no connections "
"in the current Blueprint."),
FSlateIcon(),
false,
NAME_None);
Section.AddEntry(Entry);
}
TSharedRef<SWidget>
FEUTools23EditorModule::GenerateUnusedNodesMenu(
const FToolMenuContext& MenuContext)
{
FMenuBuilder MenuBuilder(
true,
nullptr);
const UBlueprintEditorToolMenuContext*
BlueprintContext =
MenuContext.FindContext<
UBlueprintEditorToolMenuContext>();
UBlueprint* Blueprint =
BlueprintContext
? BlueprintContext->GetBlueprintObj()
: nullptr;
if (!IsValid(Blueprint))
{
MenuBuilder.AddMenuEntry(
LOCTEXT(
"BlueprintNotFound",
"Blueprint could not be found."),
FText::GetEmpty(),
FSlateIcon(),
FUIAction(
FExecuteAction(),
FCanExecuteAction::CreateLambda(
[]
{
return false;
})));
return MenuBuilder.MakeWidget();
}
// この後にメニュー生成処理を追加
return MenuBuilder.MakeWidget();
}
▲ EUTools23Editor.cpp を追加
ここまで実装したら、一度ビルドしてエディタを起動してみましょう。ブループリントエディタを開くと、ツールバーに[Unused Nodes]メニューが追加されていることを確認できます。
3:未使用ノードの取得
GenerateUnusedNodesMenuでブループリントを取得したら、続いて、そのブループリントに含まれるグラフを取得する処理を追加します。
int32 UnusedNodeCount = 0;
for (UEdGraph* Graph :
UBlueprintEditorLibrary::ListGraphs(Blueprint))
{
if (!IsValid(Graph))
{
continue;
}
TArray<UEdGraphNode*> UnusedNodes;
for (UEdGraphNode* Node : Graph->Nodes)
{
if (IsUnusedNode(Node))
{
UnusedNodes.Add(Node);
}
}
if (UnusedNodes.IsEmpty())
{
continue;
}
UnusedNodeCount += UnusedNodes.Num();
// この後にグラフごとのメニュー生成処理を追加
}
▲ブループリントに含まれるグラフを取得
続いて、IsUnusedNodeを実装します。
bool FEUTools23EditorModule::IsUnusedNode(
UEdGraphNode* Node)
{
if (!IsValid(Node))
{
return false;
}
// コメントノードなど、K2ノード以外は対象外
if (!Node->IsA<UK2Node>())
{
return false;
}
// Function Entryなど、
// ユーザーが削除できないノードは対象外
if (!Node->CanUserDeleteNode())
{
return false;
}
for (const UEdGraphPin* Pin :
Node->GetAllPins())
{
if (Pin &&
Pin->HasAnyConnections())
{
return false;
}
}
return true;
}
▲IsUnusedNodeの実装
ノードの収集対象は、UK2Nodeを継承するノードに限定します。各ノードについてピンの接続状態を確認し、未使用と判定されたノードを抽出します。
未使用ノードが見つかった場合は、グラフごとにメニュー項目を生成します。先ほどのGenerateUnusedNodesMenu内で、未使用ノードを取得する処理に続けて、以下の処理を追加します。
MenuBuilder.BeginSection(
Graph->GetFName(),
FText::FromName(Graph->GetFName()));
for (UEdGraphNode* Node : UnusedNodes)
{
const TWeakObjectPtr<UEdGraphNode>
WeakNode(Node);
FText NodeTitle =
Node->GetNodeTitle(
ENodeTitleType::ListView);
if (NodeTitle.IsEmpty())
{
NodeTitle =
Node->GetClass()->
GetDisplayNameText();
}
MenuBuilder.AddMenuEntry(
NodeTitle,
FText::Format(
LOCTEXT(
"JumpToNode_Tooltip",
"Jump to {0} in {1}."),
NodeTitle,
FText::FromName(
Graph->GetFName())),
FSlateIcon(),
FUIAction(
FExecuteAction::CreateStatic(
&FEUTools23EditorModule::
JumpToNode,
WeakNode)));
}
MenuBuilder.EndSection();
▲メニュー項目の生成処理
ここまで実装したら、ビルドして動作を確認してみましょう。ブループリント内に未使用ノードが存在する場合は、メニューに対応する項目が生成されます。
未使用ノードがひとつも見つからなかった場合は、その旨を示すメニュー項目を表示するようにします。グラフの列挙処理が完了した後に、以下の処理を追加します。
if (UnusedNodeCount == 0)
{
MenuBuilder.AddMenuEntry(
LOCTEXT(
"UnusedNodeNotFound",
"No unused nodes were found."),
FText::GetEmpty(),
FSlateIcon(),
FUIAction(
FExecuteAction(),
FCanExecuteAction::CreateLambda(
[]
{
return false;
})));
}
▲未使用ノードがひとつもなかった場合の処理
4:対象ノードへジャンプ
最後に、メニューから選択しノードへ移動するJumpToNodeを実装します。
void FEUTools23EditorModule::JumpToNode(
TWeakObjectPtr<UEdGraphNode> Node)
{
if (!Node.IsValid())
{
return;
}
FKismetEditorUtilities::
BringKismetToFocusAttentionOnObject(
Node.Get(),
false);
}
▲JumpToNodeの実装
FKismetEditorUtilities::BringKismetToFocusAttentionOnObjectに対象ノードを渡すことで、そのノードを含むブループリントエディタとグラフを開き、対象ノードへフォーカスできます。
ここまで実装すれば完成です。
5:まとめ
今回は、未使用ノードを検出し、対象ノードへ移動できるメニューを実装しました。検出結果を一覧で確認できるため、各ノードが本当に不要かどうかを確認した上で、ユーザー自身が対応を判断できます。
さらに発展させるなら、次または前の未使用ノードへ順番に移動する機能や、ツールバー上への検出数の表示、選択した未使用ノードを削除する機能などを追加するのも有効です。
本記事で作成したプロジェクト一式は、以下よりダウンロード可能です。
株式会社Leon Gameworks
●公式サイト
www.leon-game.co.jp
●X(Twitter)
@Leon_Gameworks
トンコツ(遠藤俊太)
●トンコツ開発ブログ
shuntaendo.hatenablog.com
●X(Twitter)
@tonkotsu3656
TEXT_トンコツ(Leon Gameworks)
EDIT_小村仁美 / Hitomi Komura(CGWORLD)、オムライス駆