33.5K Views
May 06, 24
スライド概要
講演者:キンアジ
「Unreal Engine Meetup Connect - Vol.2 - UETipsLT編」の講演資料です。
アーカイブ動画:
https://www.youtube.com/live/0U4n3Kl7HPI?si=8eM5Km5PNpo2moGT
イベントページ:
https://leon-gameworks.connpass.com/event/314627/
Unreal Engine をメインとするゲーム会社、株式会社Leon Gameworks のアカウントです。
UE5版 EditorUtilityWidget についてのあれこれ キンアジ
はじめに • Editor Utility WidgetのことをEUWと省略しています。 • UE5.3までの情報が記載されております。 • UnrealEditorのキャプチャーはすべて英語になっております。 • 本資料は後日公開されます。 スライド内のリンクを参照したい方は公開後にご確認ください。
自己紹介 • 名前:キンアジ • 所属: 株式会社アンナプルナ →正社員(UEつよつよ会社) 株式会社GINO →取締役COO(内製ゲーム作ってるイケイケ会社) その他 →数社とお仕事してる(主にUEで非ゲームの仕事) →とりあえずなんか色々やっております • UE歴8年ぐらい(たぶん) • 趣味:主に釣りをしている ・ ヨロシク~!
目次 1. EditorUtilityWidget(EUW)って? 2. UE5のアップデートTips 3. その他EUWTips
1.EditorUtilityWidget (EUW)って?
EditorUtilityWidget(EUW)って? 「UMG」を使用してエディター拡張ツールを作る事ができる機能です。 ゲーム上で使うUIと同じ要領で作成することができます。 ↓みたいなツールを作れる
特徴は? • ブループリントやPythonで拡張を使って作ることが可能! • 豊富に提供されているEditorUtilityなブループリントノードを使える! • Slateを触る必要はない! • UMGと同じ要領で作成できるのでレイアウトも直感的に作成できる! 誰でも気軽にツールを作れる!
EUWに関するドキュメント • 公式ドキュメント: エディタ ユーティリティ ウィジェット • わかりやすく説明してる記事: UE5:Editor Utilityを活用したツール制作術 第1回(CGWorld:とんこつ氏&キンアジより) • 基礎は大体書いてある(古いけど): 【UE4】Editor Utility Widgetについてのあれこれ(アンナプルナより) • EUWに関する記事一覧: Unreal Engine Editor Utility Widget | エディタ拡張(UE5攻略リンク様より) • 過去に公開したEUWTips: EditorUtilityWidgetPetitDeepdive(Docswell:キンアジより) ※以降のTipsはこれらの記事の内容をある程度読むことでより理解が深まるかと思います
2.UE5のEUW アップデートTips
ObjectMixer(UE5.2~) UE5.2から、EUWにてObjectMixerというWidgetが使えるようになりました。 独自のクラスフィルタリングやアクターが持つプロパティをObjectMixerウィジェット上に 直接表示させるようなカスタマイズされたOutlinerを独自に作成することができます。 ※UE5.2だと意図通りに動かない機能があるため、UE5.3~使うのがオススメです。 詳しい説明:【UE5】BPだけで独自のOutlinerを作っちゃおう!【★★☆~★★★☆】(キンアジより) ObjectMixerを使用したプラグイン:KAJiraUtility(Github:キンアジより) ObjectMixer
EUWの複数起動(UE5.2~) UE5.1までは、EUWのウィンドウは同じクラスで複数立ち上げることをC++を用いなければ できませんでしたが、UE5.2からは UEditorUtilitySubsystem::SpawnAndRegisterTabWithIDにて複数立ち上げること ができます。
EUWの複数起動(UE5.2~) ちなみに、UE5.2から追加されたEUWのEditorにあるRunUtilityWidgetボタンと 右クリメニューのRunEditorUtilityWidget&SpawnAndRegisterTabノードは別枠 (Asset名から自動的に作られるID)なので、IDを指定した起動方法でなくても2つまでは同じクラスの Windowを起動できます。 IDの規則 /Game/EUW_TestWidgetの場合 RunUtilityWidget IDはアセット名(EUW_TestWidget) RunEditorUtilityWidgetやSpawnAndRegisterTabの場合 IDはNone (内部的なID:/Game/EUW_TestWidget.EUW_TestWidgetEUW_TestWidget) (内部的なID:/Game/EUW_TestWidget.EUW_TestWidget_ActiveTab) Asset名を使って SpawnAndRegisterTabWithIDで起動させると、 RunUtilityWidgetButtonで起動したことになる。
変数表示の自動化(UE5.2~) UE5.2から、ProjectSettings→Engine→UserInterfaceにある AuthorizeAutomaticWidgetVariableCreationというプロパティが追加されています。 UE5.1までは、ButtonなどのWidgetは新しくDesignerウィンドウ上で追加した場合に自動的 にIsVariableのチェックが有効になっていましたが、UE5.2からは AuthorizeAutomaticWidgetVariableCreationを有効にしないと自動的にチェックが入ら ないようになっています。 ※通常のWidgetBlueprintと共通の設定です。 チェックを入れる Button等のWidget追加時に自動的に変数化されるように
EUWのデバッグ(UE5.2~) UE5.2から、EUWがプレイ中でなくてもブレークポイントによるブループリントのデバッグができるように なりました。これに伴い、以下2つのプロパティがEditorUtilityWidgetBlueprintに追加されています。 参考:[UE5] UE5.2以降、EditorUtilityWidgetがエディタ実行中に動作しなくなった時に確認すべきこと ( Is Enabled in PIE プロパティ ) (おかず様より) ・IsEnabledinPIE ・IsEnabledinDebugging
IsEnabledinPIE IsEnabledinPIEを有効にすることで、PIE中でもEUWが動作するようになります。 (デフォルトで無効) ※UE5.1まではデフォルトでPIEでも使用できたので、バージョンアップに伴いPIEで使用ができなくなった 場合はこの設定を確認してください。 動画リンク
IsEnabledinDebugging ブレークポイントで動作が停止している場合にデフォルトだとEUWは動作しなくなります。 →IsEnabledinDebuggingを有効にすることで、ブレークポイントで動作が停止している場合でも EUWが動作するようになります。 ※プレイ中、非プレイ中のどちらにも適用されます。 動画リンク
EUW用のWidget(UE5.3~) UE5.3から、EUW用にEditorUtilityButtonやEditorUtilityEditableText等の汎用的なWidget クラスが用意されました。 機能としてのアップデートではなく、Runtime用のWidgetクラスと分けることで、UE5.2までは Runtime用のWidgetクラスにEUW用のStyle(見た目)が記載されていたのがなくなり 内部的な実装の切り分けが行いやすいような設計になりました。 EditorUtilityなWidget一覧 基本的には見た目が違うだけ (↓Buttonの例) Button EditorUtilityButton EUW用Widgetクラスの記載場所
TemplateSelector(UE5.3~) UE5.2までは、デフォルトで新規にEUWを作成する際はRootとなるパネルは必ず CanvasPanelとなっていましたが(WidgetBlueprintと共通の設定が使われていた)UE5.3から デフォルトでRootとなるWidgetを選択できるTemplateSelectorが EditorUtilityWidgetBlueprint用にも実装されています。
TemplateSelector(UE5.3~) なお、TemplateSelectorで表示されるWidgetClassは ProjectSettings→Editor→EditorUtilityWidgets(Team)→WidgetClassesToHide による表示しないWidgetClassの影響を受けません。 特に問題があるわけではないのですが、意図せずRuntime用のWidgetを使用してしまう 可能性がある点に注意しましょう。 EUWで表示しない(使わない)Widget用として明示するための設定 フィルタされない
TemplateSelector(UE5.3~)
Templateを選ばせずにUE5.2以前と同じようにする方法
→以下のようなコードをProject等のStartupModule()に記載し、
「UseWidgetTemplateSelector」をFalseにします。
#include "EditorUtilityWidgetProjectSettings.h"
#include "PropertyEditorModule.h"
※build.csのPrivateDependencyModuleNames
に”blutility”を追加が必要
FPropertyEditorModule& PropertyModule =
FModuleManager::LoadModuleChecked<FPropertyEditorModule>("PropertyEditor");
PropertyModule.UnregisterCustomClassLayout(
UEditorUtilityWidgetProjectSettings::StaticClass()->GetFName());
作成時のRootWidgetとなる
WidgetBlueprintからの変換(UE5.3~) BPで変換も可能 UE5.3から、通常のWidgetBlueprintを EditorUtilityWidgetBlueprintに変換することが できるようになりました。 (WidgetBlueprintを右クリック→AssetActions →ConverttoEditorUtilityWidgetから変換可能)
ちなみに
このようなコードをプロジェクトのソース等に
関数として書いて実行すれば、
EditorUtilityWidgetBlueprintから通常の
WidgetBlueprintへ逆変換できます。
#include "EditorUtilityWidgetProjectSettings.h"
#include "PropertyEditorModule.h"
void UKAEditorUtilityBlueprintLibrary::ConvertToEUWBPToWBP(UEditorUtilityWidgetBlueprint* EUWBP)
{
if (!EUWBP || !EUWBP->IsA<UEditorUtilityWidgetBlueprint>())
{
return;
}
FName BPName = EUWBP->GetFName();
UObject* Outer = EUWBP->GetOuter();
EObjectFlags Flags = EUWBP->GetFlags();
TArray<struct FEditedDocumentInfo> OriginalEditedDocuments = EUWBP->LastEditedDocuments;
EUWBP->Rename(nullptr, GetTransientPackage(), REN_DontCreateRedirectors | REN_SkipGeneratedClasses |
REN_ForceNoResetLoaders);
TArray<UObject*> Children;
GetObjectsWithOuter(EUWBP, Children, false);
UWidgetBlueprint* WBP = NewObject<UWidgetBlueprint>(Outer, BPName, Flags);
if (WBP->WidgetTree)
{
WBP->WidgetTree->Rename(nullptr, GetTransientPackage(), REN_ForceNoResetLoaders | REN_DontCreateRedirectors);
WBP->WidgetTree = EUWBP->WidgetTree;
}
for (UObject* Child : Children)
{
Child->Rename(nullptr, WBP, REN_DontCreateRedirectors | REN_SkipGeneratedClasses | REN_ForceNoResetLoaders);
}
UEngine::FCopyPropertiesForUnrelatedObjectsParams Params;
Params.bPerformDuplication = true;
Params.bNotifyObjectReplacement = false;
Params.bPreserveRootComponent = false;
UEngine::CopyPropertiesForUnrelatedObjects(EUWBP, WBP);
WBP->LastEditedDocuments = OriginalEditedDocuments;
(UEditorUtilityLibrary::ConvertToEditorUtilityWidget
を少し改良しただけ)
WBP->GeneratedClass->ClassGeneratedBy = WBP;
check(WBP->GeneratedClass == nullptr || WBP->GeneratedClass->GetOuter() == WBP->GetOuter());
TMap<UObject*, UObject*> OldToNew;
OldToNew.Add(EUWBP, WBP);
TArray<UObject*> Targets = { EUWBP, GetTransientPackage() };
FArchiveReplaceObjectRef<UObject> ReplaceReferencesInRoot(WBP, OldToNew);
FFindReferencersArchive Archive(WBP, Targets);
check(Archive.GetReferenceCount(EUWBP) == 0);
Children.Reset();
GetObjectsWithOuter(WBP->GetOuter(), Children);
for (UObject* Child : Children)
{
FArchiveReplaceObjectRef<UObject> ReplaceReferences(Child, OldToNew);
FFindReferencersArchive Archive2(Child, Targets);
check(Archive2.GetReferenceCount(EUWBP) == 0);
}
}
※build.csのPrivateDependencyModuleNamesに
”Blutility”を追加が必要
Assetのサムネ表示用Widget(UE5.3~) UE5.3から、アセットのサムネイルをEUW上で表示できるAssetThumbnailWidgetが 追加されました。 使用例:UE5:Editor Utilityを活用したツール制作術 第2回:アセットのプロパティを設定・取得するツール(CGWorld:とんこつ氏より) AssetThumbnailWidget Blueprintで表示するAssetを設定 プロパティで見た目をどう表示するか設定
Assetのサムネ表示用Widget(UE5.3~) ThumbnailLabelの色はHintColorAndOpacityで設定されており、デフォルトでOpacityが0に なっていて見えないので、Asset名等を表示したい場合はHintColorAndOpacityの設定を 見直してみてください。 値を調整
3.その他EUWTips
簡単に実装できるキーイベント
EditorUtilityWidgetで機能するショートカットキーのようなものを、FUICommandInfo等の
C++実装をする必要なく簡単に実装できます。
EUWのBPでOnKeyDown関数をオーバーライド
※一度EUW内の何かしらのWidgetをクリックしないと有効化されない。
自動的に有効にしたい場合は、EventConstruct等で以下のようなコードを
BPに公開し実行することで可能。
#include "Framework/Application/SlateApplication.h"
#include "EditorUtilityWidget.h"
void UKAEditorUtilityBlueprintLibrary::SetEUWFocus(UEditorUtilityWidget* FocusWidget)
{
if (FocusWidget)
{FSlateApplication::Get().SetAllUserFocus(FocusWidget->TakeWidget(), EFocusCause::SetDirectly);}
}
ReturnにHandled関数を接続することで
他Windowのショートカットキー暴発の防止
※build.csのPrivateDependencyModuleNamesに
”Slate”,”SlateCore”,Blutilityを追加が必要
各種設定の保存
EUWを使用していると、DetailViews等で使用したプロパティを保存したくなる場合が多いですが、
プレイ中でなくてもSaveGameObjectは使用できるため、エディター用のデータを保存する際に有効です。
または、変数のConfigVariableを有効にして、SavedのEditor.iniに保存するのも手っ取り早いです。
SaveGameObjectでセーブ・ロード
変数をConfigに公開
※Configの場合はEUWインスタンス等の保存するインスタンスに
対してSaveConfigをする必要がある↓はSaveConfigをBPに公開するためのコード例
void UKAEditorUtilityBlueprintLibrary::SaveConfig(UObject* InObject)
{
if (InObject) { InObject->SaveConfig(); }
}
不必要に編集されるPersistentLevel
EUWが起動する事によって生成されるEUWインスタンスは
「Editor上で開いているWorld」に所属します(これによって、GetAllActorsOfClass等で
エディタ上のアクター等へのアクセスが容易にできるようになっています)
→詳しくは【UE4】EditorUtility上のWorldContextについて【★★☆】(キンアジより)
通常の起動方法で起動したEUWのインスタンスはTransient(一時的)フラグが自動的に
付与されるため、内部の変数をDetailViews等で変更しても他に影響を与えませんが、EUWを
Outerとして生成したObjectやCreateWidget等で生成したサブウィジェットとしての
EUW等には明示的にTransientフラグをつけないと、そのオブジェクトに変更が加わった場合に
PersistentLevelが変更された扱いになってしまいます。
SubWidgetのプロパティ変更
//UEditorUtilityWidgetBlueprint::CreateUtilityWidgetから抜粋(EUWのOuter(親)はEditor上のWorld)
if (UWorld* World = GEditor->GetEditorWorldContext().World())
{
CreatedUMGWidget = CreateWidget<UEditorUtilityWidget>(World, WidgetClass);
不必要に編集されるPersistentLevel 解決方法として、WidgetやObject等のインスタンスを生成する場合は、他に影響を与えない TransientなObjectをOuterにするか、Transientフラグを追加するようにしましょう。 →TransientなOuterにする場合、ブループリントではConstructObjectFromClassノードで Outerに「EditorUtilitySubsystem」あたりを接続しておくと無難です。 →インスタンスをTransientにするにはC++定義のクラスであればUCLASSの指定子 にTransientを追加すればよいですが、Blueprintクラスの場合はSetFlags関数を ブループリントに公開し実行することで、Transientなフラグを設定してあげると解決できます。 OuterをEditorUtilitySubsystemにする Transient指定子 UCLASS(Transient) class UEditorUtilityTestObject : public UEditorUtilityObject SetFlagsでTransientフラグの追加(BP関数への公開例) void UEditorUtilityTestLibrary::AddTransientFlag(UObject* InObject) { if (InObject) { InObject->SetFlags(RF_Transient); } }
レベルを開くと再生成されるEUW ULevelEditorSubsystem::LoadLevel関数等で別レベルを開くようなツールをEUWで作成 することがある場合、前ページで説明した所属ワールドの都合によりレベルが開かれたタイミング で新しいEUWのインスタンスが生成され、開いているEUWのウィンドウで表示されているものは 自動的に置き換えられてしまうので注意が必要です。 また、レベル移動前の古いEUWインスタンスは、どのWorldにも所属しなくなるため、 WorldContextを使用したLatentノード(例えばDelayノード)のCallbackが返ってこなくなり ます。レベルを開いた後はWorldContextに依存しないノードで処理をする等の工夫が必要です。 レベルを開く EUWインスタンスが作り直される (DetailViews等で表示しているPropertyがリセットされる) レベル移動 WorldContextに依存しないノードを使う
コンテンツブラウザで実行されるPreConstructイベント 通常のUserWidgetでもそうですが、EventPreConstructノードはWidgetインスタンスを生成 する前からDesignerウィンドウ上等でも実行されるため、非常に便利なノードとなっています。 しかし、 EventPreConstructノードはコンテンツブラウザ上でWidgetClassにマウスカーソル をあわせたタイミングでも毎フレーム実行されてまいます。 また、 EventPreConstructで値のセットやレベルの移動等を行ってしまうと意図せず 実行されてしまうので、そのような処理はConstructイベントに書くようにしましょう。 コンテンツブラウザ上で毎フレーム実行されている 再生成はFLevelEditorModuleのOnMapChanged()のタイミング 再生成処理自体はUEditorUtilityWidgetBlueprint::ChangeTabWorld を参照 動画リンク
メニューに直接EUWを追加する
エディター上部のToolbarや右クリックメニューに対してEUWを直接表示することができます。
※EUWである必要はないですが、Editor拡張用としてEUWが一番適しているため
//CustomWidgetMeny.h
//ModuleはToolMenus,Blutility,UMG,Slate,SlateCoreを追加
#pragma once
#include "CoreMinimal.h"
#include "EditorUtilityObject.h"
#include "EditorUtilityWidget.h"
#include "CustomWidgetMeny.generated.h"
UCLASS()
class KAEDITORUTILITYPLUGIN_API UCustomWidgetMeny : public UEditorUtilityObject
{
GENERATED_BODY()
public:
UPROPERTY()
TSubclassOf<UEditorUtilityWidget> WidgetClass;
//Widget用MenuEntryをToolMenusに追加
UFUNCTION(BlueprintCallable, Category = "KAEditorUtility")
void AddEUWMenyEntry(FName InOwner, FName InName, TSubclassOf<UEditorUtilityWidget>
InWidgetClass);
TSharedRef<SWidget> GetCustomMenuWidget(
const FToolMenuContext& ToolMenuContext,
const FToolMenuCustomWidgetContext& CustomWidgetContext,
FName MenuName);
};
※build.csのPrivateDependencyModuleNamesに
“ToolMenus”,”Blutility”,”UMG”,”Slate”,”SlateCore”を追加が必要
//CustomWidgetMeny.cpp
#include "CustomWidgetMeny.h"
#include "Subsystems/UnrealEditorSubsystem.h"
void UCustomWidgetMeny::AddEUWMenyEntry(
FName InOwner,
FName InName,
TSubclassOf<UEditorUtilityWidget> InWidgetClass)
{
UToolMenus* ToolMenus = UToolMenus::Get();
FToolMenuEntry Entry(InOwner, InName, EMultiBlockType::MenuEntry);
UToolMenu* OwnerMenu = ToolMenus->FindMenu(InOwner);
Entry.MakeCustomWidget.BindUObject(this, &UCustomWidgetMeny::GetCustomMenuWidget, InName);
Entry.Type = EMultiBlockType::Widget;
Entry.WidgetData.bNoIndent = true;
Entry.WidgetData.bNoPadding = true;
WidgetClass = InWidgetClass;
OwnerMenu->AddMenuEntry(NAME_None, Entry);
ToolMenus->RefreshAllWidgets();
}
TSharedRef<SWidget> UCustomWidgetMeny::GetCustomMenuWidget(
const FToolMenuContext& ToolMenuContext,
const FToolMenuCustomWidgetContext& CustomWidgetContext,
FName MenuName)
{
if (!WidgetClass)
{
return SNew(STextBlock).Text(FText::FromString("Invalid Menu Class"));
}
UUserWidget* MakeWidget = CreateWidget<UUserWidget>(
GEditor->GetEditorSubsystem<UUnrealEditorSubsystem>()->GetEditorWorld(), WidgetClass);
if (!IsValid(MakeWidget))
{
return SNew(STextBlock).Text(FText::FromString("Menu Widget Creation Failed"));
}
return MakeWidget->TakeWidget();
}
メニューに直接EUWを追加する 前ページのソースを実装した後、以下のようなBlueprintを実行することでメニューにEUWを表示できます。 簡単にエディター上にメニューを登録するプラグイン(試作版)を公開しておりますので、よければ御覧ください。 (プラグイン内にEUWのメニュー化機能もあり) →KAEditorUtilityPlugin(Github:キンアジより) ※生成したObjectは、UPROPERTYをつけた変数内で保持しなければ 別レベルを開いた時等のGCでObjectが削除されてしまい、その後に メニューを開こうとするとクラッシュするので注意が必要です。 EditorSubsystem等のEditor上で常駐しているインスタンス内に変数 として保持するのをオススメします。
EUWを閉じる際の確認処理
EUWのタブを閉じる際に、閉じるかどうかを制御する汎用的な仕組みの実装を行うことができます。
たとえば「とじますか?」のようなポップアップメッセージを出して、キャンセルできるような実装を行いたいときに
とても便利です。
※以下のソースはコアな実装しか記載していないため、これだけでは再現できません(複数クラスにまたがったりして収まらないので)
詳しくはKAEditorUtilityPlugin(Github:キンアジより)にプラグイン化されていますので御覧ください
bool UKA_EditorUtilityAssistSubsystem::BindCanCloseEUWTab(
class UEditorUtilityWidgetBlueprint* EUWBP,
FName TabID,
FOnGetCanCloseEUWTab CanCloseTabDelegate)
{
FName GeneratedTabID = GenerateEUWTabID(EUWBP, TabID);
if (TSharedPtr<SDockTab> DockTab = GetEUWDockTab(EUWBP, TabID))
{
UKA_EUWTabManagerProxy* TabManagerProxy = nullptr;
if (TabManagerProxyList.Contains(GeneratedTabID))
{
TabManagerProxy = TabManagerProxyList[GeneratedTabID];
}
if(!TabManagerProxy)
{
TabManagerProxy = NewObject<UKA_EUWTabManagerProxy>(this);
TabManagerProxyList.Add(GeneratedTabID, TabManagerProxy);
}
if (FAssetRegistryModule* AssetRegistryModule =
FModuleManager::GetModulePtr<FAssetRegistryModule>(TEXT("AssetRegistry")))
{
AssetRegistryModule->Get().OnAssetRemoved().AddUObject(
TabManagerProxy,
&UKA_EUWTabManagerProxy::OnAssetRemoved);
}
TabManagerProxy->GetCanCloseEUWTabDelegate = CanCloseTabDelegate;
TabManagerProxy->TabID = GeneratedTabID;
TabManagerProxy->EUWBPPath = FSoftObjectPath(EUWBP);
DockTab.Get()->SetCanCloseTab(
SDockTab::FCanCloseTab::CreateUObject(TabManagerProxy,
&UKA_EUWTabManagerProxy::GetCanCloseScriptEditorTab));
return true;
}
return false;
}
おわりに
おわりに EUWの様々なTipsを詰め込んでみましたが、いかがだったでしょうか? みなさんも是非EUWを使ってよりよい開発を目指してみてください。 不明点等あれば、X(Twitter)等で質問を受け付けておりますのでご気軽 にお声掛けください。 なお、お仕事等のお話も受け付けておりますので何卒よろしくお願いします。 X(Twitter):@kinnaji_blog ブログ:kinnaji.com Github:kinnajichan Mail:[email protected]
ありがとうございました! ・ まったね~!