In this post, we focus on how to expand the engine editor, mainly to expand MenuBar, ToolBar and Content Browser.
1. Module or Plugin?
The answer is: It depends. There is actually not that much difference between plugin and module. A plugin is actually a collection of modules, and there is no different in essence. The main difference between using plugin and modules is reusability. Plugins are suitable when you find that the current module dose not depend on any other project modules, or to be simple, a plugin should support itself without any modules besides those in the plugin except they are engine module, so that you can transfer a plugin to any other projects. Plugin means high reusability, while adding modules directly to the project is suitable for those cannot exist without other gameplay module or plugins.
That doesn’t mean plugin cannot depend on other plugins or modules, acturally it can depend on other plugins, but, be careful for that.
In this post, we’ll use modules instead of plugins for now.
2. Understanding the UE Editor
First, we need to understand Unreal’s editor interface, everybody knows what is an UE editor, but it is beneficial for us to standardize the name of each part in the Unreal editor.
Number | Standard Name |
---|---|
1 | Menu Bar |
2 | Main Tool Bar |
3 | Level Viewport |
4 | Content Drawer |
5 | Bottom Toolbar |
6 | Outliner |
7 | Detail View |
And the Content Browser is as followed:
In addition, for convenience, open the Editor Preferences, open General-Miscellaneous and toggle Display UI Extension Points, which allows the Extend Points visiable.
3. Understanding UI Command
Add a module and named it with EditorExtension, add Core, CoreObject, Slate and SlateCore module dependencies.
Add a MenuBar extension can be done by FExtender or UToolMenu, both are no easy to use and both will be introduced soon. This is a tutorial so I may not encapsulate my code.
Add a Entry in existed menu
An Entry can be treated as a button, for covinience, I will not explain what the noun is.
We add an Entry in Window-Log which will print “Hello World” in log after being clicked.
The result is:
But first of all, we need to introduce a comcept : UICommand. A UICoomand base on this idea: A command maybe not only be trigger by a button, it can also be a shortcut key. like ctrl+z triggers undoing. also, I can do that by click a undo button, no matter what it is, the idea is that: A command can be triggered by many ways, and many UI operation my trigger the same command. If we want to reuse command, we need a commamd abstract, that is, the FUICommand.
/** 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;
Additionally, Unreal also provides something like icons, from me perspective, it is a little bit complicated, most of the time, we do not need command reusability, we just need a button with name, tooltip, icon and the delegate execute after clicking.
Fortunately, Unreal do provided APi to do that without using FUICommand.
Add an Entry on the Menubar
void FEditorExtensionModule::StartupModule() { // 通过FExtender实现 const TSharedPtr<FExtender> Extender = MakeShareable(new FExtender); Extender->AddMenuExtension("Log", EExtensionHook::After, MakeShareable(new FUICommandList), FNewMenuDelegate::CreateLambda([](FMenuBuilder& MenuBuilder) { MenuBuilder.AddSeparator(); MenuBuilder.AddMenuEntry(LOCTEXT("LabelPrintLog", "打印Hello World"), LOCTEXT("Tooltip", "在Log中打印Hello world"), FSlateIcon(FAppStyle::GetAppStyleSetName(), "Icons.Blueprint"), FUIAction(FExecuteAction::CreateLambda([]() { UE_LOG(LogTemp, Log, TEXT("Hello world")) }))); })); FLevelEditorModule& LevelEditorModule = FModuleManager::LoadModuleChecked<FLevelEditorModule>(TEXT("LevelEditor")); LevelEditorModule.GetMenuExtensibilityManager()->AddExtender(Extender); }
tips: my default culture is zh_hans, so I didn’t write english on localization value, because now I should translate Chinese into English, if I write English directly, there will be no Chinese edition.
tips: I choose a icon provided by Unreal, you can use your own icon, but it is a little complicated.
Add a sub-menu on the MenuBar
void FEditorExtensionModule::StartupModule() { // 通过FExtender实现 const TSharedPtr<FExtender> Extender = MakeShareable(new FExtender); Extender->AddMenuExtension("Log", EExtensionHook::After, MakeShareable(new FUICommandList), FNewMenuDelegate::CreateLambda([](FMenuBuilder& MenuBuilder) { MenuBuilder.AddSeparator(); MenuBuilder.AddMenuEntry(LOCTEXT("LabelPrintLog", "打印Hello World"), LOCTEXT("Tooltip", "在Log中打印Hello world"), FSlateIcon(FAppStyle::GetAppStyleSetName(), "Icons.Blueprint"), FUIAction(FExecuteAction::CreateLambda([]() { UE_LOG(LogTemp, Log, TEXT("Hello world")) }))); })); Extender->AddMenuBarExtension("Help", EExtensionHook::After, MakeShareable(new FUICommandList), FMenuBarExtensionDelegate::CreateLambda([](FMenuBarBuilder& MenuBarBuilder) { MenuBarBuilder.AddPullDownMenu(LOCTEXT("MenubarName", "新菜单"), LOCTEXT("Menubar Tool Tip", "新菜单提示"), FNewMenuDelegate::CreateLambda([](FMenuBuilder& MenuBuilder) { MenuBuilder.AddSeparator(); MenuBuilder.AddMenuEntry(LOCTEXT("LabelPrintLog", "打印Hello World"), LOCTEXT("Tooltip", "在Log中打印Hello world"), FSlateIcon(FAppStyle::GetAppStyleSetName(), "Icons.Blueprint"), FUIAction(FExecuteAction::CreateLambda([]() { UE_LOG(LogTemp, Log, TEXT("Hello world")) }))); }), "SubMenuHook"); })); FLevelEditorModule& LevelEditorModule = FModuleManager::LoadModuleChecked<FLevelEditorModule>(TEXT("LevelEditor")); LevelEditorModule.GetMenuExtensibilityManager()->AddExtender(Extender); }
I use lambda expression for convinience in copying code, but it shouldn’t for the code is too complex.
Add a button on the Toolbar
TSharedPtr<FExtender> ToolExtender = MakeShareable(new FExtender()); ToolExtender->AddToolBarExtension("Content", EExtensionHook::After, PluginCommands, FToolBarExtensionDelegate::CreateLambda(这个自己填吧,累了); FLevelEditorModule& LevelEditorModule = FModuleManager::LoadModuleChecked<FLevelEditorModule>("LevelEditor"); LevelEditorModule.GetToolBarExtensibilityManager()->AddExtender(ToolExtender);
4. Conclusion
In conclusion, it is too complicated and cumbersome to implement engine editor extension with C++, is there a way to do that much easily?
In fact, that are two convinient ways: by using blueprint and python, which will be introduced in the following posts.
Additionally, there are also open-source plugins: