Кастомные редакторы для Unity3D, которые мы используем в игре

Unity — сказочный движок.

Иногда эта сказка добрая — про пони и радугу. А иногда — про страшного бабайку. И очень важная часть данного движка, а именно расширения для редактора (custom inspectors) — как раз про бабайку. В статье я бы хотел поделиться опытом использования собственных расширений на примере нашей игры.

Некоторые из наиболее интересных наших расширений. Сколько на экран влезло
Некоторые из наиболее интересных наших расширений. Сколько на экран влезло

Начнём с определения. Вот, что говорится в документации про расширения: «Unity позволяет вам расширить редактор своими собственными инспекторами и окнами редактора (Editor Windows), и вы можете задать, как свойства должны отображаться и инспектора при помощи пользовательского Property Drawers».

То есть имеем три основных типа для расширения функциональности, наследуемые от базовых классов:

  • UnityEditor.Editor;
  • UnityEditor.EditorWindow;
  • UnityEditor.PropertyDrawer.

И сразу же первая загвоздка — все расширения делаются только с помощью кода. Никакого визуального программирования и блок-схем, WYSIWYG, перетаскивания кнопочек по формочкам. Только C #, только хардор. Изменил размер кнопки — будь добр пересобрать весь проект, чтобы увидеть изменения. Соответственно, написать новое расширение или поправить скаченное из Asset Store не программисту — практически непосильная задача.

Первый тип (Editor) используется для того, чтобы сказать Unity «как отобразить класс». Это может быть ScriptableObject или MonoBehaviour. Используется чаще всего, по крайне мере у нас. Второй (EditorWindow) — может быть использован для отображения почти любого содержимого в пределах нового окна, за которое он отвечает. Третий (PropertyDrawer) отвечает на вопрос «как отобразить параметр». Причём это может быть как один из стандартных типов параметров, так и свой собственный.

Пример кастомного окна редактора из документации
Пример кастомного окна редактора из документации

Для каждого из типов расширений можно найти по небольшому «Hello World» в документации. Но чтобы сделать действительно полезное, удобное и красивое расширение, придётся ещё постараться. Связано это, во-первых, с наличием огромного числа недокументированных и внутренних (Internal) функций движка, которые можно использовать.

Во-вторых, с большим количеством багов и общей неинтуитивностью подсистемы. В-третьих, почти все статьи про расширения на английском или вообще иероглифы. Если с английским лично я ещё могу справится, то китайский поддается только Google-переводчику, что слабо помогает общему усвоению информации. Именно поэтому сделать что-то сложнее двух дополнительных кнопок — сродни алхимии и чёрной магии. Но когда это нас останавливало?

В нашей, казалось бы, небольшой игре используется 22 собственных скрипта для расширений на приблизительно 2,5 тысяч строк кода в сумме. Приведу особо интересные примеры для каждого типа расширений.

PropertyDrawer

Здесь, как раз, ничего сильно интересного у нас нет. Просто для примера — отображение свойства неактивным, если объект на сцене. И возможность редактировать свойство, если объект является префабом.

Эта функция используется для свойств-идентификаторов объектов. Что-то типа ИД, необходимого для сериализации/десериализации структуры уровня. Защита от самого себя, чтобы не поменять идентификатор во время сборки уровня в редакторе. Если так сделать — уровень сохранится, но загружаться не будет.

Кастомные редакторы для Unity3D, которые мы используем в игре

Еще есть PropertyDrawer для класса «Координаты X и Y» — вместо двух строчек выводит в одну. Ещё один — для отображения коллекции string в виде выпадающего списка.

Кастомные редакторы для Unity3D, которые мы используем в игре

EditorWindow

Здесь уже интереснее. Кастомное окно у нас одно. Используется как основной способ редактирования уровня. Весь код я приводить, конечно, не буду. Из интересного можно выделить пару моментов.

Первый — получение картинки-превью префаба. То есть той картинки, которая отображается в самом редакторе Unity. Реализуется с помощью одного метода AssetPreview.GetAssetPreview(gameObject).

Второй — центрирование камеры в SceneView к выбранной ячейке.

Editor

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

Дальше от него наследуются все остальные кастомные эдиторы. Самые «толстые» у нас — это ContentPackEditor, LevelEditor, LocalizationEditor, GameFieldEditor. Каждый от 200 до 300 строк кода. Опять же, приводить код каждого смысла не имеет. Расскажу только про интересные моменты.

Для ContentPackEditor нужны были функции визуальной сортировки элементов массива. И они в Unity есть, это ReorderableList в UnityEditorInternal. Вот статья по основам использования.

От себя я добавил сворачивание/разворачивание списка при клике на заголовок.

Кастомные редакторы для Unity3D, которые мы используем в игре

В LocalizationEditor надо было сделать что-то вроде таблицы, чтобы чётные и нечётные сроки выводились разным цветом. Решается это заведением двух разных стилей.

Вторым проблемным моментом было сделать поиск в уже добавленных ключах локализации и мгновенный вывод результатов в виде выпадающего списка под строкой поиска. Вот для этого найти решение было действительно непросто. В итоге нашёлся метод на каком-то китайском сайте с иероглифами. Хорошо, что код — он и в Африке (Китае) код.

Путём небольшой доработки напильником стало выглядеть вот так.

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

Почитать статью без картинок, зато с объемным кодом на C # можно на форуме.

Подписывайтесь, комментируйте, покупайте нашу игру.

3636
38 комментариев

Пытаться делать в редакторе Юнити вещи, для которых он не предназначен - плохая идея. Вы гробите туеву хучу времени на борьбу с API и UI, вместо того, что бы задействовать сторонние тулсы, написав импорт данных в пару десятков строк.

На примере вашего локализатора - да боже мой, импорт из Excel был бы в 1000 раз удобнее.

13
Ответить

Только пожалуйста, не надо писать очередные десериализаторы XML, json и прочих сторонних форматов! И уж тем более, класть их потом в папку Resources (почему-то очень популярный у начинающих разработчиков варварский подход). ScriptableObject сериализируется в прекрасный и читаемый YAML, и большинство гейм-дизайнеров скажут вам только спасибо, если игровые данные можно будет отредактировать сразу в движке, не занимаясь никакими импортами – а удобство гейм-дизайнеров это всегда основная задача разработчиков тулзов.

Локализация это, пожалуй, единственный пример где импорта стороннего формата имеет смысл, ведь локализаторы это сторонняя компания. Но и при работе с локализацией гейм-дизайнеров должны писать исходные тексты на «основном» языке, пока контент не готов к отправке на перевод, прямо в движке.

Чем длиннее работа над одной итерацией, тем меньше итерация, тем хуже итоговое качество продукта. Не надо вставлять палки в колеса продукту.

5
Ответить

И да, и нет. Основной вопрос - работа с ключами. Чтобы ключи не повторялись, чтобы для каждого языка был ключ, чтобы можно было удалить или добавить фразу на всех языках одновременно...
Я бы сказал, что расширения юнити и Excel должны дополнять друг друга. Например, чтобы дать текст на перевод стороннему человеку, а потом загрузить его обратно в игру. И при этом ничего не сломать. Нельзя недооценивать человеческий фактор)

5
Ответить

Вообще не понимаю нытье по этому поводу - у юнити лучшая кастомизация редактора что я видел. У меня на всех проектах весь пайплайн валидации контента которого много построен на редакторах и я не помню чтобы у меня что-то горело. Плюс не забывайте что они добавили UIElements и теперь все стало гораздо проще и быстрее писать (если конечно вы не делаете это в моем стиле)

6
Ответить

Ого, что-то новенькое. Пошел читать, спасибо за наводку.

Ответить

Прочитал вчера твой этот коммент, сегодня решил попробовать на очередной задаче, залез - ни документации, ни апи нормального, и вообще всё экспериментальное.

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

Ответить

Для работы с Editor есть, кстати, очень неплохой плагин OdinInspector. У него там и сериализация своя есть, чтобы выводить в Инспекторе всё подряд, и очень много встроенных функций, чтобы некоторые штуки делать просто и легко через аттрибуты.

4
Ответить