IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 游戏开发 -> 【UE·Editor篇】做一个超好用的菜单栏扩展框架 -> 正文阅读

[游戏开发]【UE·Editor篇】做一个超好用的菜单栏扩展框架

众所周知,在UE4做编辑器扩展是一件无比蛋疼的事情
首先要考虑是写Plugin还是Module的形式,然后又是加Build.cs,新建文件夹新建文件。涉及到菜单栏扩展还需要知道一堆类的用法,FExtender、FMenuBuilder、FMenuBarBuilder、Command、MenuDelegate等。加菜单,加菜单栏,接口又不一样,AddMenuEntry、AddSubMenu、AddPullDownMenu。
Unity一行代码 [MenuItem]搞定的事情,为什么UE4就这么麻烦呢?
在这里插入图片描述

本着我不入地狱谁入地狱的心态,做了一个非常方便扩展菜单栏功能的简易框架(代码不到200行)。最终的效果是这样:
在这里插入图片描述
在这里插入图片描述
继承特定的类,只要注册好路径,你就可以写你想要的逻辑了。下面会开始讲解这个功能是怎么设计和实现的,如果不感兴趣也可以直接下github把代码复制到自己工程直接用。github工程地址


EditorModule vs Plugin

首先需要考虑的是菜单栏扩展是写成Plugin还是EditorModulePlugin意味着代码更独立,方便移植到不同项目里EditorModule不方便移植,但是可以引用GameModule的类。考虑到编辑器扩展可能会用到GameModule的一些类,比如说将来我们要做一个查找工程里有没有特定类型的蓝图的功能,这个类型包括GameModule任意自定义的类。所以在这个框架里,我选择把菜单栏扩展的功能放在EditorModule。
EditorModule的创建很基础了,网上教程也很多:[Creating an Editor Module]。(https://michaeljcole.github.io/wiki.unrealengine.com/Creating_an_Editor_Module/)
这里就放几张图简单过一下。
在这里插入图片描述
在这里插入图片描述
文件结构如下:
在这里插入图片描述


MenuManager

要想实现最终的效果,我们首先需要划分目标。第一步当然就是扩展最简单的菜单栏。也是网上一堆教程:编辑器扩展:自定义菜单栏。但是鸡佬的做法略有不同,所以这里会讲的细一点。
首先我们新建一个MenuManger继承自EngineSubSystem
为什么用EngineSubSystem?EngineSubSystem是一种特殊的单例。当Module被加载的时候SubSystem会自动创建并初始化,不需要我们操心它的调用时机。关于菜单栏扩展的核心代码我们将写在MenuManager。
在这里插入图片描述


MenuBar、PullDownMenu、SubMenu

接下来创建菜单栏、下拉框、二级菜单、按钮。首先认识MenuBar、PullDownMenu、SubMenu这几个词的区别,以免等下调用相关函数的时候脑子晕掉。
在这里插入图片描述
首先在MenuManger的初始化函数加载LevelEditorModule,然后创建FExtender(扩展器类),接下来调用FExtender的AddMenuBarExtension方法。
在这里插入图片描述
AddMenuBarExtension有四个参数:

  • FName ExtensionHook。要挂在那个菜单附近。
  • EExtensionHook::Position HookPosition。要挂的位置的类型,前或后,还有另一个类型用不着。
  • const TSharedPtr< FUICommandList >& CommandList。可以用于绑定通用的按钮操作,比如复制粘贴撤销等。这个案例里我们用不着,可以直接使用nullptr。可以参考CurveEditor类对CommandList的使用。在这里插入图片描述
  • const FMenuBarExtensionDelegate& MenuBarExtensionDelegate。菜单栏扩展委托。使用FMenuBarExtensionDelegate::CreateXXX型函数进行创建委托。除了CreateUObject还有CreateStatic、CreateLambda等其他方式。注意这里的CreateUObject不是创建UObject的意思,而是CreateByUObjet,即通过提供的这个UObject类的这个方法来创建一个委托。

回到代码,AddMenuBarExtension那行的意思就是告诉扩展器我要在Help的后面插入MenuBar,不绑定通用操作,要插入的菜单长什么样,叫什么名字,由我MenuManager的AddMenuBarExtension告诉你

然后是MenuMager的AddMenuBarExtension及相关代码:
在这里插入图片描述

  • AddMenuBarExtension调用AddPullDownMenu告诉编辑器要在“First”的菜单栏里添加下拉框。注意使用的是FMenuBarBuilder参数
  • AddMenuExtension调用AddSubMenu告诉编辑器要在“Second”的下拉框中添加子菜单。使用FMenuBuilder参数。
  • AddSubMenuExtension调用AddMenuEntry告诉编辑器要在“Test”的菜单中注册方法来调用。使用FMenuBuilder参数。注册委托使用的FUIAction类。
  • 最终调用LogTestFunc方法。

最后写完就是出现这样的菜单栏,点击以后会打log。
在这里插入图片描述


Class Default Object

大部分网上的教程也就到上面那一步了。但是,离真正可以投入实际项目使用还差的很远。下面鸡佬将讲解本框架最精华的部分
上面讲到,如果要绑定委托,有好几种方法。Static?但是把所有要扩展菜单的方法都写成Static既麻烦又不能实现自动化。UObject?但是需要对象啊,谁来创建对象呢?创建了以后是不是还要统一管理也麻烦啊。不对,UObject真的需要我们创建对象吗?我们是不是忘了还有CDO的存在。
CDO,ClassDefaultObject,所有UObject类都存在的一个默认对象。关于CDO,我以前也讲过这里不展开了:【UE·底层篇】一文搞懂StaticClass、GetClass和ClassDefaultObject
有了CDO,我们就可以在不需要管理类创建的情况下,将类的方法绑定到菜单上
在这里插入图片描述
在这里插入图片描述
看起来好像和Static来绑定也没啥本质区别的?别急,如果我说可以查找到所有需要绑定委托的类,并且统一进行绑定呢?
TObjectIterator对象迭代器,可以查找所有对象。把T代入UClass就可以查找所有UClass。然后我们把所有需要绑定的类都继承一个基类,最后用迭代器遍历的时候对UClass进行判断就可以找到所有需要绑定委托的类了。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述


设计数据结构

找UClass也有了,绑定方法也实现了。现在需要的是用合适的数据结构把这些类统筹起来。并且需要对指定路径进行解析。比如说菜单路径是“First/Second/Third/Button”,另一个是“A/B/C/D”。怎么让“First"和"A"在调用AddPullDownMenu的时填充进去,“Button”和“D”在调用AddMenuEntry的时候填充进去,其他中间的在调用AddSubMenu的时候填充进去。
很显然这是一个树形结构加上一个数组结构在这里插入图片描述
对于“First”和“A”这种要添加到菜单栏的字符串来说,它们应该是树的根节点。然后再用一个数组把所有根节点连接起来。节点类MenuItemNode结构如下:
在这里插入图片描述
然后在MenuManger添加FMenuItemNode的数组记录根节点。添加构造树的方法。
在这里插入图片描述
接着在MenuItem添加路径、菜单名称、菜单提示、初始化函数。
在这里插入图片描述
然后是AddMenuItemToNodeList的实现:

//查找节点
static FMenuItemNode* FindMenuItemNode(TArray<FMenuItemNode>& MenuNodes, const FString& MenuName)
{
	for (FMenuItemNode& Node : MenuNodes)
	{
		if (Node.NodeName == MenuName)
		{
			return &Node;
		}
	}
	return nullptr;
}
void UMenuManager::AddMenuItemToNodeList(UMenuItem* MenuItem)
{
	if (MenuItem == nullptr)
	{
		return;
	}
	//将路径ABCD分解,按顺序存储数组
	TArray<FString> MenuNames;
	FString Path = MenuItem->GetMenuPath();
	if (Path.IsEmpty())
	{
		return;
	}
	FString Left;
	while (Path.Split("/", &Left, &Path))
	{
		if (Left.IsEmpty())
		{
			continue;
		}
		MenuNames.Add(Left);
	}
	MenuNames.Add(Path);
	//查找根节点,没有则创建
	FMenuItemNode* RootMenuNode = FindMenuItemNode(RootNodeList, MenuNames[0]);
	if (RootMenuNode == nullptr)
	{
		FMenuItemNode MenuItemNode;
		MenuItemNode.NodeName = MenuNames[0];
		int32 Index = RootNodeList.Add(MenuItemNode);
		RootMenuNode = &RootNodeList[Index];
	}
	//根据上面记录的字符串数组循环查找,没有则创建节点
	FMenuItemNode* ParentNode = RootMenuNode;
	for (int i = 1; i < MenuNames.Num(); ++i)
	{
		FString& ChildName = MenuNames[i];
		FMenuItemNode* ChildNode = FindMenuItemNode(ParentNode->Children, ChildName);
		if (ChildNode == nullptr)
		{
			FMenuItemNode MenuItemNode;
			MenuItemNode.NodeName = ChildName;
			int32 Index = ParentNode->Children.Add(MenuItemNode);
			ChildNode = &ParentNode->Children[Index];
		}
		ParentNode = ChildNode;
	}
	//最后给叶子节点赋值MenuItem的指针
	ParentNode->MenuItem = MenuItem;
}

注册菜单栏

最后回到MenuManger,首先是在初始化函数里构造树。
在这里插入图片描述
重新编写AddMenuBarExtensionAddMenuExtension方法。对于根节点,调用AddPullDownMenu,对于根节点以外的节点则需要判断是叶子节点还是父节点。是叶子节点则调用MenuEntry注册委托。
在这里插入图片描述
这样就大功告成了,以后想注册新的菜单栏,只需要继承自UMenuItem,调用Init函数注册路径,然后在OnMenuClick里写逻辑即可。
在这里插入图片描述


学习资料


关于作者

  • 水曜日鸡,喜欢ACG的游戏程序员。曾参与索尼中国之星项目《硬核机甲》的开发。 目前在某大厂做UE4项目。

CSDN博客:https://blog.csdn.net/j756915370
知乎专栏:https://zhuanlan.zhihu.com/c_1241442143220363264
游戏同行聊天群:891809847

  游戏开发 最新文章
6、英飞凌-AURIX-TC3XX: PWM实验之使用 GT
泛型自动装箱
CubeMax添加Rtthread操作系统 组件STM32F10
python多线程编程:如何优雅地关闭线程
数据类型隐式转换导致的阻塞
WebAPi实现多文件上传,并附带参数
from origin ‘null‘ has been blocked by
UE4 蓝图调用C++函数(附带项目工程)
Unity学习笔记(一)结构体的简单理解与应用
【Memory As a Programming Concept in C a
上一篇文章      下一篇文章      查看所有文章
加:2021-12-06 15:34:42  更:2021-12-06 15:35:13 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/27 20:32:48-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码