ぼっちプログラマのメモ

UE4とかUE5とかについて書いたり書かなかったり。

【UE5】User ToolBoxのタブがエディタ起動時に表示・復元されない問題の修正方法 in UE5.3

はじめに

UE5.2からUser ToolBoxという機能が追加され、EditorUtilityWidgetを作らなくてもサクッと簡易的なエディタ拡張ができるようになりました!

User ToolBox(UTB)とは?
User ToolBox 5.2 | Tutorial

しかし、User ToolBoxで作った画面(User ToolBox Tab)がエディタ起動時に出てこない(前回の状態を再現しない)問題があります(以下はタブ復元に失敗していることを示すログ出力)。

 The tab "DefaultUserToolBox" attempted to spawn in layout 'LevelEditor_Layout_v1.8' 
but failed for some reason. It will not be displayed.

そのため、エディタ起動のたびにクリック数回してUser ToolBox Tabを出す必要があります…面倒です…面倒です!

ということで、エンジン改造して解決してみました。
以降は追加・変更したコードと補足についてです。そんなのいいから早く!という方は↓の .patch をご使用くださいまし
Snippets/Batchs/FixRestoreUserToolBoxTab.patch at main · pafuhana1213/Snippets · GitHub

なお、本記事を書いた UE5.3環境では User ToolBoxはExperimental機能なため、今後色々変わる可能性は十分にある点についてご注意ください

エンジン改造内容 in UE5.3

Engine/Plugins/Experimental/UserToolBoxCore/Source/UserToolBoxCore/Private/UserToolBoxSubsystem.cpp

TArray<FAssetData> UUserToolboxSubsystem::GetAvailableTabList()
{
...
	AssetRegistry.GetAssets(Filter,AssetDatas);
	// Begin Change @pafuhana1213
	// TArray<FAssetData> AvailableTabList;
	TArray<FAssetData> TabList;
	// End Change @pafuhana1213
	for (FAssetData AssetData:AssetDatas)
	{
		UUserToolBoxBaseTab* Tab=Cast<UUserToolBoxBaseTab>(AssetData.GetAsset());
		if (Tab==nullptr)
		{
			UE_LOG(LogTemp,Warning,TEXT("Unable to load %s"),*AssetData.PackageName.ToString())
			continue;
		}
		// Begin Change @pafuhana1213
		// AvailableTabList.Add(AssetData);
		TabList.Add(AssetData);
		// End Change @pafuhana1213
	}
	// Begin Change @pafuhana1213
	// return AvailableTabList;
	return TabList;
	// End Change @pafuhana1213
}
...
void UUserToolboxSubsystem::Initialize(FSubsystemCollectionBase& Collection)
{
...
	FUTBEditorCommands::Register();

	// Begin Change @pafuhana1213
	FLevelEditorModule& LevelEditorModule = FModuleManager::LoadModuleChecked<FLevelEditorModule>(TEXT("LevelEditor"));
	LevelEditorModule.OnTabManagerChanged().AddUObject(this, &UUserToolboxSubsystem::ReinitializeUIs);
	// End Change @pafuhana1213
}

// Begin Change @pafuhana1213
TSharedRef<SDockTab> UUserToolboxSubsystem::CreateTab(const FSpawnTabArgs& SpawnTabArgs)
{
	const FAssetData* TabAssetData =
		AvailableTabList.FindByPredicate([SpawnTabArgs](const FAssetData& Tab)
		{
			return Tab.GetTagValueRef<FString>("Name") == SpawnTabArgs.GetTabId().ToString();
		});
	
	return SNew(SDockTab)
			.TabRole(NomadTab)
			[
				GEditor->GetEditorSubsystem<UUserToolboxSubsystem>()->GenerateTabUI(*TabAssetData).ToSharedRef()
			];
}
void UUserToolboxSubsystem::ReinitializeUIs()
{
	AvailableTabList = GetAvailableTabList();
	for (const FAssetData& TabAssetData : AvailableTabList)
	{
		FString TabName=TabAssetData.GetTagValueRef<FString>("Name");
		if (RegisteredDrawer.Contains(FName(TabName)))
		{
			continue;	
		}
		
		if (TabAssetData.GetTagValueRef<bool>("bIsVisibleInDrawer"))
		{
			FLevelEditorModule& LevelEditorModule = FModuleManager::GetModuleChecked<FLevelEditorModule>(TEXT("LevelEditor"));
			if (const TSharedPtr<FTabManager> LevelEditorTabManager = LevelEditorModule.GetLevelEditorTabManager())
			{
				LevelEditorTabManager->RegisterTabSpawner(FName(TabName), FOnSpawnTab::CreateUObject(this, &UUserToolboxSubsystem::CreateTab));
			}
		}
	}
}
// End Change @pafuhana1213
...

Engine/Plugins/Experimental/UserToolBoxCore/Source/UserToolBoxCore/Public/UserToolBoxSubsystem.h

DECLARE_MULTICAST_DELEGATE_OneParam(FOnTabChanged, UUserToolBoxBaseTab*)
UCLASS(BlueprintType)
class USERTOOLBOXCORE_API UUserToolboxSubsystem : public UEditorSubsystem
{
...
	/** Implement this for initialization of instances of the system */
	virtual void Initialize(FSubsystemCollectionBase& Collection) override;

	// Begin Change @pafuhana1213
	TSharedRef<SDockTab> CreateTab(const FSpawnTabArgs& SpawnTabArgs);
	void ReinitializeUIs();
	// End Change @pafuhana1213
	
...

	// Begin Change @pafuhana1213
	TArray<FAssetData> AvailableTabList;
	// End Change @pafuhana1213
};

何が原因で起きていて、どう解決したか

冒頭のログを出している箇所を順に追っていくとわかるのですが、原因はエディタ起動時に前回の状況を復元するための処理

SLevelEditor::RestoreContentArea 

にて呼ばれている

FTabManager::SpawnTab

にて、レベルエディタのTabManager に UserToolBoxのタブ情報が登録されていないためです。その結果、タブの生成・復元処理が失敗してログが出力されています。そのため、これを解決するために、FTabManager::SpawnTabが呼ばれる前にタブ情報を登録する必要があります。

実装は FBlutilityModule のStartupModule と ReinitializeUIs をパク参考にし、SLevelEditor::RestoreContentArea 冒頭に呼ばれる LevelEditorModule.OnTabManagerChanged() にて LevelEditorTabManager->RegisterTabSpawner を使ってタブ情報を登録しています。

とりあえずこれからプルリクエストを投げてみようと思います。反映されたらいいなぁ…
2024/2時点の最新コードでも不具合が再現したのでプルリクしました!
https://github.com/EpicGames/UnrealEngine/pull/11460

おしまい