【UE5】新建Editor Standalone Window插件,之前(或当前)创建插件的按钮消失(被顶掉/占用)的问题

问题描述

当你在同一个项目里先后新建两个Editor Standalone Window的插件模板:

然后你会发现,菜单栏里并不会出现两个插件入口。先前创建的插件入口消失了。后一个插件把前一个的入口按钮给顶掉了:

之前:

新建一个TestToolBarWiondow2后:

此时发现TestToolBarWindow的按钮消失了(但是该插件并没有被我禁用)

或者新建的按钮消失了(如果没有指定依赖顺序和配置加载阶段之类的,那么插件会按照字典序先后加载。最终留下的按钮是字典序最后面的那个。。。)

原因分析

先来看下这个插件模板的按钮注册大致流程,以TestToolBarWindow 为例,注册菜单的核心逻辑在TestToolBarWindow.cpp里的这个函数:

cpp 复制代码
void FTestToolbarWindowModule::RegisterMenus()
{
	// Owner will be used for cleanup in call to UToolMenus::UnregisterOwner
	FToolMenuOwnerScoped OwnerScoped(this);

	{
		UToolMenu* Menu = UToolMenus::Get()->ExtendMenu("LevelEditor.MainMenu.Window");
		{
			FToolMenuSection& Section = Menu->FindOrAddSection("WindowLayout");
			Section.AddMenuEntryWithCommandList(FTestToolbarWindowCommands::Get().OpenPluginWindow, PluginCommands);
		}
	}

	{
		UToolMenu* ToolbarMenu = UToolMenus::Get()->ExtendMenu("LevelEditor.LevelEditorToolBar");
		{
			FToolMenuSection& Section = ToolbarMenu->FindOrAddSection("Settings");
			{
				FToolMenuEntry& Entry = Section.AddEntry(FToolMenuEntry::InitToolBarButton(FTestToolbarWindowCommands::Get().OpenPluginWindow));
				Entry.SetCommandList(PluginCommands);
			}
		}
	}
}

它这边直接写死了获取的"LevelEditor.MainMenu.Window "这个Menu 下的名为"WindowLayout "的Section

cpp 复制代码
FToolMenuSection& Section = Menu->FindOrAddSection("WindowLayout");

所以可以得知,我们创建的两个插件的按钮都是默认写到同一个Menu的同一个Section下的。

然而:

一个ToolMenu下会有多个Section(可以当成是逻辑分组),然后每个Section里会有Entry(可以直观理解为按钮入口);当你在同一个Section里Add一个已经有同名Entry的Entry,那么会把之前的同名的Entry顶替掉。

而我们注册的按钮的Entry的名字是由啥决定的呢?

点进AddMenuEntryWithCommandList ,一路查看定义(这边就不贴全代码了),会发现Entry使用的名字,如果没有额外指定的话,实际上就是这个const TSharedPtr< const FUICommandInfo >& InCommandCommandName字段:

cpp 复制代码
FToolMenuEntry& FToolMenuSection::AddMenuEntryWithCommandList(const TSharedPtr< const FUICommandInfo >& InCommand, const TSharedPtr< const FUICommandList >& InCommandList, const TAttribute<FText>& InLabelOverride, const TAttribute<FText>& InToolTipOverride, const TAttribute<FSlateIcon>& InIconOverride, const FName InTutorialHighlightName, const TOptional<FName> InNameOverride)
{
	return AddEntry(FToolMenuEntry::InitMenuEntryWithCommandList(InCommand, InCommandList, InLabelOverride, InToolTipOverride, InIconOverride, InTutorialHighlightName, InNameOverride));
}

即我们的FTestToolbarWindowCommandsOpenPluginWindowCommandName字段。

然后我们来追溯下这个字段是怎么设置的。

来到TestToolbarWindowCommands.cpp这里:

cpp 复制代码
void FTestToolbarWindowCommands::RegisterCommands()
{
	UI_COMMAND(OpenPluginWindow, "TestToolbarWindow", "Bring up TestToolbarWindow window", EUserInterfaceActionType::Button, FInputChord());
}

看看UI_COMMAND宏的定义:

cpp 复制代码
#define UI_COMMAND( CommandId, FriendlyName, InDescription, CommandType, InDefaultChord, ... ) \
	MakeUICommand_InternalUseOnly( this, CommandId, TEXT(LOCTEXT_NAMESPACE), TEXT(#CommandId), TEXT(#CommandId) TEXT("_ToolTip"), "." #CommandId, TEXT(FriendlyName), TEXT(InDescription), CommandType, InDefaultChord, ## __VA_ARGS__ );

再进去一层看MakeUICommand_InternalUseOnly的定义:

cpp 复制代码
void MakeUICommand_InternalUseOnly( FBindingContext* This, TSharedPtr< FUICommandInfo >& OutCommand, const TCHAR* InSubNamespace, const TCHAR* InCommandName, const TCHAR* InCommandNameUnderscoreTooltip, const ANSICHAR* DotCommandName, const TCHAR* FriendlyName, const TCHAR* InDescription, const EUserInterfaceActionType CommandType, const FInputChord& InDefaultChord, const FInputChord& InAlternateDefaultChord)
{
	static const FString UICommandsStr(TEXT("UICommands"));
	const FString Namespace = InSubNamespace && FCString::Strlen(InSubNamespace) > 0 ? UICommandsStr + TEXT(".") + InSubNamespace : UICommandsStr;

	FUICommandInfo::MakeCommandInfo(
		This->AsShared(),
		OutCommand,
		InCommandName,
		FText::AsLocalizable_Advanced( Namespace, InCommandName, FriendlyName ),
		FText::AsLocalizable_Advanced( Namespace, InCommandNameUnderscoreTooltip, InDescription ),
		FSlateIcon( This->GetStyleSetName(), ISlateStyle::Join( This->GetContextName(), DotCommandName ) ),
		CommandType,
		InDefaultChord,
		InAlternateDefaultChord
	);
}

再进去一层看看MakeCommandInfo的定义,即可看到CommandName的赋值:

cpp 复制代码
void FUICommandInfo::MakeCommandInfo( const TSharedRef<class FBindingContext>& InContext, TSharedPtr< FUICommandInfo >& OutCommand, const FName InCommandName, const FText& InCommandLabel, const FText& InCommandDesc, const FSlateIcon& InIcon, const EUserInterfaceActionType InUserInterfaceType, const FInputChord& InDefaultChord, const FInputChord& InAlternateDefaultChord, const FName InBundle)
{
	ensureMsgf( !InCommandLabel.IsEmpty(), TEXT("Command labels cannot be empty") );

	LLM_SCOPE_BYTAG(UI_Slate);

	OutCommand = MakeShareable( new FUICommandInfo( InContext->GetContextName() ) );
	OutCommand->CommandName = InCommandName;
	OutCommand->Label = InCommandLabel;
	OutCommand->Description = InCommandDesc;
	OutCommand->Icon = InIcon;
	OutCommand->UserInterfaceType = InUserInterfaceType;
	OutCommand->DefaultChords[static_cast<uint8>(EMultipleKeyBindingIndex::Primary)] = InDefaultChord;
	OutCommand->DefaultChords[static_cast<uint8>(EMultipleKeyBindingIndex::Secondary)] = InAlternateDefaultChord;
	OutCommand->Bundle = InBundle;
	FInputBindingManager::Get().CreateInputCommand( InContext, OutCommand.ToSharedRef() );
}

那么真相已经浮出水面。 从MakeCommandInfo 回溯可以得知,一个CommandCommandName 就是UI_COMMAND 宏的第一个变量的这个变量名!比如这里:

cpp 复制代码
UI_COMMAND(OpenPluginWindow, "TestToolbarWindow", "Bring up TestToolbarWindow window", EUserInterfaceActionType::Button, FInputChord());

那这个Command的CommandNanme就是"OpenPluginWindow"

那怪不得会串号。。。。。。

解决方案

建议创建菜单插件的时候给你自己的插件建立一个独有的Section名字,来替换掉这里的"WindowLayout"

cpp 复制代码
FToolMenuSection& Section = Menu->FindOrAddSection("WindowLayout");
相关推荐
maki07721 小时前
VR大空间资料 01 —— 常用VR框架对比
android·ue5·游戏引擎·vr·虚幻·pico
maki0772 天前
虚幻版Pico大空间VR入门教程 01 ——UE5 Android打包环境4.26~5.6
android·ue5·vr·虚幻·pico·大空间
DoomGT3 天前
UE5 - C++项目基础
c++·ue5·ue4·虚幻·虚幻引擎·unreal engine
HELLOMILI3 天前
[UnrealEngine] 虚幻引擎UE5地形入门指南 | UE5地形教程(UE5 Terrain)
游戏·ue5·游戏引擎·虚幻·虚幻引擎·unreal engine
努力的小钟3 天前
UE5 GAS GameAbility源码解析 ActivateAbility
ue5
HELLOMILI4 天前
[UnrealEngine] 虚幻编辑器界面 | 虚幻界面详解 | UE5界面详解
游戏·ue5·编辑器·游戏引擎·虚幻·unreal engine
AA陈超4 天前
虚幻引擎UE5专用服务器游戏开发-32 使用Gameplay Tags阻止连招触发
c++·游戏·ue5·游戏引擎·虚幻
努力的小钟4 天前
UE5 GAS GameAbility源码解析 CanActivateAbility
ue5
危险库4 天前
【UE4/UE5】在虚幻引擎中创建控制台指令的几种方法
c++·ue5·游戏引擎·ue4·虚幻