如何擴展編輯器功能
UNIGINE 使您能够扩展 UnigineEditor 的核心功能,例如,添加新的菜单、窗口、工具栏命令、子模式,定义属性的显示方式,甚至创建您的自定义编辑器。 UnigineEditor 使这成为可能插件系统通过添加自定义用户插件.
UnigineEditor 是一个完全用 C++ 编写的应用程序,很大程度上依赖于 Qt5 框架基础架构。因此,为了扩展其功能,不仅需要 C++ 编程技能,还应该熟悉 Qt5 框架、CMake 构建系统。
也可以看看#
- 创建您的第一个编辑器插件文章了解如何创建自定义编辑器插件。
- 编辑器 API 参考以获取有关所有可用类的更多信息。
编辑器插件系统#
插件是代码和数据的集合,开发人员可以在 UnigineEditor 中根据每个项目轻松启用或禁用它们。每个插件都被编译成一个单独发布的动态库,UnigineEditor 在运行时检测并加载它。
插件系统的基本结构如下:
以下是主要组件的简要说明:
- QPluginLoader 负责插件加载和提供元数据。
- Editor::PluginInfo — 插件元数据解析和存储,与插件接口的交互,它还包含当前插件状态和可能的插件初始化错误的信息。
- Editor::PluginManager — 一个管理器类,负责定位插件、构建插件加载队列以及加载和删除插件。
-
Editor::Plugin — 所有编辑器插件继承的基本类,它具有以下声明:
Editor::Plugin 类声明
namespace Editor { class EDITOR_API Plugin { public: Plugin(); virtual ~Plugin(); // Plugin's life cycle. virtual bool init() = 0; virtual void shutdown() = 0; }; } // namespace Editor // Associates the given Identifier (a string literal) to the interface class called Editor::Plugin. Q_DECLARE_INTERFACE(Editor::Plugin, "com.unigine.EditorPlugin")
它有两个定义插件的抽象方法生命周期,您应该为您的自定义插件覆盖它们:
- init() — 插件初始化(返回初始化结果)
- shutdown() — 插件关闭
Q_DECLARE_INTERFACE 宏将接口添加到 Qt 的元系统。
定位插件#
其库位于下面给出的目录中的插件会自动加载并添加到可用的 UnigineEditor 插件列表中,不需要特定代码:
- %project%/bin/editor — 用于 Release 构建和任何 SDK 构建。
- %project%/bin/editor_debug — 用于在开发人员的 PC 上构建的 Debug。
要查看 UnigineEditor 中所有当前加载的插件的列表,请选择 Help -> Plugins。
您可以通过在列表中选择并单击 Details 查看任何插件的描述。
如果加载插件时发生错误,描述窗口会显示详细信息。
插件元数据#
每个插件都必须有插件管理器需要的附加信息才能找到您的插件并解决它依赖关系在实际加载插件的库文件之前。 此信息以 JSON 格式存储在元文件中(将编辑器插件模板添加到项目时自动生成 myplugin.json 文件),并且可以在运行时从 Editor::PluginInfo 类的实例中获取。此元文件可能如下所示:
{
"Name" : "MyPlugin",
"Vendor" : "Unigine",
"Description" : "The plugin's description text." ,
"Version" : "@PLUGIN_VERSION@",
"CompatVersion" : "@PLUGIN_COMPAT_VERSION@"
"Dependencies" : [
{
"Name": "RequiredPlugin",
"Type": "required",
"Version" : "2.9.0.0"
},
{
"Name": "OptionalPlugin",
"Type": "optional",
"Version" : "2.8.0"
}
]
}
以下是基本要素的简要概述:
- Name — UnigineEditor 的插件列表中显示的插件名称,在描述其他插件依赖项时用作参考。
- Vendor, Description — 附加信息(可选)。
- Version — 当前插件版本。
- CompatVersion — 最后一个二进制兼容插件版本,定义当前版本与该插件的哪个版本二进制向后兼容,并用于解决对该插件的依赖关系。
- Dependencies — 描述对其他插件的依赖关系的对象列表。
- Name — 该插件所依赖的插件名称。
- Type — 依赖类型,可以是 required 或 optional。
- Version — 插件必须兼容的版本才能填充依赖,格式为 xyz 如果版本无关紧要可以为空。
实际上,作者创建了一个 .json.in 文件,然后 CMake 使用该文件生成实际的插件 .json 元数据文件,将 EDITOR_VERSION 等变量替换为其实际值.
插件依赖#
一个插件可以依赖于其他插件。插件元数据中指定了此类依赖项,以确保在此之前加载这些其他插件。
依赖项使用键 Dependency 声明,该键包含 JSON 对象数组,所需键为 Name 和 Version,可选键为 Type。
下面的公式说明了依赖信息是如何匹配的。在公式中,所需插件的名称(在依赖对象的 Name 中定义)表示为 DependencyName,所需的插件版本表示为 DependencyVersion。如果满足以下条件,则插件元数据中定义的具有给定 Name, Version 和 CompatVersion 的插件与依赖项匹配:
- 它的 Name 匹配 DependencyName。
- CompatVersion <= DependencyVersion <= Version
例如,一个依赖
{
"Name" : "SomeOtherPlugin",
"Version" : "2.4.1"
}
将由一个插件匹配
{
"Name" : "SomeOtherPlugin",
"Version" : "3.1.0",
"CompatVersion" : "2.2.0",
...
}
因为名称匹配,并且依赖标记中给出的版本 2.4.1 位于 2.2.0 和 3.1.0 之间。
插件生命周期#
为了能够编写编辑器插件,您必须了解插件管理器在您启动或关闭 UnigineEditor. 时所采取的步骤
当您启动 UnigineEditor时,插件管理器会执行以下操作:
- 在其搜索路径中查找所有动态库,并读取它们的元数据。所有没有元数据的库和没有 com.unigine.EditorPlugin IID 的库都将被忽略。所有插件的初始状态是 INVALID,因为这是在元数据格式错误的最坏情况下加载插件可能失败的第一个点。
- 为每个插件创建一个 Editor::PluginInfo 类的实例。作为插件元数据的容器,此类还跟踪当前插件状态。您可以通过 Editor::PluginManager::plugins() 函数获取 Editor::PluginInfo 实例。
- 将插件设置为 READ 状态。
- 验证该依赖关系每个插件都存在并且兼容。
- 将插件设置为 RESOLVED 状态。
- 将所有插件排序到一个称为“加载队列”的列表中,其中插件的依赖项位于插件之后(但不一定直接在它之后)。它确保插件以正确的顺序加载和初始化。
- 加载插件的库,并按照加载队列的顺序创建它们的 Editor::Plugin 实例。此时调用插件构造函数。首先创建其他插件依赖的插件。
- 将插件设置为 LOADED 状态。
-
按照加载队列调用所有插件的init()函数。在 init() 函数中,插件应确保所有导出的接口都已设置并可用于其他插件。由于每个插件都假定它所依赖的插件已经设置了它们的导出接口。
插件init()函数是一个好地方:- 创建新对象
- 加载设置
- 添加新菜单和新操作
- 连接到其他插件的信号
- 将插件设置为 RUNNING 状态。
在UnigineEditor 关闭时,插件管理器开始其关闭序列:
- 按照加载队列的顺序调用所有插件的 shutdown() 函数。插件应该在此处执行加速实际关闭的措施,例如断开否则会不必要地调用的信号。
- 通过以加载队列的相反顺序删除它们的 Editor::Plugin 实例来销毁所有插件。此时会调用插件析构函数。插件应该通过释放内存和其他资源自行清理。
热重载#
编辑器插件支持热重新加载,这在您需要执行以下操作时很有用:
- 卸载插件。
- 做一点事。
- 重新加载插件。
您可以在必要时通过 HotReload 列中的相应按钮再次卸载和加载插件(可通过 Editor Plugins 窗口访问:Help -> Plugins)。
您也可以通过代码执行相同的操作:
Unigine::Vector<Editor::PluginInfo *> plugin_infos = Editor::PluginManager::plugins();
// choose your `PluginInfo*`:
Editor::PluginInfo *required_plugin_info = ..;
// Unload a plugin:
bool unloaded = Editor::PluginManager::unloadPlugin(required_plugin_info);
// Do something
// ...
// Reload the plugin
bool loaded = Editor::PluginManager::loadPlugin(required_plugin_info);