UnigineEditor
Interface Overview
Assets Workflow
Settings and Preferences
Working With Projects
Adjusting Node Parameters
Setting Up Materials
Setting Up Properties
Landscape Tool
Using Editor Tools for Specific Tasks
FAQ
Programming
Fundamentals
Setting Up Development Environment
Usage Examples
UnigineScript
C++
C#
UUSL (Unified UNIGINE Shader Language)
File Formats
Rebuilding the Engine and Tools
GUI
Double Precision Coordinates
API
Containers
Common Functionality
Controls-Related Classes
Engine-Related Classes
Filesystem Functionality
GUI-Related Classes
Math Functionality
Node-Related Classes
Objects-Related Classes
Networking Functionality
Pathfinding-Related Classes
Physics-Related Classes
Plugins-Related Classes
Rendering-Related Classes

Creating Your First Editor Plugin

This article describes how to create an Editor plugin and get the first impression of what a plugin consists of and what its general structure is.

Warning
This feature is an experimental one and is not recommended for production use.

UnigineEditor is an application written entirely in C++ relying a lot on the Qt5 framework infrastructure. So, in order to extend its functionality not only C++ programming skills are required, but you should also be familiar with the Qt5 framework, CMake build system.

Notice
Qt Framework version 5.12+ is required to develop plugins for UnigineEditor.

As an example we shall create a simple plugin implementing the basic functionality of the Editor's Materials Hierarchy window (see the picture below).

Materials Plugin

Materials Plugin

Basically the plugin's architecture looks as follows: Materials Plugin Architecture

Materials Plugin

The MaterialsPlugin class is an initialization point and complete representation of the plugin. It creates necessary data model and via the base interface associates it with the widget, which displays the hierarchy. The model directly interacts with the Materials Manager and transforms information on materials to the required structure.

The plugin interacts will all Editor's external subsystems:

  • Adds a widget to the WindowManager;
  • Adds a new item to the main menu bar;
  • Updates the state of materials widget when the global selection is changed;
  • Updates the global selection when the local selection is changed by sending a SelectionAction to the Undo stack.

See Also#

File Structure#

The typical structure of a plugin project should look as follows:

  • project
    • bin - Engine and Editor binaries
      • Unigine_x64.dll/so - Engine library (release).
      • Unigine_x64d.dll/so - Engine library (debug).
      • EditorCore_x64.dll/so - Editor library (release).
      • EditorCore_x64d.dll/so - Editor library (debug).
    • lib - import libraries (Windows only)
      • Unigine_x64.lib - Engine (release).
      • Unigine_x64d.lib - Engine (debug).
      • EditorCore_x64.lib - Editor (release).
      • EditorCore_x64d.lib - Editor (debug).
    • include - public API
      • unigine - Engine's public API.
      • editor - Editor's public API.
    • cmake
    • src - plugin source files
      • Plugin.h
      • Plugin.cpp
      • Plugin.json.in
      • CMakeList.txt
    • CMakeList.txt

The basic components are:

  • Engine and Editor binary files;
  • Engine and Editor public API header files;
  • Plugin source files;
  • Meta-json file;
  • Template CMake-scripts;

Using Plugin Template#

UNIGINE offers you a template to simplify creation of Editor plugins. The template can be found in the following folder: <UNIGINE_SDK>/utils/project/template/editor-plugin

First, we should create a folder for our new Editor plugin project. Then, simply copy all necessary files from the SDK installation directory to this new created folder as shown in the figure below:

Notice
If you're creating a project with double precision, the corresponding files to be copied to bin and lib folders must be with the _double suffix.
Copying Files and Folders

Implementing Plugin's Logic#

Now we can implement our plugin's logic (you can simply copy source code listed below to the corresponding header and implementation files).

Notice
Put all header and implementation files of your plugin to the src folder. In our example we can delete template Plugin.h and Plugin.cpp files, as we're going to use other sources.

Plugin Class#

The files MaterialsPlugin.h and MaterialsPlugin.cpp define the implementation of our plugin. Let's highlight some important points here.

Header File#

The header file MaterialsPlugin.h defines the interface of the plugin class.

Source code (C++)
namespace Materials {

The plugin is defined in a Materials namespace, which conforms to the coding rules for namespacing in sources.

Source code (C++)
class MaterialsPlugin : public QObject, public ::Editor::Plugin
{
	Q_OBJECT
	Q_PLUGIN_METADATA(IID "com.unigine.EditorPlugin" FILE "Materials.json")
	Q_INTERFACES(Editor::Plugin)

All Editor plugins must be derived from Editor::Plugin class and are QObjects. The Q_PLUGIN_METADATA macro is necessary to create a valid Editor plugin. The IID given in the macro must be com.unigine.EditorPlugin, to identify it as an Editor plugin, and FILE must point to the plugin's meta data file as described in Plugin Meta Data.

Source code (C++)
bool init() override;
void shutdown() override;

The base class defines basic functions that are called during the life cycle of a plugin (on initialization and shutdown), which are here implemented for your new plugin.

Our plugin has three additional custom slots, that are used show the view, set global selection and update the local one when the global is changed.

Source code (C++)
public slots:
	void showView();
	void setGlobalSelection(const QModelIndexList &indexes);
	void globalSelectionChanged();

MaterialsPlugin.h

Source code (C++)
#pragma once

#include <Plugin.h>

#include <QObject>
#include <QModelIndex>

namespace Materials {
class MaterialsModel;
class MaterialsView;
}

namespace Materials {

class MaterialsPlugin : public QObject, public ::Editor::Plugin
{
	Q_OBJECT
	Q_PLUGIN_METADATA(IID "com.unigine.EditorPlugin" FILE "Materials.json")
	Q_INTERFACES(Editor::Plugin)
public:
	MaterialsPlugin();
	~MaterialsPlugin() override;

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

public slots:
	void showView();
	void setGlobalSelection(const QModelIndexList &indexes);
	void globalSelectionChanged();

private:
	MaterialsModel *model_;
	MaterialsView *view_;

};

} // namespace Materials

Implementation File#

This file contains the actual implementation of our plugin and its logic. For more information about implementing the plugin interface, see the Editor::Plugin API documentation and Plugin Life Cycle.

MaterialsPlugin.cpp

Source code (C++)
#include "MaterialsPlugin.h"
#include "MaterialsView.h"
#include "MaterialsModel.h"

#include "QtMetaTypes.h"

#include <Actions.h>
#include <Undo.h>
#include <Constants.h>
#include <WindowManager.h>
#include <Selection.h>
#include <Selector.h>

#include <QObject>
#include <QMenu>

#include <algorithm>
#include <iterator>


using Editor::Selection;
using Editor::SelectionAction;
using Editor::WindowManager;
using Editor::SelectorGUIDs;

namespace Materials
{


MaterialsPlugin::MaterialsPlugin()
	: model_ (nullptr)
	, view_ (nullptr)
{
}

MaterialsPlugin::~MaterialsPlugin() = default;

bool MaterialsPlugin::init()
{
	model_ = new MaterialsModel(this);

	view_ = new MaterialsView(model_);
	view_->setWindowTitle("Plugin - Material List");
	view_->setObjectName("PluginMaterialsView");
	WindowManager::add(view_, WindowManager::ROOT_AREA_LEFT);
	connect(view_, &MaterialsView::selected, this, &MaterialsPlugin::setGlobalSelection);

	QMenu *menu = WindowManager::findMenu(Constants::MM_WINDOWS);
	menu->addAction("Plugin - Material List", this, &MaterialsPlugin::showView);

	connect(Selection::instance(), &Selection::changed,
			this, &MaterialsPlugin::globalSelectionChanged);

	return true;
}

void MaterialsPlugin::shutdown()
{
}

void MaterialsPlugin::showView()
{
	WindowManager::show(view_);
}

void MaterialsPlugin::setGlobalSelection(const QModelIndexList &indexes)
{
	disconnect(Selection::instance(), &Selection::changed,
			this, &MaterialsPlugin::globalSelectionChanged);

	QVector<Unigine::UGUID> guids;
	guids.reserve(indexes.size());
	std::transform(std::begin(indexes), std::end(indexes), std::back_inserter(guids),
			[](const QModelIndex &idx)
			{ return idx.data(MaterialsModel::GUID_ROLE).value<Unigine::UGUID>(); });
	SelectionAction::applySelection(SelectorGUIDs::createMaterialsSelector(guids));

	connect(Selection::instance(), &Selection::changed,
			this, &MaterialsPlugin::globalSelectionChanged);
}

void MaterialsPlugin::globalSelectionChanged()
{
	using namespace std;

	if (auto selector = Selection::getSelectorMaterials())
	{
		const QVector<Unigine::UGUID> guids = selector->guids();
		QModelIndexList indexes;
		indexes.reserve(guids.size());
		transform(begin(guids), end(guids), back_inserter(indexes),
				[this](const Unigine::UGUID &guid) { return model_->index(guid); });

		auto it = remove(begin(indexes), end(indexes), QModelIndex());
		indexes.erase(it, end(indexes));

		QSignalBlocker blocker(view_);
		view_->globalSelectionChanged(indexes);
	}
	else if(auto selector = Selection::getSelectorRuntimes())
	{
	
	}
	else
	{
		view_->clearSelection();
	}
}


} // namespace Materials

Model Class#

Header File#

MaterialsModel.h

Source code (C++)
#pragma once
#include <QAbstractItemModel>

namespace Unigine { class UGUID; };
namespace Materials
{

class MaterialsModel : public QAbstractItemModel
{
	class Item;
public:
	enum Role
	{
		GUID_ROLE = Qt::UserRole + 1
	};

	MaterialsModel(QObject *parent = nullptr);
	~MaterialsModel() override;

	QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override;
	QModelIndex index(const Unigine::UGUID &guid) const;
	QModelIndex parent(const QModelIndex &child) const override;
	int rowCount(const QModelIndex &parent = QModelIndex()) const override;
	int columnCount(const QModelIndex &parent = QModelIndex()) const override;
	QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;

protected:
	void fetchMore(const QModelIndex &parent) override;
	bool canFetchMore(const QModelIndex &parent) const override;

private:
	Item *root_;
};

} // namespace Materials

Implementation File#

MaterialsModel.cpp

Source code (C++)
#include "MaterialsModel.h"
#include <UnigineGUID.h>
#include <UnigineMaterials.h>
#include "QtMetaTypes.h"

#include <QDebug>

using Unigine::UGUID;

namespace Materials
{

////////////////////////////////////////////////////////////////////////////////
// MaterialsModel::Item.
////////////////////////////////////////////////////////////////////////////////
class MaterialsModel::Item
{
public:
	static Item *createRootItem(MaterialsModel *model);

	Item(Item *parent, MaterialsModel *model, const UGUID &guid);
	~Item();

	QModelIndex index() const;

	void append(Item *item);
	Item *child(int idx);
	Item *find(const UGUID &guid) const;
	int childCount() const;

	Item *parent();
	int row() const;
	QVariant data(int role) const;

	void fetchMore();
	bool canFetchMore() const;

private:
	Item *parent_ = nullptr; // Not owned.
	MaterialsModel *model_ = nullptr; // Not owned.
	QVector<Item *> children_; // Owned.
	UGUID guid_;
};

MaterialsModel::Item *MaterialsModel::Item::createRootItem(MaterialsModel *model)
{
	Item *root = new Item(nullptr, model, UGUID());
	const int size = Unigine::Materials::getNumMaterials();
	for (int i = 0; i < size; ++i)
	{
		const Unigine::MaterialPtr mat = Unigine::Materials::getMaterial(i);
		if (mat->isHidden())
		{
			continue;
		}
		if (const Unigine::MaterialPtr &parent_mat = mat->getParent())
		{
			if (!parent_mat->isHidden())
			{
				continue;
			}
		}
		root->children_.push_back(new Item(root, model, mat->getGUID()));
	}
	return root;
}

MaterialsModel::Item::Item(Item *parent, MaterialsModel *model, const UGUID &guid)
	: parent_(parent), model_(model), guid_(guid)
{
}

MaterialsModel::Item::~Item()
{
	qDeleteAll(children_);
}

QModelIndex MaterialsModel::Item::index() const
{
	if (!parent_ || guid_.isEmpty())
	{
		return QModelIndex();
	}
	return model_->createIndex(row(), 0, const_cast<Item *>(this));
}

void MaterialsModel::Item::append(Item *item)
{
	const int size = children_.size();
	model_->beginInsertRows(index(), size, size);
	item->parent_ = this;
	children_.push_back(item);
	model_->endInsertRows();
}

auto MaterialsModel::Item::child(int idx) -> Item *
{
	return ((idx >= 0) && (idx < children_.size()))
		? children_[idx]
		: nullptr;
}

auto MaterialsModel::Item::find(const UGUID &guid) const -> Item *
{
	for (Item *child: children_)
	{
		if (child->guid_ == guid)
		{
			return child;
		}
		if (child->canFetchMore())
		{
			child->fetchMore();
		}
		if (Item *grandchild = child->find(guid))
		{
			return grandchild;
		}
	}
	return nullptr;
}

int MaterialsModel::Item::childCount() const
{
	if (!guid_.isValid())
	{
		return children_.size();
	}

	const Unigine::MaterialPtr mat = Unigine::Materials::findMaterialByGUID(guid_);
	int count = mat->getNumChildren();
	for (int i = count - 1; i >= 0; --i)
	{
		if (mat->getChild(i)->isHidden())
		{
			--count;
		}
	}
	return count;
}

MaterialsModel::Item *MaterialsModel::Item::parent()
{
	return parent_;
}

int MaterialsModel::Item::row() const
{
	if (parent_)
	{
		return parent_->children_.indexOf(const_cast<Item *>(this));
	}
	return 0;
}

QVariant MaterialsModel::Item::data(int role) const
{
	if (Qt::DisplayRole == role)
	{
		const Unigine::MaterialPtr &mat = Unigine::Materials::findMaterialByGUID(guid_);
		return QVariant::fromValue(QString(mat->getName()));
	}
	if (MaterialsModel::GUID_ROLE == role)
	{
		return QVariant::fromValue(guid_);
	}
	return QVariant();
}

void MaterialsModel::Item::fetchMore()
{
	const Unigine::MaterialPtr &mat = Unigine::Materials::findMaterialByGUID(guid_);
	const int size = mat->getNumChildren();
	QVector<Item *> items;
	items.reserve(size);

	for (int i = 0; i < size; ++i)
	{
		const Unigine::MaterialPtr child_mat = mat->getChild(i);
		if (child_mat->isHidden())
		{
			continue;
		}
		items.push_back(new Item(this, model_, child_mat->getGUID()));
	}

	if (items.empty())
	{
		return;
	}

	const int cur_size = children_.size();
	model_->beginInsertRows(index(), cur_size, cur_size + items.size() - 1);
	std::copy(std::begin(items), std::end(items), std::back_inserter(children_));
	model_->endInsertRows();
}

bool MaterialsModel::Item::canFetchMore() const
{
	return children_.empty() && childCount();
}

////////////////////////////////////////////////////////////////////////////////
// MaterialsModel.
////////////////////////////////////////////////////////////////////////////////
MaterialsModel::MaterialsModel(QObject *parent)
	: QAbstractItemModel(parent)
	, root_(Item::createRootItem(this))
{
}

MaterialsModel::~MaterialsModel()
{
	delete root_;
}

QModelIndex MaterialsModel::index(int row, int column, const QModelIndex &parent) const
{
	if (column > 0)
	{
		return {};
	}

	Item *parent_item = nullptr;
	if (!parent.isValid())
	{
		parent_item = root_;
	}
	else
	{
		parent_item = static_cast<Item *>(parent.internalPointer());
	}

	Item *child_item = parent_item->child(row);
	return child_item
		? createIndex(row, column, child_item)
		: QModelIndex();
}

QModelIndex MaterialsModel::index(const UGUID &guid) const
{
	if (guid.isEmpty())
	{
		return QModelIndex();
	}
	Item *item = root_->find(guid);
	return item ? item->index() : QModelIndex();
}

QModelIndex MaterialsModel::parent(const QModelIndex &child) const
{
	if (!child.isValid())
	{
		return QModelIndex();
	}
	Item *child_item = static_cast<Item *>(child.internalPointer());
	Item *parent_item = child_item->parent();

	return (parent_item != root_)
		? createIndex(parent_item->row(), 0, parent_item)
		: QModelIndex();
}

int MaterialsModel::rowCount(const QModelIndex &parent) const
{
	if (parent.column() > 0)
	{
		return 0;
	}

	Item *parent_item = nullptr;
	if (!parent.isValid())
	{
		parent_item = root_;
	}
	else
	{
		parent_item = static_cast<Item *>(parent.internalPointer());
	}

	return parent_item->childCount();
}

int MaterialsModel::columnCount(const QModelIndex &parent) const
{
	return 1;
}

QVariant MaterialsModel::data(const QModelIndex &index, int role) const
{
	if (!index.isValid())
	{
		return QVariant();
	}

	Item *item = static_cast<Item *>(index.internalPointer());
	return item->data(role);
}

void MaterialsModel::fetchMore(const QModelIndex &parent)
{
	if (!parent.isValid())
	{
		return;
	}
	Item *item = static_cast<Item *>(parent.internalPointer());
	item->fetchMore();
}

bool MaterialsModel::canFetchMore(const QModelIndex &parent) const
{
	if (!parent.isValid())
	{
		return false;
	}
	Item *item = static_cast<Item *>(parent.internalPointer());
	return item->canFetchMore();
}

} // namespace Materials

View Class#

Header File#

MaterialsView.h

Source code (C++)
#pragma once


#include <QWidget>
#include <QModelIndex>

namespace Unigine { class UGUID; }
class QAbstractItemModel;
class QItemSelection;
class QTreeView;

namespace Materials
{


class MaterialsView : public QWidget
{
	Q_OBJECT
public:
	MaterialsView(QAbstractItemModel *model, QWidget *parent = nullptr);
	~MaterialsView();

signals:
	void selected(const QModelIndexList &indexes);
	void menuRequested(const QModelIndexList &indexes);

public slots:
	void clearSelection();
	void globalSelectionChanged(const QModelIndexList &indexes);

private slots:
	void selectionChanged(const QItemSelection &selection);

private:
	QTreeView *view_ = nullptr;

};

} // namespace Materials

Implementation File#

MaterialsView.cpp

Source code (C++)
#include "MaterialsView.h"

#include <QAbstractItemModel>
#include <QVBoxLayout>
#include <QTreeView>

namespace Materials
{

MaterialsView::MaterialsView(QAbstractItemModel *model, QWidget *parent)
	: QWidget{parent}
{
	view_ = new QTreeView();
	view_->setAlternatingRowColors(true);
	view_->setSelectionMode(QAbstractItemView::ExtendedSelection);
	view_->setSelectionBehavior(QAbstractItemView::SelectRows);
	view_->setHeaderHidden(true);

	auto vl = new QVBoxLayout(this);
	vl->setContentsMargins(2, 2, 2, 2);
	vl->addWidget(view_);

	view_->setModel(model);

	connect(view_->selectionModel(), &QItemSelectionModel::selectionChanged
			, this, &MaterialsView::selectionChanged);
}

MaterialsView::~MaterialsView() = default;

void MaterialsView::clearSelection()
{
	view_->selectionModel()->clearSelection();
}

void MaterialsView::globalSelectionChanged(const QModelIndexList &indexes)
{
	if (indexes.empty())
	{
		clearSelection();
		return;
	}

	view_->setCurrentIndex(indexes.back());

	QItemSelection item_selection;
	for (const QModelIndex &index: indexes)
	{
		item_selection.append(QItemSelectionRange(index));
	}
	view_->selectionModel()->select(item_selection, QItemSelectionModel::ClearAndSelect);

	view_->scrollTo(indexes.back());
}

void MaterialsView::selectionChanged(const QItemSelection &selection)
{
	emit selected(view_->selectionModel()->selectedIndexes());
}


} // namespace Materials

Auxiliary File#

This file contains registration of additional metatypes to be used in our plugin to make them known to Qt's meta-object system, we need Unigine::UGUID.

Notice
This file should be saved to the src folder with all other source files.

QtMetaTypes.h

Source code (C++)
#pragma once

// including UnigineGUID.h containing definition of the metatype we're going to declare
#include <UnigineGUID.h>

// including QMetaType to be able to declare metatypes
#include <QMetaType>

// registering the Unigine::UGUID type to make it known to Qt's meta-object system
Q_DECLARE_METATYPE(Unigine::UGUID)

Building the Plugin#

In order to build our plugin we also need a couple of files.

We need to create an initial meta data description file (Materials.json.in) to describe our Materials plugin and its dependencies. This file shall be used by CMake to generate the actual plugin .json meta data file.

Materials.json.in

Source code
{
    "Name" : "Materials",
    "Vendor" : "Unigine",
    "Description" : "<p>Provides functionality to overview and manipulate the Engine's materials</p><p>The plugin is used to demonstrate the basic usage of the new `Editor Plugin System` implementation. It contains the main plugin, tree model and tree view instances</p>",
    "Version" : "@PLUGIN_VERSION@",
    "CompatVersion" : "@PLUGIN_COMPAT_VERSION@",
    "Dependencies" : []
}

And we also need to modify the CMake build script src/CMakeList.txt. Regarding the described above file structure it should look like this:

src/CMakeList.txt

Source code
# Substitute placeholders in the plugin's meta file source with actual version data
# and create the final meta file.
if (EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/Materials.json.in)
	set(PLUGIN_VERSION ${UNIGINE_VERSION})
	set(PLUGIN_COMPAT_VERSION ${UNIGINE_VERSION})
	configure_file(
		${CMAKE_CURRENT_SOURCE_DIR}/Materials.json.in
		${CMAKE_CURRENT_BINARY_DIR}/Materials.json
		)
endif()

# Enable automatic handling for moc, uic, and rcc
set(CMAKE_AUTOMOC TRUE)
set(CMAKE_AUTOUIC TRUE)
set(CMAKE_AUTORCC TRUE)

# Find additional required packages
find_package(Qt5 5.12 CONFIG REQUIRED COMPONENTS Gui)
find_package(Qt5 5.12 CONFIG REQUIRED COMPONENTS Widgets)

add_library(Materials SHARED
	# List of sources
	${CMAKE_CURRENT_SOURCE_DIR}/MaterialsPlugin.cpp
	${CMAKE_CURRENT_SOURCE_DIR}/MaterialsPlugin.h
	${CMAKE_CURRENT_SOURCE_DIR}/MaterialsView.cpp
	${CMAKE_CURRENT_SOURCE_DIR}/MaterialsView.h
	${CMAKE_CURRENT_SOURCE_DIR}/MaterialsModel.cpp
	${CMAKE_CURRENT_SOURCE_DIR}/MaterialsModel.h
	)

target_include_directories(Materials SYSTEM
	PRIVATE
	${PROJECT_SOURCE_DIR}/include # <- - - - Path to Editor and Engine libraries
	${CMAKE_CURRENT_BINARY_DIR}
	)

target_link_libraries(Materials
	PRIVATE
	Unigine::CompilerFlags # <- - - - Interface library with predefined compilation flags
	Unigine::Engine   # <- - - - - Engine
	Unigine::Editor   # <- - - - - Public Editor library
	Qt5::Core         #
	Qt5::Gui          # <- - - - - Required Qt components for the plugin
	Qt5::Widgets      # 
	)
# Set output directories for the Materials plugin target
set_target_properties(Materials
	PROPERTIES
	RUNTIME_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/bin
	LIBRARY_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/bin
	ARCHIVE_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/lib
	CXX_VISIBILITY_PRESET hidden
	)

Then we should change the default plugin project name to "Materials" in the CMakeList.txt in the plugin project's root folder:

CMakeList.txt

Source code
# Copyright (C), UNIGINE. All rights reserved.


cmake_minimum_required(VERSION 3.14)
project(Materials CXX) # < - - - Change project name here

include(${PROJECT_SOURCE_DIR}/cmake/Unigine.cmake)
add_subdirectory(${PROJECT_SOURCE_DIR}/src)

Notice
You should also set build type to Release for your generator, either by selecting manually from the IDE, or using --config CONFIG (with cmake --build). In case of a single-config generator (like Unix Makefiles) the currently active configuration is determined by the value of the CMake variable CMAKE_BUILD_TYPE.

Now we can use CMake to build our plugin.

Running the Plugin#

Now as the plugin is built we should copy resulting files to one of the following folders depending on the build type, so that UnigineEditor's Plugin System could load it automatically:

  • %project%/bin/editor — for Release build;
  • %project%/bin/editor_debug — for Debug build.

And finally we launch the UnigineEditor to check our plugin. If everything was done properly, you should see the Materials plugin in the list of loaded plugins (Help -> Plugins).

Plugins List

Last update: 2019-12-02