Создание нодовых редакторов в Unity

 Периодически при разработке игр встаёт задача создания дополнительного инструмента для создания каких-либо ассетов, которые удобно выражать в виде графа: диалогов, квестов, деревьев поведения, машин состояний. Для этого удобно использовать нодовые редакторы и юнити предоставляет api для создания таких.

Заранее отмечу, что все события должны разворачиваться в директории Editor, потому что мы не хотели бы, чтобы редакторы попали в финальный билд. Да и при сборке игры не будет доступно пространство имён UnityEditor.

Ссылка на репозиторий с игрой, для которой я писал редактор, из которого приводятся примеры.

Окно редактора

Для того, чтобы Unity понял, что мы делаем новое окно редактора, необходимо сделать класс, наследующийся от EditorWindow. В его методе OnEnable будет происходить логика инициализации окна. Там можно сделать, например, кнопки (UnityEditor.UIElements.Button) в тулбаре (UnityEditor.UIElements.Toolbar). Главное не забывать добавлять все элементы детьми к rootVisualElement или другим элементам. Там же необходимо инициализировать объект, отвечающий непосредственно за редактор нод.

Так бы выглядело у вас окно редактора если бы
я рассказал, как его можно открыть 

Поле для нод

Для того, чтобы появилось поле, на котором потом можно будет перетаскивать ноды, необходимо создать ещё один класс, наследующийся от GraphView. В его констукторе необходимо будет выполнить ряд движений бубном, чтобы добавить реализованный в недрах юнити функционал выделения и перемещения нод, масштабирования поля, а так же отобразить красивую сетку на заднем плане.

this.SetupZoom(ContentZoomer.DefaultMinScale, ContentZoomer.DefaultMaxScale);

this.AddManipulator(new ContentDragger());
this.AddManipulator(new SelectionDragger());
this.AddManipulator(new RectangleSelector());

var grid = new GridBackground();
Insert(0, grid);
grid.StretchToParentSize();

Чтобы сетка выглядела симпатично, можно наложить на неё стили. Для этого надо добавить к ней стайлшит, в котором переопределить параметры --grid-background-color--line-color--thick-line-color и --spacing для элемента GridBackground 

Окно приняло бы следующий вид, но я продолжаю
 игнорировать принцип его создания

Ноды

Для каждого вида нод стоит сделать класс, наследующийся от Node. Внутри ноды по умолчанию есть куча контейнеров, например: titleContainer, inputContainer и outputContainer. В них можно класть любые элементы, например кнопки, пины (к которым будут подсоединяться дуги) и  так далее.

Для того, чтобы контроллировать, какие пины могут соединяться, необходимо перегрузить в классе, унаследованном от GraphView метод со следующей сигнатурой:

List<Port> GetCompatiblePorts(Port startPort, NodeAdapter nodeAdapter)

Этот метод принимает пин и должен возвращать список пинов, с которыми он может соединяться. Разумно сделать следующие проверки: совместимость типов двух пинов (чтобы нельзя было пин с логическим типом подключить к пину со строковым типом, если этого не предусматривает логика приложения), убедиться, что мы не подключаем узел к себе самому, а так же любые другие проверки, которые нам подсказывает доменная логика (например в редакторе диалогов можно проверить, что мы подключаем вопросы к ответам и наоборот, а не делаем два вопроса подряд).
Красиво раскрашеные ноды позволят при использованни 
сразу примерно понимать, что происходит на экране

Инстанцирование редактора

Для того, чтобы от такого редактора был толк, нужно научиться открывать окошко с ним. Для этого есть много способов, здесь я хочу рассмотреть кастомные редакторы для ScriptableObject.

Для этого надо создать новый класс, унаследовать его от Editor. Навесить на него атрибут [CustomEditor(typeof(TYPE_WE_WANT_TO_EDIT))]. В его методе OnInspectorGUI() разместим следующий код:

    if (GUILayout.Button("Edit"))
    {
        var editor = EditorWindow.GetWindow<WINDOW_TYPE>();
        editor.INIT((TYPE_WE_WANT_TO_EDIT)target, ...);
    }
    DrawDefaultInspector();

Где WINDOW_TYPE это тип нашего класса, унаследованного от EditorWindow, а метод  инициализирует объект редактора всеми необходимыми данными.


Наконец мы можем нажать кнопку Edit и открыть
наш новенький редактор