※この記事で使用しているUnrealのVersionは5.6.1です。

※この記事のサンプルプロジェクトはありません。
代わりに、作成したプラグインはそのままGitHubにて公開しております。

※この記事はUnreal Engine (UE) Advent Calendar 2025の14日目の記事になります。
昨日は我らがおかずさん猫でもざっくり分かる新しいカメラシステム「Gameplay Camera」でした!
まだ実験的機能とのことですが、内容が濃く今後もGameplay Cameraの機能が拡充されていきそうとのことでめちゃめちゃ参考になる記事でした!

アドカレの記事を日々読ませてもらっていますが、どれも良い記事で素晴らしいなと思いつつ、自分もネタを考えて見たところ結局毎度のちょっとしたEditor拡張ネタとなりました。

目新しいような情報でもありませんが、何かの助けになりましたら幸いです。

前置き

皆さんお久しぶり&はじめましてです。 サカナダヨッ>🐟

皆さんは快適なUnreal Engineライフを過ごしておりますでしょうか?

UEFNのVerseが近い内にBlueprintの座を奪ったりするのかなとか思いつつも、Blueprintエンジョイ勢として色々な案件で日々Unreal Engineを触らせてもらい、少しでも快適に開発できたらなといろんな実装をしてきました。

そんなお魚ですが、今回はUnreal Engine標準で提供されている型であるName(FName)/String(FString)についてのエディタ上の拡張話となります。

※前半は読まなくても本文は理解できますので、手っ取り早く拡張の実装方法等を知りたい場合は本文から読むことをおすすめします。

Unreal Engineにおけるグローバルな名前の管理方法について考える レベル【★】


少し話はそれますが、ゲームを開発する上で、「プロジェクト全体で一意の名前(ID)を管理したい」ケースは非常によくあるかなと思います。

例えば、

・アイテムID
・キャラID
・ステート名
・敵AIの種類
・イベントID
・フラグID

…など、ゲーム内の種類・分類を整理するための“グローバルID”をどう設計するかは、プロジェクト規模が大きくなるほど重要になってきます。

では、Unreal Engineではどのような実装パターンが考えられるのでしょうか?

色々考えられますが、自分の中では大まかに以下の4つの選択肢があるかなと思っております。

① GameplayTag


“グローバルなID”として Unreal Engine が標準提供している仕組みです。

公式ドキュメント:Gameplay Tag

Project Settings(プロジェクト設定) にある DefaultGameplayTags.ini 等の設定ファイルに定義するだけで気軽に扱え、使い勝手も非常に良いため、手軽かつ強力に ID を管理したい場合は真っ先に候補になります(迷ったらGameplayTagでも大体なんとかなるらしい)

C++ からも以下マクロを使用して宣言して使うことができます。

//ヘッダーに記載
#pragma once

#include "NativeGameplayTags.h"

// タグの宣言
UE_DECLARE_GAMEPLAY_TAG_EXTERN(TAG_Test);
//Cppファイルに記載

// 実体の定義(コメント無しの場合)
UE_DEFINE_GAMEPLAY_TAG(Ability_Test, "Ability.Test");

// 実体の定義(コメントありの場合)
UE_DEFINE_GAMEPLAY_TAG_COMMENT(Ability_Test,"Ability.Test","Test Ability");

// このCpp内だけで使うタグ
UE_DEFINE_GAMEPLAY_TAG_STATIC(Ability_Test_Internal, "Ability.Test.Internal");

↑のマクロのいずれか(EXTERNは定義と合わせて使用)で、C++で扱いやすい形でGameplayTagを運用することができます。
コード上やエディタ上で一元管理されたタグを使用できるので、定義の手間は多少ありますがC++メインの開発であればGameplayTagが扱いやすくなるかなと思います。

FGameplayTagFGameplayTagContainer 型で変数化でき、
プロパティウィンドウや Blueprint では専用のドロップダウンUIにより、文字入力なしで確実に選択できます。

また、GameplayTagは使用しているアセットをReference Viewerで探し出すことができるのでどこでどんな使われ方をしているのか検知することも容易です。

さらに、Gameplay Ability System(GAS)をはじめ、エンジン内の多くの機能と強く統合されており、
大規模・長期運用のゲームでも採用価値が高くなります。

ただ、便利すぎるが故に何でもかんでもGameplayTagで実装してしまうようなプロジェクトもよくありますが、Tagが多すぎて何の用途で使っているかもわからないし一覧性にかける場合も出てくるのは気をつけたいところです。

その性質上必然的にTagの名前は階層構造を作ってしまいますが、階層が不要なシンプルな名前を作る場合にも過剰な機能かなとは思います。

今年のアドカレにもいくつか参考になりそうな記事があったので、リンクを貼らせてもらいます。

@T_Sumisaki(Tatsuya Sumisaki)さん
GameplayTagのすすめ

@ude1932さん
【UE5】GameplayTagをC++上で参照しやすくする為のツールを作ってみた

② DataTable


扱う名前に紐づいたメタ情報ごとまとめて管理したい場合に最適な方法です。みんなの味方ですね。

C++ やBlueprintで構造体を定義し、それを基にテーブル化するため、
マスターデータのように整理して運用できます。

公式ドキュメント:DataTable

主なメリットとしては、

・テーブルな見た目なので一覧性がいい
・CSV→DataTable変換で外部ツール(Excel / スプレッドシート)と連携しやすい
・EditorUtility などによる自動化との相性が良い
・アセットとして複数 DataTable を切り替える運用も容易
・DataAssetと組み合わせることで柔軟性をさらに向上可能

あたりかなと思います。

一方で、RowNameFName型であるため、GameplayTagほど変数・Blueprint 上でのサポートは厚くありません。

ただし、

FDataTableRowHandle
FDataTableCategoryHandle

を使えば、指定 DataTable から 自動ドロップダウンが生成されるので、実務では十分使えるレベルの UI サポートがあります。

また、FDataTableRowHandleを使っていれば、GameplayTagと同じようにDataTableのRowNameの参照をReference Viewerにて表示することができます。

Blueprint では GetDataTableRow ノードのみドロップダウンサポートがありますが、テーブル指定がピン接続されている場合は候補が表示されなくなる点には要注意です。

③ Enum


名前の選択肢がほぼ固定で、実装者側だけで閉じた用途であれば、シンプルに Enum(列挙型) を使う方法があります。

ドキュメント:【BP】Enumeration(列挙型)【C++】Enumeration(列挙型)

Blueprint / C++ どちらでも定義でき、Editor上では必ずドロップダウンUIが表示されるため、入力ミスがなく、安全性の高い選択肢です。コード側で厳密に制御したい(Stateとして扱ったりswitchで分岐する等)場合にも有用です。

Blueprint

C++

UENUM(BlueprintType)
enum class ECharacterType: uint8
{
    Human,
    Monster,
};

プロパティにすると自動でドロップダウン化され、Blueprint グラフ上でもUIが保証されるため、
「固定の要素をプロジェクト側で制御する」用途にはもってこいです。

ただし、以下のような内容は他の選択肢よりは不向きです。

・値の追加・変更はビルドに影響するため、頻繁に編集する場合
・外部データ(CSV/スプレッドシート)と連携は難しい
・何十・何百と要素がある場合

④ Config(独自定義)


GameplayTagに依存したくない(階層構造は不要、GameplayTagが膨大になりすぎるのを防ぎたい、用途が限定的等)けれど、DataTable等のアセットにも依存をしたいわけではないなどの他の選択肢の中間的な選択肢として、Configファイル(ini)を使った独自定義を行う方法も選択肢としてはあります。

公式ドキュメント:Configuration Files

UEでは UCLASS(Config) および UPROPERTY(Config) を使うことで、DefaultGame.iniをはじめ自作ini等から値を読み書きする仕組みを簡単に作れます。

UObject派生であればどんなクラスでも公開できるので、たとえばUGameinstanceSubsystem等のライフサイクルが明確で扱いやすいクラスのプロパティをそのまま公開することができます。

//DefaultGame.iniプロパティを公開
UCLASS(Config=Game,defaultconfig)
class UGlobalNameSettings : public UGameInstanceSubsystem
{
    GENERATED_BODY()

public:
    //UPROPERTYで"Config"を付与することで公開される
    UPROPERTY(Config, EditAnywhere, Category="NameList")
    TArray<FString> MyNameList;
};

また、iniファイルに公開したプロパティはProject Settings(プロジェクト設定)Editor Preference(エディタの環境設定)の画面へプロパティを公開できます。
→プロジェクトやプラグインのIModuleInterfaceを継承したクラスのStartupModuleにて公開するクラスを登録することで可能です

virtual void StartupModule() override
{
#if WITH_EDITOR
	ISettingsModule* SettingsModule = FModuleManager::GetModulePtr<ISettingsModule>("Settings");
	if (SettingsModule)
	{
		//Project Settingsへの公開(Editor Preferenceなら第1引数はEditor)
		SettingsModule->RegisterSettings(
			"Project",
			"Game",
			"GlobalName",
			LOCTEXT("SettingName", "GlobalName"),
			LOCTEXT("SettingDescription", "GlobalNameの設定"),
			GetMutableDefault<UGlobalNameSettings >()
		);
	}
#endif
}

virtual void ShutdownModule() override
{
#if WITH_EDITOR
	ISettingsModule* SettingsModule = FModuleManager::GetModulePtr<ISettingsModule>("Settings");
	if (SettingsModule)
	{
		SettingsModule->UnregisterSettings(
			"Project",
			"Game",
			"GlobalName"
		);
	}
#endif
}

※ちなみに特にクラスにこだわりが無ければ、UDeveloperSettingsを継承しておくとStartupModuleでの登録が不要です。

では、どれを使うべきか?

ここまで見てきたように、
名前(ID)管理の方法は用途に応じて大きく変わります。

方法用途長所短所
GameplayTag・ゲーム全体で共通のIDを扱う
・ GASやシステム連携が多い
・標準機能でサポートが厚い
・ドロップダウンUIが自動生成
・階層構造で整理しやすい
・GASとの親和性が高い
・階層構造が不要な用途では過剰
・タグ数が増えると管理が煩雑
DataTable・名前にメタ情報を紐付けたい
・マスターデータとして扱いたい
・CSV/Excelと連携しやすい
・自動化・外部編集と相性が良い
・DataAsset併用で柔軟性を高められる
・RowName が FName でUIサポート弱め
・BPのドロップダウンは限定的
Enum・値が固定
・実装者側で閉じたID管理
・常にドロップダウンUIで安全
・入力ミスが原理的に起こらない(コンパイルで判明)
・コード側で扱いやすい(switch等)
・値の追加/変更でコードに影響(ビルドやコンパイルが必要)
・大量項目には向かない
・外部データとの相性が悪い
独自の定義(Config)・軽量にIDを扱いたい
・アセット依存を避けたい
・特定用途に最適化したい
・独自の UObject 定義のプロパティをそのまま公開できる
・公開したい型・構造を自由に設計でき、柔軟性が高い
・仕組みを自作する必要がある(ほぼC++)
・標準UIサポートは存在しない
・高度な構造化データには向かない

また、上記の情報以外にもプロジェクトの規模、チーム構成、更新頻度等のコンテキストに応じて、
最適解は変わって来るかなと思いますし、場合によっては複数の合せ技なんかも考えられたりします。

長々と前置きを書きましたが、ここで言いたかったこととしては「名前入力のヒューマンエラーをいかに防ぐか、どのような場所で使われているかが容易に確認できること」がとても重要という点です。

特に Unreal Engine では、GameplayTagやEnumのように安全なUIが用意されている反面、NameやStringを使う入力欄に関しては “生の文字入力” になっているケースが多く存在します。

でも、すべての状況でこういった直入力を使わないと言うのはなかなか難しいところでもあります。

このような場面では、たとえ内部的にID管理が整備されていても、ユーザー入力が「文字入力」である限りヒューマンエラーは必ず発生します。

また、どこにどんな値が入っているかは当然確認しやすいほうが様々な問題を未然に防げる確率が上がります。

運用でカバーしたいところではありますが、少人数で開発環境を整えずに進めている場合や、割と大規模なプロジェクトほど人力に頼ったりする場合が多くその結果手作業な部分も多くなりがちです(大型RPGの開発なんかはかなりの確率でこういう部分が懸念されます)

そういった場面をなるべく防ぐためにもにできる限り実装側でも配慮できればなと個人的には思うので、今回はその一例としてBlueprintのノードピンおよびプロパティの Name / String の取り扱いについて深堀り&拡張してみました。

Name / Stringを安全に扱える機能 ~GetOptions~ レベル【★★☆】

① 変数のGetOptions


FNameFStringを使ったUPROPERTYな変数には、GetOptionsというとても便利なメタ指定子があります。

これは、任意のFName,FStringプロパティの入力をドロップダウンからの選択方式に変更してくれるものです。

ドロップダウンの候補にはTArrayFName,FStringを返す関数を使用することができます。

//ドロップダウンで選択できるName変数
UPROPERTY(EditAnywhere, meta = (GetOptions = "GetMyOptions"))
FName SakanaID;

//ドロップダウンの候補を作成するための関数
UFUNCTION()
TArray<FName> GetMyOptions() const;

これはBlueprintで定義した変数にも設定することができ、Blueprint関数を使ってドロップダウン候補を作成できます。

変数を選択し、DetailsパネルにあるAdvanded以下のDrop-down Optionsの項目に配列のNameかStringを返している関数を選ぶことで有効になります。

デフォルトだと、変数の型と配列で返す型はStringとNameそれぞれ揃えてあげないと候補に出てこないです。

が、指定する関数名は直接文字で入力ができるので、Name(String)変数に対してString(Name)の配列でドロップダウンを作成も可能です。

ちなみに、変数自体をArray,Map,Setに変更すると、Drop-down Optionsの項目が消えてしまいますが、単一プロパティのときにDrop-down Optionsに関数を設定しておくと、Array等に変えてもその要素の中のNameやStringにドロップダウンをつけることが可能です。

↓項目はないけどドロップダウン化されているArray変数

今年のアドカレの記事にもこちらの説明をしている記事がありましたので参考にリンクを貼らせてもらいます。

@mike928neko(Mike Neko)さん
BPのみで変数をドロップダウンメニューにしてデバッグを便利にする

② 引数のGetOptions


GetOptionsは関数の引数にも使えます。

C++での引数のドロップダウン化は2通りやり方があります。

↓その1 UFUNCTION(meta = (GetOptions = ...))を使ったやり方
Getoptionsの名前、使用する関数名、引数の名前をすべて一致させることでドロップダウン化される

    UFUNCTION(BlueprintCallable,meta = (GetOptions = "TargetID"))
    void DoSomething(FName TargetID) {};

    UFUNCTION(BlueprintCallable)
    TArray<FName> TargetID() { return TArray<FName>({ TEXT("Aji"),TEXT("Saba"),TEXT("Iwashi") }); };

↓その2 UPARAM(meta = (GetOptions = ...))を使ったやり方
※こちらは関数名を自由に設定可能

	UFUNCTION(BlueprintCallable)
	void DoSomething(UPARAM(meta = (GetOptions = "GetMyOptions"))FName TargetID) {};

	UFUNCTION(BlueprintCallable)
	TArray<FName> GetMyOptions() { return TArray<FName>({ TEXT("Aji"),TEXT("Saba"),TEXT("Iwashi") }); };

現状、標準機能のBlueprintだけでは関数の引数にドロップダウンはつけられなさそうです。

が、少しC++とEditorUtilityを書くことでBlueprint定義の関数引数にもGetOptionsでドロップダウンをつけることができます。

//Blueprint公開用のC++ライブラリ
UCLASS()
class KACND_API UKAEditorUtilityLibrary : public UBlueprintFunctionLibrary
{
	GENERATED_BODY()

public:

    //Blueprint 関数グラフへ Meta をセットする
    UFUNCTION(BlueprintCallable)
    static void SetBlueprintFunctionMeta(
        UEdGraph* FunctionGraph,
        const FName MetaKey,
        const FString& MetaValue
    )
    {
        if (!FunctionGraph || MetaKey.IsNone())
        {
            return;
        }

        // グラフの Function 用メタ構造体(FKismetUserDeclaredFunctionMetadata)を取得
        FKismetUserDeclaredFunctionMetadata* MetaData =
            FBlueprintEditorUtils::GetGraphFunctionMetaData(FunctionGraph);
        if (!MetaData)
        {
            return;
        }

        if (MetaValue.IsEmpty())
        {
            MetaData->RemoveMetaData(MetaKey);
        }
        else
        {
            MetaData->SetMetaData(MetaKey, MetaValue);
        }
    }
};

↑で定義したSetBlueprintFunctionMeta関数を使ってEditorUtilityで少し処理を書きます。

実行結果

一応これでBlueprintノードにもドロップダウンを仕込むことができました。

この設定は保存もされるのでエディタ起動毎とかに実行する必要はありません。ただ、運用するとなるとドロップダウンがどこで設定されているかがUI上にどこにも表示がないので、設定されたmetaを可視化するツールは作る必要はあるかなと思います。

とまあ、GetOptionsについて説明しましたが、正直これだけでも十分開発する上では助かりますが、実際にプロジェクトで使い込んでいくと、GetOptions だけではカバーしきれない部分も見えてきます。

例えば:

・プロパティ/引数ごとに毎回 meta = (GetOptions = “…”) を書く必要がある
  C++ でも BP でも「この変数/引数だけ別のリストを使いたい」などが増えると、記述がどんどん散らばる

・候補リストの中身がコードやBPに散乱しがち
  プロジェクト全体で共通の「ID一覧」を DataTable / DataAsset などで一元管理したくても、
  実際の GetOptions 実装はあちこちに生えていきがち
  「同じIDを扱っているのに中身Iがバラバラ」という状態になりやすい

・DisplayName や Tooltip などの「人間向けメタ情報」を付けにくい
  内部IDは FName で持ちたいが、UI上は別の表示名で出したい
  各候補ごとに説明文を出したい…といったことを、素のGetOptionsだけではできない

・コンパイル時の検証(Validate)との連携が弱い
  この引数に、候補に存在しないIDが入っていたらコンパイルエラー/警告にしたりは難しい
  特定の関数・特定の変数だけ、もっと厳しめのチェックをかけたい等がある場合も難しい

・どこでどの値が使われているかが辿れない
  特定の値を使っている場所一覧のようなものは、アクター等のプロパティの場合複数のWorldに依存するので一括で追いづらい

等、安全に扱いやすくプロジェクト全体でID体系を整えて運用するとなると、もう一段上の“仕組み”が欲しいな…なんてことをよく思っておりました。

ので、

そんなプラグインをなんとなく作ってみようと思います🐟️

Name / Stringを安全に使えるプラグインの作成 レベル【★★★★】

今回作成するプラグインの完成イメージとして、

1.GetOptionsのようにドロップダウンで入力でき表示をカスタマイズする
  ドロップダウンの値に対応した表示名や補足(Tips)を設定できるように。
2.C++コード不要&Blueprintのみですべての拡張可能(もちろんC++の受け口もあり)
  →ドロップダウン候補や適用範囲はEditorUtilityなBlueprint(C++)やDataTableで設定可能。
  (ブログ説明ではDataTable版は省略)
3.Blueprintのコンパイル時に設定したName/Stringのプロパティの値が規則に違反してたらWarningやErrorを出す
  →BlueprintCompilerExtensionを使ってコンパイル時のValidate規則に任意の処理を追加可能。
4.Reference Viewerにて設定した値がどこで使われているかを追いかけることができる
  →プロパティを持つ親のアセットが保存されるときにSearchableNameを付与。
5.すでに開発が進んでいるプロジェクトでも容易に運用できる設計
  →プラグイン独自の構造体を使用するやり方ではなく、既存のNameやStringのプロパティ入力方式をカスタマイズする。

みたいにしていきます。

※以降ソースコード説明を書きますが、全文書くと膨大なのでほとんどヘッダーのみ記載しています。あくまで実装の流れを書いているだけでブログのまま実装はできません。全ソースを覗きたい方はこちらを御覧ください。

Step0.準備


まずは新規プラグインを作成します。

PluginsウィンドウからAddボタンにて新規プラグインを追加します。

今回のテンプレートはBlank(一番シンプルな構成)を指定しています。

名前はKACustomNameDropdownとしてあります。

作成したら、upluginBuild.csを編集します。

今回はEditor専用のモジュールなので、ModuleTyepEditorにしてあります。

Build.csでは、今回使うモジュールのIncludeをしておきます。

BlueprintGraphPropertyEditor周りをメインに使うので、↓のような構成になっております。

Step1.見た目


まずは見た目となるクラスを作成していきます。

Unrealでは、Editor上のUIの見た目を構成するのはSlateと呼ばれるクラスを継承して作ることができます。

今回はプロパティエディタ上の見た目を構成するクラスとBlueprintのピン上の見た目を構成するクラスの2つを作成します。

プロパティの方はシンプルにSCompoundWidgetを継承、Blueprintのピンに関してはSGraphPinという専用のクラスがあるのでそちらを継承して作成します。

次に、各種Slateクラスを登録するためのクラスを定義します。

プロパティエディタに関しては、IPropertyTypeCustomizationというクラスを継承することで独自のプロパティUIを作成できます。

Blueprintのグラフピンに関しては、FGraphPanelPinFactoryというクラスを継承することでグラフピンを独自のピンUIを作成できます。

次に、それぞれの見た目をUnreal Editorが使用してくれるように登録する処理を記載します。

作成したプラグインのモジュールクラスのStartupModuleShutdownModuleをオーバーライドし中身を記載します。

これでひとまず見た目を作成することができるようになりました。

↓作成したクラスを使った見た目(プロパティエディタ)

↓作成したクラスを使った見た目(Blueprintピン)

実際の値をドロップダウン化した上、表示名を下に追加しました。また、ドロップダウンの候補や表示されているUIにカーソルを合わせることで設定したTipsが出るようにもなっています。

Step2.ドロップダウンの設定


見た目はできたので、今度は任意のプロパティ/ピンに対して自分で定義したドロップダウンの候補を設定できるようにします。

この設定はDataAssetを用いて設定項目を1箇所で管理できるような設計を目指します。また、登録できるものは独自のEditorUtilityなC++/Blueprintなクラスでの拡張も行えるものにしていきます。

まずは、DataAssetに登録するEditorUtilityな設定用のクラスを定義します。後ほど説明する内容ですが、このクラスではドロップダウン設定の他にもコンパイル時のValidation設定やSearchableName付与の設定等もできるようにしておきます。

次に、KACND_DropdownProviderを登録することができるDataAssetクラスを作成します。

このDataAssetクラスを元に、エディタ上でDataAssetを作成するとUKACND_DropdownProviderを継承したクラスを自由に設定できるようになります。

テクニックとしては、EditInlineNewUCLASSInstancedなオブジェクトのUPROPERTY変数に設定することで、変数には自動で設定用オブジェクトのインスタンスが作成され、独自の設定を行えるようになります。

これを最初に作成したSlateクラスで、ここで指定したピンや変数の名前だったらドロップダウンUIを展開するようにすることで設定した情報をそのままピンや変数上に表示が可能です。

ここまでできたので、試しにBlueprintで定義したドロップダウンを使って見ます。

↓EditorUtilityBlueprintとしてUKACND_DropdownProviderを継承。

↓GetNameListをオーバーライドして、ドロップダウンの要素を作成。TargetNamePinListTargetVariableNameListに任意の名前を指定。
※BlueprintNativeEventなのでBlueprintでもC++でも処理をかけます。

↓結果

Step3.コンパイル時のValidation


基礎の設計はできたので、次はValidation(設定された値を検証して、ルール違反をしていた場合に通知したりすること)を拡充していきます。

Blueprintには様々な便利拡張がありますが、今回はUBlueprintCompilerExtensionを使ってBlueprintコンパイル時にドロップダウン候補外の値がピンに入っている場合に通知を出すようにしてみます。

まず、UBlueprintCompilerExtensionを継承したクラスを作成します。

作成したBlueprintCompilerExtensionクラスは、プラグインモジュールのStartupModule関数にて登録を行います。

これで、コンパイルに対して独自の挙動やメッセージを仕込むことができるようになりました。

試しにドロップダウン外の候補の場合にコンパイルエラーにして独自のメッセージを出してみます。

Blueprintで返した結果がBlueprintで使われて…若干面白いですね。

ワンポイントですが、アセットのロード時(削除中のルーティング等)にBlueprintImplementableEvent(BlueprintNativeEvent)は呼んではいけないので、FUObjectThreadContext::Get().IsRoutingPostLoadがTrueの場合は安全を考慮してスキップするようにしています。

void UKACND_BlueprintCompilerExtension::ProcessBlueprintCompiled(
	const FKismetCompilerContext& CompilationContext,
	const FBlueprintCompiledData& Data
)
{
	Super::ProcessBlueprintCompiled(CompilationContext, Data);

	const UBlueprint* Blueprint = CompilationContext.Blueprint;
	if (!Blueprint)
	{
		return;
	}

	//BlueprintNativeEventを読んではいけないタイミング
	//アセットのルーティングによるロード中の場合
	if (FUObjectThreadContext::Get().IsRoutingPostLoad)
	{
		return;
	}
	
	// 関数ノードの引数ピン検証
	ValidateNodes(CompilationContext);
}

ちなみに、このコンパイルがどれだけの負荷がかかっているかもチェックしてみました。

コンパイルエラーを出すノードを500ほど用意し、ValidateをONとOFFで検証。

Validate:OFFのとき 大体230ms前後(5回検証)

Validate:ONのとき 大体250ms前後(5回検証)

コンパイル時間はONにすると少し増えてますが、これぐらいなら許容かなと思います。
(設定する項目が増えるとどんどん増えていきますのでこのあたりは必要に応じてONOFFを調整かなと思います)

ここまで来るとプラグインとして拡張した意義が少しずつですが生まれてきました。

Step4.SearchableNameによるReference Viewerへの参照表示


さて、文字列を使っているとどこで使われているかを判別したくなると思いますが、冒頭でGameplayTagやDataTableの項目で説明しましたReference Viewerへの参照表示機能を、任意のオブジェクトが持つFNameプロパティに対しても設定できるようにします。

この機能はSearchableNameというメタデータを、アセットがロードやセーブされるタイミングで呼び出されるSerialize関数で設定することで実装できます。
AssetRegistry等から高速で検索できるように、アセットそのものに依存しない設計となっているため、アセットとなるObject本体が参照を持つのではなく、ファイルそのものとのやり取りのようなイメージで作られています。

故にSearchableNameを仕込めるタイミングが限られていたりするので外から制御するような実装を行うために少しトリッキーな実装をします。

まず、アセットへのSeachableNameを付与するためのクラスを作成します。

Injectorはアセットが保存される前に、そのアセットの中にドロップダウンによって設定された値が変数やBlueprintノードピンにあるかを検証し、設定されている場合はそのアセットを親としてアタッチし、保存プロセスを一緒にたどります。そしてSerializeがInjectorにも実行され、その段階でSearchableNameを付与します。

なので、アセット保存前のデリゲートにアクセスするためにモジュールクラスにデリゲートのバインドを追加してInjectorの付与処理を書きます。

Packageに対して保存可能なオブジェクトとしてぶら下げるという若干無理やり感な実装ではありますが、ひとまずこれで挙動を見てみます。

GameplayTagやDataTableと同じようにReference Viewerに参照を表示できました。

ついでに保存時のイベントを使用しているので、Validateを行えるようにし検証結果によってMessageLogウィンドウに任意のメッセージを表示できるようにもしました。

これならいちいちValidatorを書かなくても自動で値のチェックをしてくれるのでとても便利ですね。

ちなみに、SearchableNameで任意のプロパティがどこで使われているかをすべて網羅して確認するには少し工夫することでエディタ機能のみで一覧化できます。

①ドロップダウン用のデータアセットをReference Viewerで表示。

②左側にある参照を全選択

③右クリック→Re-Center Graphを押す

③ピン/変数の名前やその値を検索ボックスに入力

④使われている値一覧が表示される

さらに特定の参照を選択することで、その参照が使われているアセット一覧を表示することができます。

一覧化した状態で参照を全選択状態でコピーを行うと、アセットのパスをテキストとして一覧でコピーできるので、ドキュメントなどでリスト化する際も使えて便利かなと思います。

Step5.プロジェクトで導入しやすい形に


概ね入れたい機能は入れられたので、あとはプラグインとしての体裁を整えていくだけです。

DataAssetを任意のものを指定できるようにしたり、ValidationやSearchableNameの入れ込みをONOFFできるようにするためにConfigに公開するためのクラスを定義します。

また、Editorの起動時にDataAssetをロードしておく必要がありますが、その際EngineのInitが終わっていないとAssetのロードでLinkerErrorやWarningが出てしまうので、DataAssetのロードはFCoreDelegates::OnPostEngineInitのタイミングでロードします。

冒頭で説明したように、コンフィグ用に定義したクラスをStartupModuleで登録します。

これで、ProjectSettingsにプラグインの設定を公開できました。

Step6.使ってみる


これで大体サクッと入れられそうな機能は入れられたんじゃないかなと思いますので、ここでプラグインの運用についてサンプルケースを紹介してみたいと思います。

① ゲーム進行で使うフラグの管理

ストーリーのあるゲームでは、ストーリーの進行状況に応じた状態を記録するために「フラグ」と呼ばれる値の束を扱うことがよくあります。

フラグは実装的には型(boolint等)や初期値、その他使う側がわかりやすいように説明文等を設定できるようにしたり、手軽に追加・編集を行えるようにDataTableで実装されることがよくあるのかなと思います。

また、Scenario用やGimmick用等複数に分けて管理することもあるんじゃないかなと思います。

そんな場合においては今回のプラグインが役に立つのではないかなと思います。

UKACND_DropdownProviderBaseを継承し、DataTableからドロップダウンの値やメタ情報の要素をGetNamelist関数をオーバーライドして作成します。
ターゲットのPin名と変数名はそれぞれFlagIDとしています

これで、BPやC++で変数や関数ピンでFlagIDと名前をつけるだけで自動的にドロップダウンが作成され、設定された値はメタ情報とともに表示されます。また、保存時にSearchableNameが付与されるのでどこで使用されているかがわかるようになります。

② ActorのTags

アクタークラスには、汎用的にメタデータを仕込む場所としてTagsというプロパティが設けられています。このTagsはNameの配列なので、このプラグインの機能でドロップダウン化することができます。

流石に全アクター共通でドロップダウンを作成するのは運用しづらいので、特定のクラスのアクターのみにフィルタします。

これをDataAssetに登録することで、BP_SampleActorTagsプロパティをドロップダウン化できました。

こんな感じで、いろんな使い方ができるかなと思います。

まとめ

長い記事になってしまいましたが、概ね書きたいことはかけたかなと思います。

ブログでは書ききれていない機能等も含め、最終的なプラグインの構成は以下のようになっています。

このブログで紹介したプラグインの完成品は、GitHubにて公開しておりますので中身が気になる方はご自由にダウンロード・改変等してください。

※一応試験運用しているプロジェクトもありますが、ブログ用にサクッと作ったものなので問題等あれば教えてもらえると助かります。マッテルヨ>🐟️

このプラグインのいいところは、既存の実装を全く変更することなく今プロジェクト使っているプロパティをドロップダウン化できちゃいます。

似た実装で、GameplayTagDataTableRowHandleのような構造体を使って独自のカスタマイズを実装するような方法もありますが、独自に作るとなるとどうしても構造体依存で実装されていって毎回ひと手間加わってしまったりしますが、今回の設計であればそのようなクセはまったくなくUnreal Engine標準の文字機構をそのまま使用できます。

また、エディタオンリーなものしかないので、パッケージに影響を与えないのも良い点かなと思います。

ただ、多少無理やりな部分があったり、エンジン内部の都合でしかたない実装があるので、このあたりは今後Unreal Engineのアップデートで改善できるようになってたらいいな…と思ってます。

最後に

Unreal Engineは日々進化をし続けており、未来ではこんなつまらないことにいちいち悩むようなことはなくなっているかもしれません。

とはいえ、人間である以上何かしらミスをしたりしてしまうことはほぼ確実にありますので、なるべくは快適な開発ができるに越したことはないかなと思います。

開発者が安心して開発ができるような気配りは、心にゆとりを持てるようにもなるのでより良いUnreal Engineライフを送るためにも様々な配慮ができると良いかなと個人的には思っております。

些末な記事で恐縮ですが、みなさんの開発で少しでも役に立ちましたら嬉しい限りです。

明日は@UnPySideさんの「World PartitionでLevelInstance+DataLayerを使ってみた」です。ワクワクですね!

毎度些末な記事で恐縮ですが、以上となります。

※この記事で使用しているUnrealのVersionは5.6.1です。

※この記事のサンプルプロジェクトはありません。
代わりに、作成したプラグインはそのままGitHubにて公開しております。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です