Unigine使用QtWidgets制作编辑器扩展(Editor Plugin)的简单随笔

Unigine有一点比较烦,就是不支持用C#写Editor Script。由于Unigine Editor是搭建在Qt框架上的(准确点说,是Qt 5.12.3),所以要写插件也得搭建一套Qt的开发环境出来,同时插件也得用C++来写。

好在Qt本身不难,Unigine也只使用到了最基础的Qt控件。虽然是C++但Qt做了不少简化工作,因此有基本C++基础的都能玩得来。QtWidgets的设计也不复杂,C#下面玩过WinForms的很快就能上手。我原来完全不懂Qt,但看了两天文档后就能做东西了,毕竟要做的只是一些编辑器里用的小工具,而不是什么有着复杂界面的应用程序。能摆几个Button,几个CheckBox,几个输入框,很多时候就够了。

先来做准备工作

首选需要去Qt官网注册一个Qt账号,然后下载5.12.3的离线安装包,地址在这里(可能需要挂梯子):https://download.qt.io/archive/qt/5.12/5.12.3/qt-opensource-windows-x86-5.12.3.exe

接着按照Unigine的教程安装好Qt环境,并配置好“UNIGINE_QTROOT”的环境变量。

下一步为项目添加插件支持。之后会发现,项目里不仅多了刚添加的插件项目,还有几个Unigine的示例插件项目。可以将这几个项目都添加到解决方案里,作为参考例子。

注意这些新增的项目的Platform Toolset编译选项都要选择Visual Studio 2015。没有就去Visual Studio Installer里添加一下“VS 2015 C++ Build Tools”。

有一个细节,就是生成的DLL文件,除了要放到bin\plugins\[组织名称]\文件夹下以外,还必须以一个固定的文件名作为结尾,否则Unigine就不认了。规则也不复杂:

debug release
float [插件名称]_editorplugin_float_x64d.dll [插件名称]_editorplugin_float_x64.dll
double [插件名称]_editorplugin_double_x64d.dll [插件名称]_editorplugin_double_x64.dll

Unigine创建好插件项目后,默认就已经将输出的文件名配置好了。如果要自己改,记得遵循这个规则。

搞成这样是因为Unigine有两种Plugin:可以在运行时加载的插件(比如bin\plugins\Unigine文件夹下的那些),以及只能在编辑器里加载的插件。另外还允许单精度项目和双精度项目共
存。

学QtWidgets可以看:Getting Started Programming with Qt Widgets。这个教程是用QtCreator演示的,Visual Studio可以安装这个插件:Qt Visual Studio Tools。把这个例子搞懂了基本也足够做编辑器Plugin的界面了。

想要进一步了解则可以看:Qt Widgets Examples

再看看Unigine提供的示例项目

这其中比较容易上手的是AssetsPlugin和MaterialsPlugin这俩。前者是使用Unigine自己的Gui系统绘制了一堆按钮,同时演示了一下各种不同的Asset操作代码。别看AssetsPlugin.cpp好长一串,除去init和shutdown,剩下的基本上都是那一堆Button的事件处理函数。

拆解开来,总共就四步:

  1. 在init()里,通过WindowManager::findMenu(Constants::MM_WINDOWS)获取编辑器的菜单栏,并向其中添加一个Action。
  2. 在这个Action里,创建Gui窗口并添加事件处理。
  3. 回到init(),订阅WindowManager::windowHidden信号,并将窗口删除。
  4. shutdown()里,同样执行一遍删除窗口操作,并取消所有信号的订阅。

MaterialsPlugin要复杂一些:在MaterialsPlugin::init()里创建MaterialsView对象,而MaterialsView是一个QWidget,其中包含了一个QTreeView。MaterialsModel则是这个TreeView使用的数据格式。

牵扯到TreeView的代码都不简单,但可以先不管它。要注意的还是MaterialsPlugin::init()函数,和AssetsPlugin不一样,这里直接把QWidget创建了出来,然后在菜单栏的Action里使用WindowManager::show()显示。而删除的部分也是在MaterialsPlugin::shutdown()里面直接删除,没有像AssetsPlugin那边在WindowManager::windowHidden信号里做了一个延后删除的动作。

这就是在Unigine里使用QtWidgets唯一的要点了:在init()里创建。

接着搞个窗口试试

用代码手搓一个QMainWindow,然后往里面填一些控件:

class SampleProject_Editor_Plugin final : public QObject, public ::UnigineEditor::Plugin
{
	Q_OBJECT
	Q_DISABLE_COPY(SampleProject_Editor_Plugin)
	Q_PLUGIN_METADATA(IID UNIGINE_EDITOR_PLUGIN_IID FILE "SampleProject_Editor_Plugin.json")
	Q_INTERFACES(UnigineEditor::Plugin)

public:
	SampleProject_Editor_Plugin() = default;

	virtual bool init() override;
	virtual void shutdown() override;

	void ShowMainWindow();
	void OnMainButtonClicked();

private:
	QAction *menuAction{};
	QMainWindow *mainWindow{};
};
bool SampleProject_Editor_Plugin::init()
{
	auto menu = UnigineEditor::WindowManager::findMenu(UnigineEditor::Constants::MM_TOOLS);
	menu->addSeparator();
	menuAction = menu->addAction("Plugin - SampleProjectEditor", this, &SampleProject_Editor_Plugin::ShowMainWindow);

	mainWindow = new QMainWindow();
	mainWindow->setWindowTitle("Plugin - SampleProject_Editor");
	mainWindow->setObjectName("Plugin_SampleProjectEditor");
	mainWindow->resize(200, 300);

	auto centralWidget = new QWidget(mainWindow);
	mainWindow->setCentralWidget(centralWidget);

	auto layoutWidget = new QWidget(centralWidget);
	layoutWidget->setFixedWidth(200);

	auto vLayout = new QVBoxLayout(layoutWidget);
	vLayout->setSpacing(5);
	vLayout->setContentsMargins(5, 5, 5, 5);

	auto checkBox = new QCheckBox(layoutWidget);
	checkBox->setText("CheckBox");
	vLayout->addWidget(checkBox);

	auto label = new QLabel(layoutWidget);
	label->setText("Some input box");
	vLayout->addWidget(label);

	auto textBox = new QLineEdit(layoutWidget);
	textBox->setPlaceholderText("waiting input...");
	vLayout->addWidget(textBox);

	auto button = new QPushButton(layoutWidget);
	button->setText("This Button");
	vLayout->addWidget(button);
	connect(button, &QPushButton::clicked, this, &SampleProject_Editor_Plugin::OnMainButtonClicked);

	auto radioButton1 = new QRadioButton(layoutWidget);
	radioButton1->setText("radioButton1");
	radioButton1->setChecked(true);
	vLayout->addWidget(radioButton1);

	auto radioButton2 = new QRadioButton(layoutWidget);
	radioButton2->setText("radioButton2");
	vLayout->addWidget(radioButton2);

	return true;
}

void SampleProject_Editor_Plugin::shutdown()
{
	disconnect(UnigineEditor::WindowManager::instance(), nullptr, this, nullptr);

	if (menuAction != nullptr) {
		auto menu = UnigineEditor::WindowManager::findMenu(UnigineEditor::Constants::MM_TOOLS);
		menu->removeAction(menuAction);
		menuAction = nullptr;
	}

	if (mainWindow != nullptr) {
		delete mainWindow;
		mainWindow = nullptr;
	}
}

void SampleProject_Editor_Plugin::ShowMainWindow()
{
	if (mainWindow != nullptr) {
		mainWindow->show();
	}
}

void SampleProject_Editor_Plugin::OnMainButtonClicked()
{
	QMessageBox::information(mainWindow, "Plugin", "Some message");
}

没什么特别的,就是普通的QMainWindow然后里面一个QVBoxLayout,再摆了几个纯演示的控件。一般这类代码都会由QtCreator生成,不过需求没那么复杂的话,手搓问题也不大。

编译,运行,然后点击编辑器的Tools -> Plugin - SampleProjectEditor,就能看见下面这样的窗口:

QMainWindow

到这一步,熟悉Qt的就已经懂了。QMainWindow都有了,那接下来想干啥还不是随自己的意了么😁

想和Unigine整合的更紧密一点,就仿照MaterialsPlugin项目的做法,不用QMainWindow,而是只用QWidget做基础,然后用WindowManager::add()添加到Unigine窗口管理器内,再用WindowManager::show()显示出来即可。

想要用上QtCreator也不难,创建.ui文件,然后在其Propery Page里将Item Type改成Custom Build Tool,接着手动调用uic生成UI代码即可。具体的命令行可以仿照Unigine的做法:右击AssetsPlugin.h查看它的Property Page,会发现这里就是通过Custom Build Tool,手动调用了moc.exe生成代码的。

posted @ 2025-10-09 12:19  horeaper  阅读(15)  评论(0)    收藏  举报