Fork me on GitHub

编辑器 - 使用数据资产新增、配置按钮 - 心意不定的UE4随笔录

使用数据资产新增、配置按钮

成果

  1. 创建用于描述按钮行为的数据资产。

image-20220427212143656

  1. 选择行为类型,并设置参数。

image-20220427212510404

image-20220427212659766

image-20220427212714903

  1. 添加至Config,并填写其按钮的名称等描述性参数

image-20220427213048284

  1. 重启Editor 可以点击上方按钮使用配置的功能

image-20220427213413973

image-20220427213444784

image-20220427213505328

实现原理

  1. 首先添加一个插件, 这里推荐 Editor Stanalone Window 插件,可以复用部分代码

image-20220427194936370

  1. 创建 EditorAction 类,此类用于定义按钮的行为。

​ 之前的示例里使用到了我提供了两个类:

  • UEditorAction_OpenWidget : 用于打开指定的 EditorUtilityWidgetBlueprint
  • UEditorAction_RunPython : 执行指定的Python脚本

​ 创建 ButtonActionDefine 类,此类继承自DataAsset,其持有一个 EditorAction 类的字段,以使用资产来定义按钮的行为。

ActionClass.drawio

  1. 创建 UButtonToolsGlobalConfig 用作配置文件,其持有一个 FEditorButtonDefine 类实例数组,该类用于定义按钮的名称&行为等。

    ButtonUtilsConfig.drawio

    1. 将设置注册进项目设置面板,参考如下代码:
    void FButtonToolsModule::RegisterSettings()
    {
    	ISettingsModule* SettingModule = FModuleManager::GetModulePtr<ISettingsModule>("Settings");
    	if(SettingModule)
    	{
    		SettingModule->RegisterSettings("Project", "Plugins", "ButtonTools",
    			LOCTEXT("ButtonToolsSettings", "ButtonToolsSettings"),
    			LOCTEXT("ButtonToolsSettings", "Configure editor buttons."),
    			GetMutableDefault<UButtonToolsGlobalConfig>());
    	}
    }
    
    1. 生成按钮对应的Commands,此处因为将配置进行了开放,不能直接使用UI_COMMAND宏来进行声明,请参考以下代码:
    void FButtonToolsCommands::RegisterConfigCommands()
    {
    	const UButtonToolsGlobalConfig* Config = GetDefault<UButtonToolsGlobalConfig>();
    	for (const auto& Define : Config->ButtonDefines)
    	{
    		TSharedPtr<FUICommandInfo>& Command = ConfigCommands.Add_GetRef(TSharedPtr<FUICommandInfo>());
    		MakeUICommand_InternalUseOnly(this, Command, TEXT(LOCTEXT_NAMESPACE), *Define.CommandName, *(Define.CommandName + "_ToolTip"), TCHAR_TO_ANSI(*("." + Define.CommandName)), *Define.FriendlyName, *Define.Description, EUserInterfaceActionType::Button, FInputGesture());
    	}
    }
    
    1. 定义点击按钮的事件
    void FButtonToolsModule::DoConfigButtonAction(int ID)
    {
    	const UButtonToolsGlobalConfig* Config = GetDefault<UButtonToolsGlobalConfig>();
    	if(Config->ButtonDefines.IsValidIndex(ID) && Config->ButtonDefines[ID].Action.TryLoad())
    	{
    		UButtonActionDefine* Define = Cast<UButtonActionDefine>(Config->ButtonDefines[ID].Action.TryLoad());
    		if(Define && Define->Action)
    		{
    			Define->Action->DoAction();
    		}
    	}
    }
    
    1. 生成Combo按钮
    TSharedRef<SWidget> FButtonToolsModule::FillComboButton(TSharedPtr<class FUICommandList> Commands)
    {
    	FMenuBuilder MenuBuilder(true, Commands);
    
    	const UButtonToolsGlobalConfig* EditorConfig = GetDefault<UButtonToolsGlobalConfig>();
    	for (int i = 0; i < FButtonToolsCommands::Get().ConfigCommands.Num(); ++i)
    	{
    			MenuBuilder.AddMenuEntry(FButtonToolsCommands::Get().ConfigCommands[i], NAME_None, TAttribute<FText>(), TAttribute<FText>());
    	}
    
    	return MenuBuilder.MakeWidget();
    }
    

    模块依赖与版本问题

    如果需要使用提供的两个Action类型,需要添加对以下模块的依赖:

    "Blutility",
    "PythonScriptPlugin",
    "UMGEditor",
    "AssetRegistry",
    

    另外,本样例代码基于4.26进行的开发,若您使用更早的版本,则可以存在链接不到 UEditorUtilityWidgetBlueprint 该类的情况,这是因为UE在早期版本中并未将此类开发出来,可以给该类加上导出宏以解决该问题。

    // add BLUTILITY_API
    class BLUTILITY_API UEditorUtilityWidgetBlueprint : public UWidgetBlueprint
    

    源码参考

    ButtonActionDefine.h & cpp

    // .h
    #pragma once
    
    #include "CoreMinimal.h"
    #include "Engine/DataAsset.h"
    #include "ButtonActionDefine.generated.h"
    
    UCLASS(BlueprintType)
    class BUTTONTOOLS_API UButtonActionDefine : public UDataAsset
    {
    	GENERATED_BODY()
    public:
    	UPROPERTY(BlueprintReadOnly, EditDefaultsOnly, Instanced)
    	UEditorAction* Action;
    };
    
    UCLASS(BlueprintType, EditInlineNew)
    class BUTTONTOOLS_API UEditorAction : public UObject
    {
    	GENERATED_BODY()
    public:
    	virtual void DoAction() const PURE_VIRTUAL(UEditorAction::DoAction)
    };
    
    UCLASS(BlueprintType, EditInlineNew)
    class BUTTONTOOLS_API UEditorAction_OpenWidget : public UEditorAction
    {
    	GENERATED_BODY()
    protected:
    	UPROPERTY(BlueprintReadOnly, EditDefaultsOnly, meta = (AllowedClasses = "EditorUtilityWidgetBlueprint"))
    	FSoftObjectPath WidgetBlueprintPath;
    
    public:
    	virtual void DoAction() const override;
    };
    
    UCLASS(BlueprintType, EditInlineNew)
    class BUTTONTOOLS_API UEditorAction_RunPython : public UEditorAction
    {
    	GENERATED_BODY()
    protected:
    	UPROPERTY(BlueprintReadOnly, EditDefaultsOnly)
    	FString Script;
    
    public:
    	virtual void DoAction() const override;
    };
    
    
    // .cpp
    #include "ButtonActionDefine.h"
    #include "EditorUtilitySubsystem.h"
    #include "EditorUtilityWidgetBlueprint.h"
    #include "IPythonScriptPlugin.h"
    #include "AssetRegistry/AssetRegistryModule.h"
    
    void UEditorAction_OpenWidget::DoAction() const
    {
    	UEditorUtilitySubsystem* EditorUtilitySubsystem = GEditor->GetEditorSubsystem<UEditorUtilitySubsystem>();
    	if (EditorUtilitySubsystem)
    	{
    		FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked<FAssetRegistryModule>("AssetRegistry");
    		UEditorUtilityWidgetBlueprint* Widget = Cast<UEditorUtilityWidgetBlueprint>(WidgetBlueprintPath.TryLoad());
    		if (Widget)
    		{
    			EditorUtilitySubsystem->SpawnAndRegisterTab(Widget);
    		}
    	}
    }
    
    void UEditorAction_RunPython::DoAction() const
    {
    	IPythonScriptPlugin::Get()->ExecPythonCommand(*Script);
    }
    
    

    ButtonToolsGlobalConfig.h

    //ButtonToolsGlobalConfig.h
    #pragma once
    
    #include "CoreMinimal.h"
    #include "ButtonToolsGlobalConfig.generated.h"
    
    USTRUCT(BlueprintType)
    struct FEditorButtonDefine
    {
    	GENERATED_BODY()
    public:
    	// 用于注册UICommand时的CommandID
    	UPROPERTY(BlueprintReadOnly, EditDefaultsOnly)
    	FString CommandName;
    	// 用于注册UICommand时的展示名
    	UPROPERTY(BlueprintReadOnly, EditDefaultsOnly)
    	FString FriendlyName;
    	// 用于注册UICommand时的描述
    	UPROPERTY(BlueprintReadOnly, EditDefaultsOnly)
    	FString Description;
    	// 该按钮点击时的事件DA路径
    	UPROPERTY(BlueprintReadOnly, EditDefaultsOnly, meta=(AllowedClasses="ButtonActionDefine"))
    	FSoftObjectPath Action;
    };
    
    
    UCLASS(config=Game, defaultconfig)
    class BUTTONTOOLS_API UButtonToolsGlobalConfig : public UObject
    {
    	GENERATED_BODY()
    public:
    	UPROPERTY(Config, EditDefaultsOnly, Category = ButtonTools)
    	TArray<FEditorButtonDefine> ButtonDefines;
    };
    
    

    ButtonToolsCommands.h & cpp

    // .h
    #pragma once
    
    #include "CoreMinimal.h"
    #include "Framework/Commands/Commands.h"
    #include "ButtonToolsStyle.h"
    
    class FButtonToolsCommands : public TCommands<FButtonToolsCommands>
    {
    public:
    
    	FButtonToolsCommands()
    		: TCommands<FButtonToolsCommands>(TEXT("ButtonTools"), NSLOCTEXT("Contexts", "ButtonTools", "ButtonTools Plugin"), NAME_None, FButtonToolsStyle::GetStyleSetName())
    	{
    	}
    
    	// TCommands<> interface
    	virtual void RegisterCommands() override;
    
    	void RegisterConfigCommands();
    
    public:
    	TSharedPtr< FUICommandInfo > OpenPluginWindow;
    	TArray<TSharedPtr< FUICommandInfo >> ConfigCommands;
    };
    
    
    
    // .cpp
    #include "ButtonToolsCommands.h"
    #include "ButtonToolsGlobalConfig.h"
    
    #define LOCTEXT_NAMESPACE "FButtonToolsModule"
    
    void FButtonToolsCommands::RegisterCommands()
    {
    	UI_COMMAND(OpenPluginWindow, "ButtonTools", "Bring up ButtonTools window", EUserInterfaceActionType::Button, FInputGesture());
    	RegisterConfigCommands();
    }
    
    void FButtonToolsCommands::RegisterConfigCommands()
    {
    	const UButtonToolsGlobalConfig* Config = GetDefault<UButtonToolsGlobalConfig>();
    	for (const auto& Define : Config->ButtonDefines)
    	{
    		TSharedPtr<FUICommandInfo>& Command = ConfigCommands.Add_GetRef(TSharedPtr<FUICommandInfo>());
    		MakeUICommand_InternalUseOnly(this, Command, TEXT(LOCTEXT_NAMESPACE), *Define.CommandName, *(Define.CommandName + "_ToolTip"),
    			TCHAR_TO_ANSI(*("." + Define.CommandName)), *Define.FriendlyName, *Define.Description, EUserInterfaceActionType::Button, FInputGesture());
    	}
    }
    
    #undef LOCTEXT_NAMESPACE
    

    ButtonTools.h & cpp

    // .h
    #pragma once
    
    #include "CoreMinimal.h"
    #include "Modules/ModuleManager.h"
    
    class FToolBarBuilder;
    class FMenuBuilder;
    
    class FButtonToolsModule : public IModuleInterface
    {
    public:
    
    	/** IModuleInterface implementation */
    	virtual void StartupModule() override;
    	virtual void ShutdownModule() override;
    	
    	/** This function will be bound to Command (by default it will bring up plugin window) */
    	void PluginButtonClicked();
    	
    private:
    
    	void RegisterMenus();
    	void RegisterSettings();
    	void UnregisterSettings();
    
    	TSharedRef<class SDockTab> OnSpawnPluginTab(const class FSpawnTabArgs& SpawnTabArgs);
    
    	void DoConfigButtonAction(int ID);
    
    	TSharedRef<SWidget> FillComboButton(TSharedPtr<class FUICommandList> Commands);
    
    private:
    	TSharedPtr<class FUICommandList> PluginCommands;
    };
    
    
    
    
    // .cpp
    // Copyright Epic Games, Inc. All Rights Reserved.
    #include "ButtonTools.h"
    #include "ButtonToolsStyle.h"
    #include "ButtonToolsCommands.h"
    #include "LevelEditor.h"
    #include "Widgets/Docking/SDockTab.h"
    #include "Widgets/Layout/SBox.h"
    #include "Widgets/Text/STextBlock.h"
    #include "ToolMenus.h"
    #include "ButtonToolsGlobalConfig.h"
    #include "ButtonActionDefine.h"
    #include "ISettingsModule.h"
    
    static const FName ButtonToolsTabName("ButtonTools");
    
    #define LOCTEXT_NAMESPACE "FButtonToolsModule"
    
    void FButtonToolsModule::StartupModule()
    {
    	// This code will execute after your module is loaded into memory; the exact timing is specified in the .uplugin file per-module
    	
    	FButtonToolsStyle::Initialize();
    	FButtonToolsStyle::ReloadTextures();
    
    	FButtonToolsCommands::Register();
    
    	RegisterSettings();
    	
    	PluginCommands = MakeShareable(new FUICommandList);
    
    	for(int i=0;i<FButtonToolsCommands::Get().ConfigCommands.Num();++i)
    	{
    		PluginCommands->MapAction(
    			FButtonToolsCommands::Get().ConfigCommands[i],
    			FExecuteAction::CreateRaw(this, &FButtonToolsModule::DoConfigButtonAction, i),
    			FCanExecuteAction());
    	}
    
    	UToolMenus::RegisterStartupCallback(FSimpleMulticastDelegate::FDelegate::CreateRaw(this, &FButtonToolsModule::RegisterMenus));
    	
    	/*FGlobalTabmanager::Get()->RegisterNomadTabSpawner(ButtonToolsTabName, FOnSpawnTab::CreateRaw(this, &FButtonToolsModule::OnSpawnPluginTab))
    		.SetDisplayName(LOCTEXT("FButtonToolsTabTitle", "ButtonTools"))
    		.SetMenuType(ETabSpawnerMenuType::Hidden);*/
    }
    
    void FButtonToolsModule::ShutdownModule()
    {
    	// This function may be called during shutdown to clean up your module.  For modules that support dynamic reloading,
    	// we call this function before unloading the module.
    
    	UToolMenus::UnRegisterStartupCallback(this);
    
    	UToolMenus::UnregisterOwner(this);
    
    	UnregisterSettings();
    
    	FButtonToolsStyle::Shutdown();
    
    	FButtonToolsCommands::Unregister();
    
    	FGlobalTabmanager::Get()->UnregisterNomadTabSpawner(ButtonToolsTabName);
    }
    
    TSharedRef<SDockTab> FButtonToolsModule::OnSpawnPluginTab(const FSpawnTabArgs& SpawnTabArgs)
    {
    	FText WidgetText = FText::Format(
    		LOCTEXT("WindowWidgetText", "Add code to {0} in {1} to override this window's contents"),
    		FText::FromString(TEXT("FButtonToolsModule::OnSpawnPluginTab")),
    		FText::FromString(TEXT("ButtonTools.cpp"))
    		);
    
    	return SNew(SDockTab)
    		.TabRole(ETabRole::NomadTab)
    		[
    			// Put your tab content here!
    			SNew(SBox)
    			.HAlign(HAlign_Center)
    			.VAlign(VAlign_Center)
    			[
    				SNew(STextBlock)
    				.Text(WidgetText)
    			]
    		];
    }
    
    void FButtonToolsModule::DoConfigButtonAction(int ID)
    {
    	const UButtonToolsGlobalConfig* Config = GetDefault<UButtonToolsGlobalConfig>();
    	if(Config->ButtonDefines.IsValidIndex(ID) && Config->ButtonDefines[ID].Action.TryLoad())
    	{
    		UButtonActionDefine* Define = Cast<UButtonActionDefine>(Config->ButtonDefines[ID].Action.TryLoad());
    		if(Define && Define->Action)
    		{
    			Define->Action->DoAction();
    		}
    	}
    }
    
    TSharedRef<SWidget> FButtonToolsModule::FillComboButton(TSharedPtr<class FUICommandList> Commands)
    {
    	FMenuBuilder MenuBuilder(true, Commands);
    
    	const UButtonToolsGlobalConfig* EditorConfig = GetDefault<UButtonToolsGlobalConfig>();
    	for (int i = 0; i < FButtonToolsCommands::Get().ConfigCommands.Num(); ++i)
    	{
    			MenuBuilder.AddMenuEntry(FButtonToolsCommands::Get().ConfigCommands[i], NAME_None, TAttribute<FText>(), TAttribute<FText>());
    	}
    
    	return MenuBuilder.MakeWidget();
    }
    
    void FButtonToolsModule::PluginButtonClicked()
    {
    	FGlobalTabmanager::Get()->TryInvokeTab(ButtonToolsTabName);
    }
    
    void FButtonToolsModule::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");
    			for (int i = 0; i < FButtonToolsCommands::Get().ConfigCommands.Num(); ++i)
    			{
    				Section.AddMenuEntryWithCommandList(FButtonToolsCommands::Get().ConfigCommands[i], PluginCommands);
    			}
    		}
    	}
    
    	{
    		UToolMenu* ToolbarMenu = UToolMenus::Get()->ExtendMenu("LevelEditor.LevelEditorToolBar");
    		{
    			FToolMenuSection& Section = ToolbarMenu->FindOrAddSection("Settings");
    			{
    				Section.AddEntry(FToolMenuEntry::InitComboButton(
    					"ButtonTools",
    					FUIAction(
    						FExecuteAction(),
    						FCanExecuteAction(),
    						FGetActionCheckState()
    					),
    					FOnGetContent::CreateRaw(this, &FButtonToolsModule::FillComboButton, PluginCommands),
    					LOCTEXT("ButtonTools", "ButtonTools"),
    					LOCTEXT("ButtonTools", "ButtonTools"),
    					FSlateIcon()
    				));
    			}
    		}
    	}
    }
    
    void FButtonToolsModule::RegisterSettings()
    {
    	ISettingsModule* SettingModule = FModuleManager::GetModulePtr<ISettingsModule>("Settings");
    	if(SettingModule)
    	{
    		SettingModule->RegisterSettings("Project", "Plugins", "ButtonTools",
    			LOCTEXT("ButtonToolsSettings", "ButtonToolsSettings"),
    			LOCTEXT("ButtonToolsSettings", "Configure editor buttons."),
    			GetMutableDefault<UButtonToolsGlobalConfig>());
    	}
    }
    
    void FButtonToolsModule::UnregisterSettings()
    {
    	ISettingsModule* SettingModule = FModuleManager::GetModulePtr<ISettingsModule>("Settings");
    	if (SettingModule)
    	{
    		SettingModule->UnregisterSettings("Project", "Plugins", "ButtonTools");
    	}
    }
    
    #undef LOCTEXT_NAMESPACE
    	
    IMPLEMENT_MODULE(FButtonToolsModule, ButtonTools)
    
    
posted @ 2022-04-27 22:46  mood701  阅读(405)  评论(0)    收藏  举报