- PVSM.RU - https://www.pvsm.ru -
Здравствуйте уважаемые читатели, в сегодняшней статье я хотел бы остановиться на таком феномене в разработке приложений на Unity3d, как визуальная разработка или если бы точнее, разработка с применением визуального представления кода и логики. И, прежде чем продолжить хочу сразу уточнить, речь не идет о визуальном программировании, от слова “совсем”, никаких Blueprint вариаций в мире Unity и никаких генераций C# кода. Так что же тогда подразумевается под визуальным редактором логики? Если вам интересен ответ на данный вопрос добро пожаловать под кат.
Очень часто, а некоторые утверждают что всегда, в ходе разработки программисты пишут много разного кода, который делает много разных вещей, от системных, до механик игрового процесса. Этот код, если программист “настоящий”, как правило делается унифицированным и изолированным, чтобы его можно было использовать повторно (в рамках Unity этим кодом являются компоненты, они же наследники MonoBehavior). Не трудно представить, что такого кода, может быть очень много, особенно, если это не первый проект. А теперь представим себе, что мы начинаем новый проект и нужно сделать много быстрых и разных прототипов, а команда программистов ограничена, и вся занята основным проектом или проектами. Гейм-дизайнеры негодуют, им надо тестировать, проверять, продюсеры бегают по менеджерам, пытаясь выцепить для себя программиста, деньги ограничены, время уходит и т. п. и т. д.
Другая сторона медали, мы (программисты) написали много кода в виде компонентов, они висят большим списком на разных объектах сцены. И вот мы расширяем команду, наняли нового программиста, он открывает сцену, чтобы разобраться и тонет в каше вопросов: кто кого вызывает, в каком порядке, какой компонент с каким связан и каким образом и т. п. Вы резонно скажите: — “ А документация?” Документация есть (хотя вовсе не факт), но тут речь идет о том, чтобы порог вхождения новых людей в команду был максимально низким, и время на этот процесс максимально коротким.
Как же решить описанные выше ситуации? Ответ в названии статьи – Визуальный Редактор Логики. Что же это такое? Это среда, которая позволяет в визуальном виде оперировать различными компонентами логики и настраивать их взаимосвязи (в “мягком” варианте), а также опосредованно от сцены оперировать объектами сцены. Если описать это в сказочно-простом виде, то это как в детстве собирать из кубиков разные конструкции (только в нашем случае, кубики не связаны между собой жестко, убрав нижней, наша конструкция не упадет).
Итак, с определением мы разобрались, но что это нам дает в итоге?
Что ж, звучит все довольно неплохо? А что же в реальности? Если открыть Asset Store и посмотреть раздел Visual Scripting, то можно увидеть, в принципе, большое количество различных плагинов. Большинство из них — это вариации на тему Blueprint из Unreal Engine, т. е. по сути кодогенерация. Систем, которые подходят под понятия визуального редактора логики практически нет. Наиболее близкие по смыслу это:
Есть ли выход спросите вы, уважаемые читатели? Я нашел только один, написать свою систему, что я и сделал, но путь к ней был довольно долгий и тернистый.
Идея разработать плагин визуального редактора логики для Unity3D возникла давно. Сначала это были лишь мысли, что, если бы вот так, то было бы круто. Мысли эти появились в процессе работы над проектом, в котором было очень много однотипных игр, более 20 штук, которые надо было делать очень и очень быстро. Первая реализация была ужасная в плане интерфейса, хотя, конечно, и позволила успешно разработать весь набор игр с заданной скоростью.
Для следующего проекта было решено делать полноценный визуальный редактор, однако в итоге из-за малого опыта, реализация оказалась не удачной, все дико тормозило, кол-во связей и т. п. вещей зашкаливало настолько, что разобраться что и куда было невозможно (см. скриншот и не пугайтесь).

После этого на какое-то время идея была отложена. Следующие проекты уже я делал на чистом коде, но в голове по-прежнему витала идея. Постепенно с учетом прошлых ошибок формировалось окончательное (как мне казалось) видение и список требований. И в 2017 году, после завершения очередного фриланс-проекта, я решил, что могу себе позволить потратить 6-7 месяцев на работу над этим плагином и попробовать его выложить в Asset Store (он и до сих пор лежит, и называется Panthea VS). С точки зрения опыта работы над таким сложным проектом, было все очень круто, финансовая стороны увы печальна, все-таки уметь программировать и уметь продавать, это вещи разные. Это был ноябрь 2017 года, после чего я слегка потерял мотивацию, развелся, сменил город, поменял полностью жизнь, и чтобы не впасть в самоедство решил посмотреть под другим углом на тему визуального редактора логики. Итогом стал uViLEd, который я решил выложить бесплатно. Поскольку я подписал фуллтайм контракт, то работать над ней пришлось по выходным вечерам и праздникам и заняло это весь 2018 и начало 2019 года. uViLEd — это большое переосмысление Panthea VS, полный рефакторинг кода под компилятор Roslyn (C# 7+), поэтому работает все только начиная с версии Unity3d 2018.3.
Примечание: на Panthea VS вышли несколько проектов (Android и iOS, в частности Грузовичок Лёва и машинки), в принципе опыт использования удачный, но всплыл момент, что одно дело написать редактор, другое дело научиться правильно его использовать (как бы это странно не звучало ).
Итак, что получилось у меня в итоге, сначала смотрим картинку, а затем продолжим (далее картинок будет больше).

Что же из себя представляет в своей основе визуальный редактор логики.

Здесь:
Все в купе – компоненты, переменные и связи – формируют логику. В целом ничего сверхсложного, программист пишет код компонентов и переменных, а гейм-дизайнер или другой (а может и тот же самый) программист/скриптер формирует логику, путем размещения этих компонентов в редакторе и настройки связей и параметров.
Для начала работы с редактором необходимо открыть сцену, а затем запустить сам редактор.

После запуска редактора инициализируем сцену (кнопка обновления в редакторе), после чего появится возможно создавать или добавлять существую логику в сцену.
После создания логики (файл, который будет описывать компоненты, их параметры, связи между компонентами, переменные и их значения), можно наполнять его смыслом. Для добавления компонента или переменной, достаточно перетащить соответствующий скрипт в область редактора логики. Альтернативный вариант воспользоваться каталогом, который формируется автоматически с использованием атрибута ComponentDefinition.

После того как мы добавили несколько компонентов в логику, их можно перемещать, в том числе группами или объединять в визуальную группу.

Рассмотрим подробнее что из себя представляем сам компонент в визуальном редакторе.

Здесь:
Для настройки параметров (публичных полей или полей с атрибутом SerializeField), необходимо выделить компонент в редакторе логики и открыть инспектор Unity3d.

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

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



Связи устанавливаются на основе правила совпадения типов, которые передает и принимает точка. Исключения составляется входная точка, которая не принимает данных, с ней можно соединить любую выходную точку. При установке связи система автоматически проверяет совпадение типов и показывает можно или нет эту связь установить. Входные и выходные точки задаются в коде компонента с помощью следующих классов:
INPUT_POINT
OUTPUT_POINT
INPUT_POINT<T>
OUTPUT_POINT<T>
Первые два класса используются для входной и выходной точки, которые не принимают параметры, вторые соответственно наоборот. Т может быть любым типом.
public INPUT_POINT <float> InputFloatValue = new INPUT_POINT<float>();
public OUTPUT_POINT <float> OutputFloatValue = new OUTPUT_POINT<float>();
Для того, чтобы вызвать цепочку связей, необходимо использовать функцию Execute.
OutputFloatValue.Execute(5f);
Для того, чтобы обработать такой вызов, в коде компонента необходимо установить обработчик для входной точки (о том, где именно мы поговорим чуть позже).
InputFloatValue.Handler = value => Debug.Log(value);
И в завершении, хочу упомянуть важный момент о связях. Если связей из одной точки выходит несколько, то в редакторе есть возможность настроить порядок их вызова.
Как было сказано ранее, переменные — это специальные объекты, которые позволяют разделять данные между компонентами через ссылки на них. Переменные, как и компоненты, создаются программистами.
[ComponentDefinition(Name = "Float",
Path = "uViLEd Components/Base/Variable/Base",
Tooltip = "Variable for a floating-point number",
Color = VLEColor.Cyan)]
public class VariableFloat : Variable<float> { }
Как видно базовым классом для переменных является generic класс Variable, где T тип данных, которые заключены в переменной, Т может быть любым типом, который может быть сериализован.
В редакторе переменные отображаются в следующем виде:

Для изменения отображения значений переменной достаточно переопределить метод ToString в типе Т.
public struct CustomData
{
public readonly int Value01;
public readonly int Value02;
public CustomData (int value01, int value02)
{
Value01= value01;
Value02= value02;
}
public override string ToString()
{
var stringBuilder = new StringBuilder();
stringBuilder.AppendLine("Value01 = {0}".Fmt(Value01));
stringBuilder.Append("Value02 = {0}".Fmt(Value02));
return stringBuilder.ToString();
}
}
Таким образом переменная данного типа будет выглядеть как:
public class VariableCustomData : Variable<CustomData> { }
Для того, чтобы добавить ссылку на переменную в компонент, необходимо использовать специальный класс.
public VARIABLE_LINK<CustomData> CustomVariableLink = new VARIABLE_LINK<CustomData>();
После этого, ссылку можно установить в инспекторе, при этом, в выпадающем меню, буду показаны только переменные типа CustomData, что значительно упрощает работу с ними.
Для удобства работы с переменными существуют специальные методы, позволяющие определить, когда переменная поменяла свое значение или когда в нее были установлены любые данные.
CustomVariableLink.AddSetEventHandler(CustomDataSet);
CustomVariableLink.AddChangedEventHandler(CustomDataChanged);
Здесь стоит учитывать, что Changed работает по условию Equals, поэтому, если используются структуры и классы, этот метод необходимо переопределять, чтобы гарантировать правильность работы.
Из-за особенностей работы системы uViLEd прямые ссылки на объекты Unity не могут быть использованы в ней, поскольку они не могут быть восстановлены при загрузке логики. Чтобы решить эту проблему, была создана специализированная оболочка VLObject, которая позволяет создавать такие ссылки, а также сохранять и загружать их. Помимо прочего, эта оболочка имеет специальный редактор свойств, который позволяет вам получать компоненты из любого объекта сцены (см. Рисунок ниже), если вы хотите обратиться к ним. С VLObject вы можете хранить ссылки не только на объекты сцены и их компоненты, но также на префабы и файлы ресурсов, такие как текстуры, звуки и т. п.

Примечание: если существующая логика используется в другой сцене, ссылки на объекты будут потеряны, включая ссылки на префабы, потому что сцена действует как их хранилище. Это также необходимо учитывать, если вы планируете использовать логику в качестве шаблона, в этом случае наилучшим вариантом будет передача необходимых ссылок на него извне (например, из логики, привязанной к сцене).
Также существует возможность ограничить тип объекта Unity, который будет установлен в VLObject. Это влияет только на инспектор Unity и используется для удобства работы с ними.
[SerializeField] [TypeConstraint(typeof(Button))] private VLObject _button;
Для того, чтобы создать компонент логики, программисту достаточно добавить в проект простой C# скрипт-файл (также можно создать сразу компонент или переменную через специальное меню во вкладке Project) и изменить в нем код на следующий:
[ComponentDefinition(Name = "MyComponent", Path = "MyFolder/MySubfolder", Tooltip = "this my logic component", Color = VSEColor.Green)]
public class MyLogicComponent : LogicComponent
{
}
Как было сказано ранее ComponentDefinition это атрибут, который позволяет автоматически формировать каталог компонентов, здесь следует учесть, что Color (цвет заголовка) задается в строке в виде HEX-формата.
LogicComponent – это базовый класс всех компонентов, в свою очередь являющийся наследником ScripatableObject.
Ниже приведен простой пример компонента, который делает ветвление по входящему значению типа bool:
public class IfBool : LogicComponent
{
public INPUT_POINT<bool> ValueToBeChecked = new INPUT_POINT<bool>();
public OUTPUT_POINT True = new OUTPUT_POINT();
public OUTPUT_POINT False = new OUTPUT_POINT();
public override void Constructor()
{
ValueToBeChecked.Handler = ValueToBeCheckedHandler;
}
private void ValueToBeCheckedHandler(bool value)
{
if(value)
{
True.Execute();
}else
{
False.Execute();
}
}
}
Итак, как видно из кода, мы создали входную точку компонента, которая принимает значение типа bool и две выходных точки, которые вызываются в зависимости, какое значение мы получили.
Наверное, у вас сейчас возникает вопрос, что это за Constructor такой? Поясняю. По умолчанию ScriptableObject не поддерживает методы типа Start, Update и т. п., но при этом поддерживаются методы Awake, OnEnable, OnDisable и OnDestroy. Так вот Awake (как и OnEnable), в случае если ScriptableObject создается через метод CreateInstance вызывается всегда, и в этом, собственно, и есть проблема. Из-за того, что объект создается в памяти для сериализации в режиме редактора необходимо было исключить работу кода компонента в этот момент, поэтому и был добавлен аналог Awake в виде метода Constructor, тоже самое касается и методов OnDisable и OnDestroy при удалении объекта в редакторе. Если необходимо корректно обработать удаление компонента (при выгрузке сцены, например), то необходимо использовать интерфейс IDisposable.
В целом, как видно, ничего сложно в создании компонентов нет. Это обычный класс, в котором может быть любой код, какой только захотите. В частном случае компоненты вообще могут не содержать входных и выходных точек, а общаться с помощью глобальный сообщений. Кстати, для этого в uViLEd присутствует класс GlobalEvent — это система сообщений, основанная на типах данных (подробнее о ней можно почитать в моей статье).
Последнее, о чем хотелось бы упомянуть, это возможность конфигурировать входные и выходные точки компонента в зависимости от параметров компонента.

Для этого в коде компонента, достаточно имплементировать один или оба интерфейса IInputPointParse и IOutputPointParse. Ниже приведет пример кода абстрактного generic-класса для компонентов ветвления Switch, здесь автоматически создаются выходные точки, в зависимости от параметра SwitchValues.
public abstract class SwitchAbstract<T> : LogicComponent, IOutputPointParse
{
[Tooltip("input point for transmitting value, which should be checked")]
public INPUT_POINT<T> ValueToBeChecked = new INPUT_POINT<T>();
[Tooltip("set of values for branching")]
public List<T> SwitchValues = new List<T>();
protected Dictionary<string, object> outputPoints = new Dictionary<string, object>();
public override void Constructor()
{
ValueToBeChecked.Handler = ValueToBeCheckedHandler;
}
protected virtual bool CompareEqual(T first, T second)
{
return first.Equals(second);
}
protected virtual string GetValueString(T value)
{
var outputPontName = value.ToString();
#if UNITY_EDITOR
if (!UnityEditor.EditorApplication.isPlaying)
{
if (outputPoints.ContainsKey(outputPontName))
{
outputPontName += " ({0})".Fmt(outputPoints.Count);
}
}
#endif
return outputPontName;
}
private void ValueToBeCheckedHandler(T checkedValue)
{
foreach (var value in SwitchValues)
{
if (CompareEqual(checkedValue, value))
{
((OUTPUT_POINT)outputPoints[GetValueString(value)]).Execute();
return;
}
}
}
public IDictionary<string, object> GetOutputPoints()
{
#if UNITY_EDITOR
if (!UnityEditor.EditorApplication.isPlayingOrWillChangePlaymode)
{
outputPoints.Clear();
}
#endif
if (outputPoints.Count == 0)
{
foreach (var value in SwitchValues)
{
outputPoints.Add(GetValueString(value), new OUTPUT_POINT());
}
}
return outputPoints;
}
}
Для отладки логики в uViLEd предусмотрено несколько механизмов:
Для последнего пункта в uViLEd предусмотрен специальный режим, который включается при запуске сцены.

У этого режима, к сожалению, есть определенные ограничения, связанные с переходом между сценами. В этом случае отладочные данные с предыдущей сцены и логик будут утеряны, а в новой они начнут отображаться, только с момента, когда логика будет активирована в редакторе.
В данной статье я постарался вкратце познакомить вас с подходом в разработке который я применяю в своих текущих проектах. Несмотря на изначальный скепсис (и мой в том числе) практика показывает значительно удобство его применения, особенно при прототипировании. Помимо прочего значительно упростилась работа гейм-дизайнеров, они не лезут в сцену, не тыкают в объекты для настройки игрового процесса, для них создается отдельная логика с набором данных переменных и компонентов, в которых они могут спокойно все настраивать. Также большое преимущество дает тот факт, что в моих проектах, часто контент загружается извне. С применением визуального редактора логики я могу обновлять баланс игрового процесса без обновления основного приложения, в некоторых случаях, можно менять и саму логику.
Для себя я решил, что подобный подход к разработке вполне имеет место быть, конечно он не применим к большим проектам, но он может быть использован там для каких-то скриптов игрового процесса для оживления мира, в дизайне уровней и т. п. В моих текущих проектах (детский сегмент) он, пока что, отлично проявляет себя.
Что же дальше?
Это была первая часть из серии статей про визуальный редактор логики uViLEd, далее будут части про:
Напишите в комментариях, если у вас появятся конкретные вопросы, которые хочется, чтобы я освятил в последующих статьях.
PS: я постарался рассказать о ключевых моментах uViLEd, если у вас появится желание вы можете ознакомится с ним скачав плагин из Asset Store, там присутствует полная документация (правда на английском): руководство пользователя, гайд для программистов и API.
Визуальный редактор логики uViLEd [1]
Стать о глобальных системах сообщений [2]
Автор: Ichimitsu
Источник [3]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/news/314796
Ссылки в тексте:
[1] Визуальный редактор логики uViLEd: https://assetstore.unity.com/packages/tools/visual-scripting/uviled-visual-logic-editor-137517
[2] Стать о глобальных системах сообщений: https://habr.com/ru/post/282524/
[3] Источник: https://habr.com/ru/post/448190/?utm_campaign=448190
Нажмите здесь для печати.