观察UE4的界面命令(UICommand)扩展编辑器界面的流程并实践

目标

在UE4中,一个界面命令对应一个FUICommandInfo,他可以由多种方式触发,比如快捷键或者菜单按钮。本篇的目标是观察它扩展编辑器界面的流程,即:如何创建一个界面命令?如何与一个具体的代码操作对应起来?如何确实地添加到编辑器的某一处上?随后实践。

参考的目标主要是插件模板:编辑器工具栏按钮,以及AssetManagerEditor这个内建的插件(它实现了显示Asset的引用关系的功能)

基本流程概括

概括讲:
一个FUICommandInfo将被放到一个TCommands(继承自FBindingContext)里并在其中被创建,随后FUICommandList将一个FUICommandInfo与一个FUIAction(命令具体实施的操作)对应起来。最后,编辑器某处界面将添加一个“扩展”,在这个“扩展”中指定了FUICommandList是哪个,以及将为哪几个FUICommandInfo添加界面元素。
可表示为下图:
在这里插入图片描述
出现的包含Command字样的类比较多,下面分别观察下他们的角色:

FUICommandInfo

一个FUICommandInfo实际对应一种命令,它有如下成员变量:

/** Input commands that executes this action */
TArray<TSharedRef<FInputChord>> ActiveChords;

/** Default display name of the command */
FText Label;

/** Localized help text for this command */
FText Description;

/** The default input chords for this command (can be invalid) */
TArray<FInputChord> DefaultChords;

/** Brush name for icon to use in tool bars and menu items to represent this command */
FSlateIcon Icon;

/** Brush name for icon to use in tool bars and menu items to represent this command in its toggled on (checked) state*/
FName UIStyle;

/** Name of the command */
FName CommandName;

/** The context in which this command is active */
FName BindingContext;

/** The type of user interface to associated with this action */
EUserInterfaceActionType UserInterfaceType;
TCommands
/** A base class for a set of commands. Inherit from it to make a set of commands. See MainFrameActions for an example. */
template<typename CommandContextType>
class TCommands : public FBindingContext

TCommands是一个基类,代表了一组命令。你需要继承它来实现一组新的命令。(可以看\Engine\Source\Editor\MainFrame\Private\Frame\MainFrameActions.h中的示例)。

它的基类FBindingContext

/**
 * Represents a context in which input bindings are valid
 */
class SLATE_API FBindingContext
	: public TSharedFromThis<FBindingContext>

代表了一个输入绑定有效的环境。
(关于这个类的意义还有待更深入研究)

FUICommandList

FUICommandList中有一个成员,将存储命令所对应的操作。

typedef TMap< const TSharedPtr< const FUICommandInfo >, FUIAction > FUIBindingMap;
/** Mapping of command to action */
FUIBindingMap UICommandBindingMap;

它拥有多个不同版本的MapAction用来将一个命令与操作对应,其中一个版本如下:

/**
 * Maps a command info to a series of delegates that are executed by a multibox or mouse/keyboard input
 *
 * @param InUICommandInfo	The command info to map
 * @param ExecuteAction		The delegate to call when the command should be executed
 * @param RepeatMode		Can this action can be repeated if the chord used to call it is held down?
 */
void MapAction( const TSharedPtr< const FUICommandInfo > InUICommandInfo, FExecuteAction ExecuteAction, EUIActionRepeatMode RepeatMode = EUIActionRepeatMode::RepeatDisabled );
FUIAction

FUIAction对应了一个实际的操作

/**
 * Implements an UI action.
 */
struct SLATE_API FUIAction

它的成员变量包含各种 delegate,指出这个操作将执行什么函数、能否执行等。

/** Holds a delegate that is executed when this action is activated. */
FExecuteAction ExecuteAction;

/** Holds a delegate that is executed when determining whether this action can execute. */
FCanExecuteAction CanExecuteAction;

/** Holds a delegate that is executed when determining the check state of this action. */
FGetActionCheckState GetActionCheckState;

/** Holds a delegate that is executed when determining whether this action is visible. */
FIsActionButtonVisible IsActionVisibleDelegate;

/** Can this action can be repeated if the chord used to call it is held down? */
EUIActionRepeatMode RepeatMode;

开始实践

下面开始实践,创建命令并尝试将其加入到各种编辑器扩展中。
首先,创建一个空白的插件:
在这里插入图片描述
插件名字TestYaksue

实践:创建命令

\Plugins\TestYaksue\Source\TestYaksue\Private\中创建TestYaksueCommands.hTestYaksueCommands.cpp来创建命令。

TestYaksueCommands.h

#pragma once

#include "CoreMinimal.h"
#include "Framework/Commands/Commands.h"

class FTestYaksueCommands : public TCommands<FTestYaksueCommands>
{
public:
	FTestYaksueCommands();

	// TCommands<>的接口:注册命令
	virtual void RegisterCommands() override;

public:
	//命令A
	TSharedPtr< FUICommandInfo > CommandA;
};

FTestYaksueCommands将容纳这个插件所有的命令,当前只有一个命令CommandA


TestYaksueCommands.cpp

#include "TestYaksueCommands.h"

#define LOCTEXT_NAMESPACE "FTestYaksueModule"

FTestYaksueCommands::FTestYaksueCommands() : TCommands<FTestYaksueCommands>(
	"TestYaksueCommands",
	NSLOCTEXT("Contexts", "TestYaksueCommands", "TestYaksue Plugin"),
	NAME_None,
	FName(*FString("todo")))
{
}

void FTestYaksueCommands::RegisterCommands()
{
	UI_COMMAND(CommandA, "TestYaksueCommandA", "Execute TestYaksue CommandA", EUserInterfaceActionType::Button, FInputChord());
}

#undef LOCTEXT_NAMESPACE

构造函数中将调用基类的构造函数:

  • 参数1:const FName InContextName。现在是"TestYaksueCommands"
  • 参数2:const FText& InContextDesc。现在是NSLOCTEXT("Contexts", "TestYaksueCommands", "TestYaksue Plugin")
  • 参数3:const FName InContextParent。现在是NAME_None
  • 参数4:const FName InStyleSetName。这里留意,他和图标有关,现在暂时是FName(*FString("todo")),如果需要图标,则这里需要改动。

RegisterCommands中将使用UI_COMMAND宏注册自己所有命令。留意最后一个参数,现在是FInputGesture(),它和快捷键有关。


TestYaksue.cpp中有这个插件的模块的启动与关闭函数。

首先

#include"TestYaksueCommands.h"

在模块的启动函数中:

FTestYaksueCommands::Register();

RegisterTCommands的接口。用来注册命令,通常在模块的启动函数中调用。观察它的实现可看到内部调用了RegisterCommands函数。

/** Use this method to register commands. Usually done in StartupModule(). */
FORCENOINLINE static void Register()

在模块的关闭函数中:

FTestYaksueCommands::Unregister();

UnregisterTCommands的接口。负责清理所有和这组命令相关的资源,通常在模块的关闭函数中被调用。

/** Use this method to clean up any resources used by the command set. Usually done in ShutdownModule() */
FORCENOINLINE static void Unregister()

实践:为命令映射具体的操作

FTestYaksueModule中声明一个函数用来指定CommandA所对应的操作:

//CommandA所对应的操作
void CommandAAction();

具体做的就是弹出一个对话框

void FTestYaksueModule::CommandAAction()
{
	FMessageDialog::Open(EAppMsgType::Ok, FText::FromString("Execute CommandA"));
}

而“映射”操作需要由FUICommandList完成。
为此,可以在FTestYaksueModule中创建一个新的FUICommandList

TSharedPtr<class FUICommandList> PluginCommandList;

然后在模块的启动函数中创建它并映射命令:

//创建UICommandList
PluginCommandList = MakeShareable(new FUICommandList);
//为命令映射操作
PluginCommandList->MapAction(
	FTestYaksueCommands::Get().CommandA,
	FExecuteAction::CreateRaw(this, &FTestYaksueModule::CommandAAction),
	FCanExecuteAction());

实践:扩展关卡编辑器的菜单与工具栏

为了扩展关卡编辑器(LevelEditor),需要

#include "LevelEditor.h"
#include "Framework/MultiBox/MultiBoxBuilder.h"

在模块的启动函数中:

//获得关卡编辑器模块
FLevelEditorModule& LevelEditorModule = FModuleManager::LoadModuleChecked<FLevelEditorModule>("LevelEditor");
//扩展关卡编辑器的菜单
{
	TSharedPtr<FExtender> MenuExtender = MakeShareable(new FExtender());
	MenuExtender->AddMenuExtension("WindowLayout"
		, EExtensionHook::After
		, PluginCommandList
		, FMenuExtensionDelegate::CreateLambda([](FMenuBuilder& Builder)
			{
				Builder.AddMenuEntry(FTestYaksueCommands::Get().CommandA);
			}));
	LevelEditorModule.GetMenuExtensibilityManager()->AddExtender(MenuExtender);
}
//扩展关卡编辑器的工具栏
{
	TSharedPtr<FExtender> ToolbarExtender = MakeShareable(new FExtender);
	ToolbarExtender->AddToolBarExtension("Settings"
		, EExtensionHook::After
		, PluginCommandList
		, FToolBarExtensionDelegate::CreateLambda([](FToolBarBuilder& Builder)
			{
				Builder.AddToolBarButton(FTestYaksueCommands::Get().CommandA);
			}));
	LevelEditorModule.GetToolBarExtensibilityManager()->AddExtender(ToolbarExtender);
}

可以在关卡编辑器的菜单中看到新扩展的命令。
在这里插入图片描述
也可以在关卡编辑器的工具栏中看到新的命令:
在这里插入图片描述

实践:快捷键

首先,需要在模块的PrivateDependencyModuleNames中添加:"InputCore"

在使用UI_COMMAND宏创建命令时,最后一个参数是快捷键,例如:

UI_COMMAND(CommandA, "TestYaksueCommandA", "Execute TestYaksue CommandA", EUserInterfaceActionType::Button, FInputChord(EModifierKey::Shift | EModifierKey::Alt, EKeys::Z));

将表示Shift + Alt+Z是这个命令的快捷键。
不过,光是这样是没有效果的,没有任何情况可以用这个快捷键触发命令。为了使这个快捷键有效,需要将这个命令使用某个“特殊”的FUICommandList中映射。
例如:

//将CommandA添加入关卡编辑器的GlobalLevelEditorActions中,这样可以触发快捷键
{
	TSharedRef<FUICommandList> LevelEditorCommandList = LevelEditorModule.GetGlobalLevelEditorActions();
	LevelEditorCommandList->MapAction(
		FTestYaksueCommands::Get().CommandA,
		FExecuteAction::CreateRaw(this, &FTestYaksueModule::CommandAAction),
		FCanExecuteAction());
}

LevelEditorModule.GetGlobalLevelEditorActions将获得关卡编辑器的FUICommandList,使用其来映射命令可以在关卡编辑器中触发这个命令


也就是说,关卡编辑器的FUICommandList将会在关卡编辑器内检测是否有快捷键。而之前在【实践:为命令映射具体的操作】中自己创建的FUICommandList并没有额外的检测快捷键是否触发的机制,所以没有任何快捷键效果。
除了关卡编辑器,“内容浏览器”也有类似的FUICommandList。它在触发命令的时候还可以知道所选择的资源是哪些。

为了实验,可以再创建一个CommandB

//命令B
TSharedPtr< FUICommandInfo > CommandB;
UI_COMMAND(CommandB, "TestYaksueCommandB", "Execute TestYaksue CommandB", EUserInterfaceActionType::Button, FInputChord(EModifierKey::Shift | EModifierKey::Alt, EKeys::X));
void FTestYaksueModule::CommandBAction(FOnContentBrowserGetSelection GetSelectionDelegate)
{
	//获得当前选择的资源
	TArray<FAssetData> SelectedAssets;
	TArray<FString> SelectedPaths;
	if (GetSelectionDelegate.IsBound())
		GetSelectionDelegate.Execute(SelectedAssets, SelectedPaths);
	
	//要显示的信息:
	FString Message = "Execute CommandB:";
	for (auto ad : SelectedAssets)
		Message += ad.GetAsset()->GetName()+" ";
	//显示对话框
	FMessageDialog::Open(EAppMsgType::Ok, FText::FromString(Message));
}

然后先

#include "ContentBrowserModule.h"

再在模块的启动函数中:

//获得内容浏览器模块
FContentBrowserModule& ContentBrowserModule = FModuleManager::LoadModuleChecked<FContentBrowserModule>(TEXT("ContentBrowser"));
//加入内容浏览器的命令
TArray<FContentBrowserCommandExtender>& CBCommandExtenderDelegates = ContentBrowserModule.GetAllContentBrowserCommandExtenders();
CBCommandExtenderDelegates.Add(FContentBrowserCommandExtender::CreateLambda([this](TSharedRef<FUICommandList> CommandList, FOnContentBrowserGetSelection GetSelectionDelegate)
	{
		CommandList->MapAction(FTestYaksueCommands::Get().CommandB,
			FExecuteAction::CreateLambda([this, GetSelectionDelegate]
				{
					CommandBAction(GetSelectionDelegate);
				})
		);
	}));

现在,CommandB已经可以由快捷键触发,但是它不能在关卡编辑器中触发,只能在内容浏览器中触发:
在这里插入图片描述

实践:其他菜单拓展

UE4有多处菜单都可以拓展,例如:

内容浏览器中的Asset右键菜单
//增加内容浏览器中Asset右键菜单
TArray<FContentBrowserMenuExtender_SelectedAssets>& CBAssetMenuExtenderDelegates = ContentBrowserModule.GetAllAssetViewContextMenuExtenders();
CBAssetMenuExtenderDelegates.Add(FContentBrowserMenuExtender_SelectedAssets::CreateLambda([](const TArray<FAssetData>& SelectedAssets)
	{
		TSharedRef<FExtender> Extender(new FExtender());

		Extender->AddMenuExtension(
			"AssetContextReferences",
			EExtensionHook::After,
			nullptr,
			FMenuExtensionDelegate::CreateLambda([](FMenuBuilder& MenuBuilder)
				{
					MenuBuilder.AddMenuEntry(FTestYaksueCommands::Get().CommandB);
				}));

		return Extender;
	}));

注意AddMenuExtension时,"AssetContextReferences"指定了命令将要添加到的分栏位置,而nullptr参数位置本来应该是一个FUICommandList,现在设置为空。是因为ContentBrowserModule已经拥有一个FUICommandList了,它在上一步中为CommandB映射过操作了。
在这里插入图片描述

Asset编辑器中的菜单

需要首先在PrivateDependencyModuleNames中添加"UnrealEd"
然后插件模块启动函数中:

//增加Asset编辑器中的菜单
TArray<FAssetEditorExtender>& AssetEditorMenuExtenderDelegates = FAssetEditorToolkit::GetSharedMenuExtensibilityManager()->GetExtenderDelegates();
AssetEditorMenuExtenderDelegates.Add(FAssetEditorExtender::CreateLambda([this](const TSharedRef<FUICommandList> CommandList, const TArray<UObject*> ContextSensitiveObjects)
	{
		//映射操作
		CommandList->MapAction(
			FTestYaksueCommands::Get().CommandA,
			FExecuteAction::CreateRaw(this, &FTestYaksueModule::CommandAAction),
			FCanExecuteAction());
		//添加菜单扩展
		TSharedRef<FExtender> Extender(new FExtender());
		Extender->AddMenuExtension(
			"FindInContentBrowser",
			EExtensionHook::After,
			CommandList,
			FMenuExtensionDelegate::CreateLambda([](FMenuBuilder& MenuBuilder)
				{
					MenuBuilder.AddMenuEntry(FTestYaksueCommands::Get().CommandA);
				}));
		return Extender;
	}));

在这里插入图片描述

实践:图标

先制作两个图标到插件的Resources目录中
在这里插入图片描述
需要向PrivateDependencyModuleNames中添加"Projects",因为其中含有IPluginManager可以帮助找到插件的目录在哪。


TestYaksueStyle.h:

#pragma once

#include "CoreMinimal.h"
#include "Styling/SlateStyle.h"

class FTestYaksueStyle
{
public:

	//初始化,启动模块时调用
	static void Initialize();

	//关闭模块时调用
	static void Shutdown();

	/** reloads textures used by slate renderer */
	static void ReloadTextures();

	//返回这个style的名字
	static FName GetStyleSetName();

private:

	//创建一个实例
	static TSharedRef< class FSlateStyleSet > Create();

private:

	//style实例
	static TSharedPtr< class FSlateStyleSet > StyleInstance;
};

TestYaksueStyle.cpp

#include "TestYaksueStyle.h"
#include "Framework/Application/SlateApplication.h"
#include "Styling/SlateStyleRegistry.h"
#include "Slate/SlateGameResources.h"
#include "Interfaces/IPluginManager.h"

TSharedPtr< FSlateStyleSet > FTestYaksueStyle::StyleInstance = NULL;

void FTestYaksueStyle::Initialize()
{
	if (!StyleInstance.IsValid())
	{
		StyleInstance = Create();
		FSlateStyleRegistry::RegisterSlateStyle(*StyleInstance);
	}
}

void FTestYaksueStyle::Shutdown()
{
	FSlateStyleRegistry::UnRegisterSlateStyle(*StyleInstance);
	ensure(StyleInstance.IsUnique());
	StyleInstance.Reset();
}

FName FTestYaksueStyle::GetStyleSetName()
{
	return TEXT("TestYaksueStyle");
}

#define IMAGE_BRUSH( RelativePath, ... ) FSlateImageBrush( Style->RootToContentDir( RelativePath, TEXT(".png") ), __VA_ARGS__ )

const FVector2D Icon40x40(40.0f, 40.0f);

TSharedRef< FSlateStyleSet > FTestYaksueStyle::Create()
{
	//创建一个新的Style实例:
	TSharedRef< FSlateStyleSet > Style = MakeShareable(new FSlateStyleSet(GetStyleSetName()));

	//设置资源目录,为本插件的Resources目录
	Style->SetContentRoot(IPluginManager::Get().FindPlugin("TestYaksue")->GetBaseDir() / TEXT("Resources"));

	//注册图标:
	Style->Set("TestYaksueCommands.CommandA", new IMAGE_BRUSH(TEXT("PicA"), Icon40x40));
	Style->Set("TestYaksueCommands.CommandB", new IMAGE_BRUSH(TEXT("PicB"), Icon40x40));

	return Style;
}

#undef IMAGE_BRUSH


void FTestYaksueStyle::ReloadTextures()
{
	if (FSlateApplication::IsInitialized())
		FSlateApplication::Get().GetRenderer()->ReloadTextureResources();
}

FTestYaksueCommands的构造函数中将这个Style的名字传进来

FTestYaksueCommands::FTestYaksueCommands() : TCommands<FTestYaksueCommands>(
	"TestYaksueCommands",
	NSLOCTEXT("Contexts", "TestYaksueCommands", "TestYaksue Plugin"),
	NAME_None,
	FTestYaksueStyle::GetStyleSetName())

在模块的启动函数中:

FTestYaksueStyle::Initialize();
FTestYaksueStyle::ReloadTextures();

最后可以看到图标:
在这里插入图片描述

最终的代码

TestYaksue.Build.cs

using UnrealBuildTool;

public class TestYaksue : ModuleRules
{
	public TestYaksue(ReadOnlyTargetRules Target) : base(Target)
	{
		PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs;
		
		PublicIncludePaths.AddRange(
			new string[] {
				// ... add public include paths required here ...
			}
			);
				
		
		PrivateIncludePaths.AddRange(
			new string[] {
				// ... add other private include paths required here ...
			}
			);
			
		
		PublicDependencyModuleNames.AddRange(
			new string[]
			{
				"Core",
				// ... add other public dependencies that you statically link with here ...
			}
			);
			
		
		PrivateDependencyModuleNames.AddRange(
			new string[]
			{
				"CoreUObject",
				"Engine",
				"Slate",
				"SlateCore",
                "InputCore",
                "UnrealEd",
                "Projects",
				// ... add private dependencies that you statically link with here ...	
			}
			);
		
		
		DynamicallyLoadedModuleNames.AddRange(
			new string[]
			{
				// ... add any modules that your module loads dynamically here ...
			}
			);
	}
}

TestYaksueCommands.h

#pragma once

#include "CoreMinimal.h"
#include "Framework/Commands/Commands.h"

class FTestYaksueCommands : public TCommands<FTestYaksueCommands>
{
public:
	FTestYaksueCommands();

	// TCommands<>的接口:注册命令
	virtual void RegisterCommands() override;

public:
	//命令A
	TSharedPtr< FUICommandInfo > CommandA;
	//命令B
	TSharedPtr< FUICommandInfo > CommandB;
};

TestYaksueCommands.cpp

#include "TestYaksueCommands.h"

#include"TestYaksueStyle.h"

#define LOCTEXT_NAMESPACE "FTestYaksueModule"

FTestYaksueCommands::FTestYaksueCommands() : TCommands<FTestYaksueCommands>(
	"TestYaksueCommands",
	NSLOCTEXT("Contexts", "TestYaksueCommands", "TestYaksue Plugin"),
	NAME_None,
	FTestYaksueStyle::GetStyleSetName())
{
}

void FTestYaksueCommands::RegisterCommands()
{
	UI_COMMAND(CommandA, "TestYaksueCommandA", "Execute TestYaksue CommandA", EUserInterfaceActionType::Button, FInputChord(EModifierKey::Shift | EModifierKey::Alt, EKeys::Z));
	UI_COMMAND(CommandB, "TestYaksueCommandB", "Execute TestYaksue CommandB", EUserInterfaceActionType::Button, FInputChord(EModifierKey::Shift | EModifierKey::Alt, EKeys::X));
}

#undef LOCTEXT_NAMESPACE

TestYaksue.h

#pragma once

#include "CoreMinimal.h"
#include "Modules/ModuleManager.h"
#include"ContentBrowserDelegates.h"

class FTestYaksueModule : public IModuleInterface
{
public:

	/** IModuleInterface implementation */
	virtual void StartupModule() override;
	virtual void ShutdownModule() override;


	//CommandA所对应的操作
	void CommandAAction();
	//CommandB所对应的操作
	void CommandBAction(FOnContentBrowserGetSelection GetSelectionDelegate);
private:
	TSharedPtr<class FUICommandList> PluginCommandList;
};

TestYaksue.cpp

#include "TestYaksue.h"

#include"TestYaksueCommands.h"
#include"TestYaksueStyle.h"

#include "Misc/MessageDialog.h"
#include "Framework/Commands/UICommandList.h"
#include "LevelEditor.h"
#include "Framework/MultiBox/MultiBoxBuilder.h"
#include "ContentBrowserModule.h"

#define LOCTEXT_NAMESPACE "FTestYaksueModule"

void FTestYaksueModule::StartupModule()
{
	// This code will execute after your module is loaded into memory; the exact timing is specified in the .uplugin file per-module

	//初始化Style
	FTestYaksueStyle::Initialize();
	FTestYaksueStyle::ReloadTextures();

	//注册命令:
	FTestYaksueCommands::Register();

	
	//创建UICommandList
	PluginCommandList = MakeShareable(new FUICommandList);
	//为命令映射操作
	PluginCommandList->MapAction(
		FTestYaksueCommands::Get().CommandA,
		FExecuteAction::CreateRaw(this, &FTestYaksueModule::CommandAAction),
		FCanExecuteAction());

	
	//获得关卡编辑器模块
	FLevelEditorModule& LevelEditorModule = FModuleManager::LoadModuleChecked<FLevelEditorModule>("LevelEditor");
	//扩展关卡编辑器的菜单
	{
		TSharedPtr<FExtender> MenuExtender = MakeShareable(new FExtender());
		MenuExtender->AddMenuExtension("WindowLayout"
			, EExtensionHook::After
			, PluginCommandList
			, FMenuExtensionDelegate::CreateLambda([](FMenuBuilder& Builder)
				{
					Builder.AddMenuEntry(FTestYaksueCommands::Get().CommandA);
				}));
		LevelEditorModule.GetMenuExtensibilityManager()->AddExtender(MenuExtender);
	}
	//扩展关卡编辑器的工具栏
	{
		TSharedPtr<FExtender> ToolbarExtender = MakeShareable(new FExtender);
		ToolbarExtender->AddToolBarExtension("Settings"
			, EExtensionHook::After
			, PluginCommandList
			, FToolBarExtensionDelegate::CreateLambda([](FToolBarBuilder& Builder)
				{
					Builder.AddToolBarButton(FTestYaksueCommands::Get().CommandA);
				}));
		LevelEditorModule.GetToolBarExtensibilityManager()->AddExtender(ToolbarExtender);
	}

	//将CommandA添加入关卡编辑器的GlobalLevelEditorActions中,这样可以触发快捷键
	{
		TSharedRef<FUICommandList> LevelEditorCommandList = LevelEditorModule.GetGlobalLevelEditorActions();
		LevelEditorCommandList->MapAction(
			FTestYaksueCommands::Get().CommandA,
			FExecuteAction::CreateRaw(this, &FTestYaksueModule::CommandAAction),
			FCanExecuteAction());
	}


	//获得内容浏览器模块
	FContentBrowserModule& ContentBrowserModule = FModuleManager::LoadModuleChecked<FContentBrowserModule>(TEXT("ContentBrowser"));
	//加入内容浏览器的命令
	TArray<FContentBrowserCommandExtender>& CBCommandExtenderDelegates = ContentBrowserModule.GetAllContentBrowserCommandExtenders();
	CBCommandExtenderDelegates.Add(FContentBrowserCommandExtender::CreateLambda([this](TSharedRef<FUICommandList> CommandList, FOnContentBrowserGetSelection GetSelectionDelegate)
		{
			CommandList->MapAction(FTestYaksueCommands::Get().CommandB,
				FExecuteAction::CreateLambda([this, GetSelectionDelegate]
					{
						CommandBAction(GetSelectionDelegate);
					})
			);
		}));

	//增加内容浏览器中Asset右键菜单
	TArray<FContentBrowserMenuExtender_SelectedAssets>& CBAssetMenuExtenderDelegates = ContentBrowserModule.GetAllAssetViewContextMenuExtenders();
	CBAssetMenuExtenderDelegates.Add(FContentBrowserMenuExtender_SelectedAssets::CreateLambda([](const TArray<FAssetData>& SelectedAssets)
		{
			TSharedRef<FExtender> Extender(new FExtender());

			Extender->AddMenuExtension(
				"AssetContextReferences",
				EExtensionHook::After,
				nullptr,
				FMenuExtensionDelegate::CreateLambda([](FMenuBuilder& MenuBuilder)
					{
						MenuBuilder.AddMenuEntry(FTestYaksueCommands::Get().CommandB);
					}));

			return Extender;
		}));

	//增加Asset编辑器中的菜单
	TArray<FAssetEditorExtender>& AssetEditorMenuExtenderDelegates = FAssetEditorToolkit::GetSharedMenuExtensibilityManager()->GetExtenderDelegates();
	AssetEditorMenuExtenderDelegates.Add(FAssetEditorExtender::CreateLambda([this](const TSharedRef<FUICommandList> CommandList, const TArray<UObject*> ContextSensitiveObjects)
		{
			//映射操作
			CommandList->MapAction(
				FTestYaksueCommands::Get().CommandA,
				FExecuteAction::CreateRaw(this, &FTestYaksueModule::CommandAAction),
				FCanExecuteAction());
			//添加菜单扩展
			TSharedRef<FExtender> Extender(new FExtender());
			Extender->AddMenuExtension(
				"FindInContentBrowser",
				EExtensionHook::After,
				CommandList,
				FMenuExtensionDelegate::CreateLambda([](FMenuBuilder& MenuBuilder)
					{
						MenuBuilder.AddMenuEntry(FTestYaksueCommands::Get().CommandA);
					}));
			return Extender;
		}));

}

void FTestYaksueModule::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.

	FTestYaksueCommands::Unregister();
}

void FTestYaksueModule::CommandAAction()
{
	FMessageDialog::Open(EAppMsgType::Ok, FText::FromString("Execute CommandA"));
}

void FTestYaksueModule::CommandBAction(FOnContentBrowserGetSelection GetSelectionDelegate)
{
	//获得当前选择的资源
	TArray<FAssetData> SelectedAssets;
	TArray<FString> SelectedPaths;
	if (GetSelectionDelegate.IsBound())
		GetSelectionDelegate.Execute(SelectedAssets, SelectedPaths);
	
	//要显示的信息:
	FString Message = "Execute CommandB:";
	for (auto ad : SelectedAssets)
		Message += ad.GetAsset()->GetName()+" ";
	//显示对话框
	FMessageDialog::Open(EAppMsgType::Ok, FText::FromString(Message));
}

#undef LOCTEXT_NAMESPACE
	
IMPLEMENT_MODULE(FTestYaksueModule, TestYaksue)
  • 21
    点赞
  • 38
    收藏
    觉得还不错? 一键收藏
  • 7
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值