Search Unity

Прошел год с момента выхода новой системы Unity UI, и я решил отдать должное ее предшественнику, IMGUI

Вы можете подумать: «Как странно. Зачем нужно беспокоиться о старой системе UI, если доступна новая?». Что же, по сравнению с предыдущей системой Unity UI предлагает расширенные возможности настройки игровых интерфейсов. Однако IMGUI не вышел из употребления, так как с его помощью удобно расширять сам редактор Unity Editor и его возможности и создавать новые инструменты для работы с ним..

Приступая к работе

Первый вопрос: – «Почему это называется IMGUI?». Название IMGUI происходит от «Immediate Mode GUI» — «прямой» режим работы графического интерфейса. ОК, и что это? Поясним, что это означает. Существует два основных подхода к системам GUI: сохраненный и прямой.

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

При использовании прямого режима система не запоминает информацию об интерфейсе: вместо этого она каждый раз спрашивает, каким должен быть интерфейс. Каждый элемент должен быть задан с помощью вызова функции. Реакция на любое действие пользователя возвращается немедленно, без дополнительного запроса. Такая система неэффективна для пользовательского интерфейса игры и неудобна для художников из-за зависимости от кода, но хорошо подходит для редактора Unity: его интерфейс уже зависит от кода, при этом работа с редактором не предполагает постоянных изменений интерфейса в реальном времени, в отличие от игры. Изменения отображаемых инструментов в соответствии с новым состоянием системы также полезны.

В качестве отступления: интересный ролик от Кейси Мьюратори, в котором обсуждаются достоинства и принципы прямого режима!

Обработка событий

У активного интерфейса IMGUI всегда существует обрабатываемое событие. Оно может принимать разные обличья: «нажатие кнопки мыши пользователем», «необходимость перерисовки интерфейса» и так далее. Вид конкретного события можно узнать из значения Event.current.type.

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

GUI diagram 1

Написание отдельных функций для каждого события занимает много времени. Кроме того, эти функции будут отчасти совпадать: на каждом шаге будет производиться действия с одними и теми же элементами (кнопка 1, 2 или 3); сами производимые действия могут отличаться, однако совпадает общая структура. Поэтому лучше сделать единую функцию, которая занимается обработкой событий, связанных с тремя кнопками:

GUI diagram 2

В этом случае единственная функция OnGUI вызывает библиотечные функции — например, GUI.Button, которые производят необходимые действия в зависимости от вида обрабатываемого события.

Чаще всего используются события следующих пяти видов:

EventType.MouseDown Пользователь нажал кнопку мыши.
EventType.MouseUp Пользователь нажал кнопку мыши..
EventType.KeyDown Пользователь нажал клавишу..
EventType.KeyUp Пользователь отпустил клавишу.
EventType.Repaint Необходимо перерисовать интерфейс.

Полный список видов событий приведен в документации EventType.

Обычно элемент GUI.Button реагирует на различные события следующим образом?

EventType.Repaint Перерисовка кнопки в указанном квадрате.
EventType.MouseDown Проверка координат курсора на совпадение с квадратом, содержащим кнопку. Если есть совпадение, установить флаг «кнопка нажата» и инициировать перерисовку кнопки в нажатом виде.
EventType.MouseUp Снять флаг «кнопка нажата», инициировать перерисовку кнопки в отпущенном виде. Провести проверку координат курсора: если курсор находится в квадрате кнопки, вернуть TRUE (кнопка была нажата и отпущена).

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

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

Создание элементов интерфейса

Классы GUI и EditorGUI  предоставляют библиотеку стандартных элементов интерфейса, используемых в Unity. Их можно использовать для создания собственных классов Editor, EditorWindowили PropertyDrawer.

Часто начинающие разработчики обходят вниманием класс GUI, предпочитая использовать EditorGUI. На самом деле элементы обоих классов одинаково полезны и могут совместно использоваться для расширения функциональности редактора; разница в том, что элементы из EditorGUI невозможно использовать во внутриигровых интерфейсах, так как в коде они принадлежат редактору, а элементы из GUI принадлежат Unity в целом.

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

(Для просмотра требуется браузер с поддержкой WebGL — например, новые версии Firefox).

Цветные полосы прокрутки в демо — новый элемент интерфейса. Они связаны с плавающими переменными, имеющими значение от 0 до 1. Их можно использовать, к примеру, в Unity Inspector для отображения состояния отдельных частей игрового объекта. Цвет полос изменяется в зависимости от значения, чтобы пользователь мог быстро разобраться в ситуации.

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

  • Rect, определяющий координаты, с которых должны считываться вводные данные с мыши и на которых элемент нужно будет отрисовывать;
  • float, значение которой представляет элемент;
  • GUIStyle, содержащий необходимую информацию об отступах, шрифтах, текстурах и других элементах стиля, используемых элементом. В нашем случае это будет текстура, используемая при отрисовке полосы.

Функция должна будет возвращать новое значение плавающего числа, установленное после перемещения ползунка. Это будет важно, к примеру, для событий, связанных с мышью, но не для событий перерисовки элемента; поэтому функция будет возвращать по умолчанию то же значение, что было передано при вызове. Тогда будут возможны вызовы вида “value = MyCustomSlider(… value …)”, после которых сохраняется существующее значение связанной переменной.

Тогда сигнатура функции принимает такой вид:

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

Параметр FocusType.Passive определяет роль элемента в навигации с клавиатуры. Passive означает, что наша полоса вовсе не реагирует на ввод с клавиатуры; для иных случаев используются Native или Keyboard. Подробности можно узнать из документа, посвященного FocusType.

Далее мы воспользуемся оператором ветвления, чтобы разделить код, нужный для событий различных видов. Метод Event.current.GetTypeForControl() поможет определить вид события; в качестве аргумента он принимает управляющий индекс нашего элемента. Такая фильтрация не универсальна, поэтому позднее нам придется добавить дополнительные проверки.

Теперь мы готовы писать код, определяющий поведение элемента в различных ситуациях. Начнем с перерисовки:

На этом можно было бы закончить кодирование поведения: уже реализована функция визуализации связанного плавающего значения от 0 до 1, которое элемент демонстрирует в режиме «только чтение». Однако мы продолжим работу над элементом, чтобы добиться интерактивности.

Мы также хотим добиться удобства в использовании элемента. Следует учитывать горизонтальное движение мыши после «захвата» ползунка зажатием кнопки вне зависимости от того, остается ли курсор в пределах полосы вертикально, и при этом сделать так, чтобы движение мыши не влияло на другие элемента интерфейса, пока пользователь не отпустит кнопку.

Для этого мы воспользуемся переменной GUIUtility.hotControl. Она содержит управляющий индекс элемента, который в данный момент взаимодействует с мышью. IMGUI использует эту переменную в функции GetTypeForControl(): когда она не равна нулю, события, связанные с мышью, отфильтровываются, если передаваемый контрольный индекс не совпадает со значением hotControl.

Устанавливаем и сбрасываем hotControl:

В тех случаях, когда GUIUtility.hotControl содержит индекс элемента интерфейса, отличного от данного, GetTypeForControl() не станет возвращать EventType.MouseUp / MouseDown, и заход в эти два case не произойдет.

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

Два последних действия — установка GUI.changed и вызов Event.current.Use() — особенно важны для налаживания взаимодействие внутри множества элементов IMGUI. Установка значения GUI.changed в TRUE позволяет использовать функции EditorGUI.BeginChangeCheck() and EditorGUI.EndChangeCheck() для проверки изменения значения переменной действиями пользователя. Следует избегать значения FALSE, так как при этом возможен пропуск предшествующего взаимодействия с другим элементом.

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

MyCustomSlider готов: у нас получился простой, но полезный элемент IMGUI. Ему еще есть, куда расти; позднее мы обсудим, как можно расширить функционал элемента — например, добавить мультиредактирование.

Управляющие функции

Необходимо также упомянуть взаимоотношение IMGUI с компонентом Scene View. Не всем пользователям это известно, но управляющие функции (Handle) интерфейса Scene View, с помощью которых производится перемещение, масштабирование и вращение объектов — ортогональные стрелки, кольца и т.д. — также используют IMGUI.

Стандартные элементы из классов GUI и EditorGUI, используемые в Unity Editor / EditorWindows, двухмерны; однако основные концепции IMGUI — такие, как управляющие индексы и EventType — не привязаны ни к самому компоненту Unity Editor, ни к двухмерности. Управляющие функции для трехмерных элементов Scene View представлены классом Handles, который в этом случае служит заменой GUI и EditorGUI. К примеру, аналогом функции EditorGUI.IntField, создающей элемент для редактирования одного целого числа, служит эта функция:

Такой элемент служит для редактирования одной переменной Vector3 с помощью интерактивных стрелок в Scene View.

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

Управляющие функции можно использовать в редакторах, и наоборот — функции из GUI можно использовать в Scene View. Для этого придется приложить дополнительные усилия: установить GL-матрицы или применить Handles.BeginGUI() и Handles.EndGUI()для установки контекста. Кроме того, функция OnSceneGUI позволяет редактору обрабатывать события в Scene View.

Объекты состояния

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

IMGUI предоставляет возможность хранения так называемых объектов состояния. Для этого определяется новый класс, который будет использоваться для хранения данных, и объект этого класса связывается с индексом элемента интерфейса. С каждым индексом можно связать только один объект состояния, при этом IMGUI сам проводит инициализацию с помощью конструктора по умолчанию. Эти объекты не сериализуются при загрузке кода редактора (даже в том случае, если поставлена метка [Serializable]), происходящей при каждой сборке кода, поэтому их нельзя использовать для долговременного хранения данных.

Приведем пример. Пусть нашим новым элементом будет кнопка, которая возвращает TRUE при нажатии и мерцает красным цветом, если удерживать ее больше двух секунд. Мы воспользуемся объектом состояния для хранения времени зажатия кнопки. Объявим класс:

Время зажатия кнопки будет сохраняться в свойстве «mouseDownAt» при вызове MouseDownNow(), а с помощью IsFlashing мы будем определять, должна ли кнопка мерцать красным в данный момент. Она не будет мерцать, если индекс не находится в hotControl, или прошло менее двух секунд с момента зажатия кнопки; в противном случае цвет кнопки будет изменяться каждые 0,1 секунды.

Теперь напишем код для самого элемента-кнопки:

Получается достаточно простой код. Фрагменты, используемые для ответа на mouseDown и mouseUp, имеют много общего с теми, что были ранее использованы для обработки захвата ползунка в нашей полосе прокрутки. Отличается только вызов state.MouseDownNow() при нажатии кнопки мыши, а также при перерисовке кнопки меняется значение GUI.color.

Вероятно, вы заметили вызов функции style.Draw(). Именно стили будут следующей темой для рассмотрения.

Стили графического интерфейса

Мы применяли GUI.DrawTexture для изображения полосы в нашем первом элементе. FlashingButton несколько сложнее: изображение кнопки должно включать в себя не только закругленный прямоугольник — саму кнопку — но и надпись. Можно было бы попытаться нарисовать кнопку с помощью GUI.DrawTexture и поместить поверх него GUI.Label, но существует более интересное решение. Попробуем воспользоваться техникой, используемой для изображения GUI.Label, не используя сам GUI.Label.

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

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

Существует четыре способа получения объектов GUIStyle для последующего применения:

  • Написать новый стиль (new GUIStyle()) и ввести нужные значения.
  • Воспользоваться готовым стилем из класса EditorStyles. Они полезны в тех случаях, когда требуется, чтобы новые элементы интерфейса выглядели так же, как существующие.
  • Если не нужны значительные изменения по сравнению с существующим стилем — например, требуется выровнять текст по правому краю в кнопке — то можно скопировать нужный стиль из класса EditorStyles и изменить нужное свойство.
  • Извлечь стиль из GUISkin.

GUISkin — это коллекция объектов GUIStyle, которую можно присоединить к проекту в качестве ресурса и свободно редактировать с помощью Unity Inspector. Если создать новый GUISkin и открыть его, то можно увидеть, что он содержит слоты для всех стандартных элементов интерфейса — кнопок, текстовых окон, переключателей и т.д. — но особенный интерес представляет секция «пользовательские стили». В нее можно поместить любое количество объектов GUIStyle с уникальными именами, к которым можно будет обращаться с помощью метода GUISkin.GetStyle(“имя_стиля”).

Существует несколько способов загрузки GUISkin из кода: можно хранить его в папке «Editor Default Resources» и воспользоваться EditorGUIUtility.LoadRequired(); также можно использовать AssetDatabase.LoadAssetAtPath() для загрузки из указанного пути. Важно не помещать ресурсы, предназначенные только для редактора, в пакеты ресурсов или в папку Resources.

Теперь, когда у нас есть GUIStyle, можно отрисовать GUIContent, содержащий нужный текст, изображение и подсказку, с помощью GUIStyle.Draw(). В качестве аргументов используются координаты прямоугольника, в котором выполняется отрисовка, сам GUIContent, и управляющий индекс элемента.

Разметка IMGUI

Каждый из элементов интерфейса, которые мы рассмотрели, имел параметр Rect, определяющий положение на экране. Кроме того, как мы упомянули в предыдущей части, GUIStyle содержит информацию об интервалах между элементами.

Один из доступных подходов к размещению элементов на рабочем поле — вручную провести вычисления размеров и положений всех Rect, чтобы интервалы оказались соблюдены. Однако IMGUI предлагает более простое решение: с помощью механизма разметки можно автоматически вычислить параметры Rect с учетом интервалов.

Для этого существует особенный тип событий: EventType.Layout. IMGUI посылает такое событие интерфейсу, и в ответ элементы интерфейса вызывают функции разметки: GUILayoutUtility.GetRect(), GUILayout.BeginHorizonal / Vertical, и GUILayout.EndHorizontal / Vertical и другие. IMGUI запоминает результаты этих вызовов в виде дерева, которое содержит все элементы интерфейса и необходимое для них пространство. После построения дерева проводится его рекурсивный обход, во время которого вычисляются размеры элементов и их положение относительно друг друга. Следующий элемент располагается на требуемом интервале от предыдущего.

Далее, когда происходит событие EventType.Repaint — или другое событие — элементы снова вызывают функции разметки. В этот раз IMGUI повторяет «записанные» предыдущие вызовы и возвращает вычисленные прямоугольники. То есть, если во время события Layout с помощью GUILayoutUtility.GetRect() были вычислены параметры прямоугольников, то при вызове этой функции во время выполнения Repaint возвращается сохраненный ранее результат.

Как и в случае с управляющими индексами, необходимо соблюдать порядок вызовов функций разметки, производимых во время события Layout и во время других событий, иначе элементы могут получить данные «чужих» квадратов. Это означает, что значения, возвращаемые вызовом GUILayoutUtility.GetRect() во время события Layout, бесполезны, так как IMGUI не будет знать, какому элементу соответствует каждый прямоугольник, до окончания события и обработки дерева.

Давайте обновим нашу полосу с ползунком, добавив использование разметки. Это несложно; после получения квадрата от IMGUI мы можем вызывать уже готовый код:

В случае вызова GUILayoutUtility.GetRect во время события Layout IMGUI запоминает, что определенный стиль нужен для пустого контента. Контент пуст, так как не указано изображение или текст, для которых нужно пространство. Во время других событий GetRect возвращает существующий прямоугольник для использования. Получается, что во время события Layout наш элемент MyCustomSlider будет вызываться с неправильным прямоугольником, но производить этот вызов нужно для того, чтобы произошел также вызов GetControlID().

Информация, необходимая IMGUI для определения размеров прямоугольника, содержится в стиле. Посмотрим, каким образом можно позволить пользователю определять все или некоторые параметры прямоугольника.

Нам понадобится класс GUILayoutOption. Объекты этого класса — это инструкции для системы разметок, указывающие, что определенный прямоугольник должен вычисляться определенным образом: например, высота или ширина должны иметь определенное значение, или входить в определенный интервал значений, или прямоугольник должен (или не должен) расширяться горизонтально или вертикально. Чтобы создать их, нужно вызвать определенные функции GUILayout — GUILayout.ExpandWidth(), GUILayout.MinHeight() и другие — и передать их в GUILayoutUtility.GetRect() в виде массива. Они сохранятся в дереве разметок и будут учтены во время обхода дерева.

Вместо того, чтобы создавать собственные массивы из объектов GUILayoutOption, мы воспользуемся ключевым словом C# «params», с помощью которого можно вызвать метод, используя любое количество параметров, из которых автоматически составляется массив. Вот новая версия функции нашего элемента-полосы:

Теперь GetRect получает от пользователя любое количество GUILayoutOption.

Подобный метод совмещения функции элемента IMGUI с версией этой же функции, использующей автоматическое размещение по разметке, применим для любого элемента IMGUI, включая встроенные в класс GUI. Именно так класс GUILayout предоставляет «размещенные» версии элементов из класса GUI (а у нас используется класс EditorGUILayout, соответствующий EditorGUI).

Кроме того, можно одновременно использовать различные элементы, размещенные автоматически и вручную. Пространство резервируется с помощью GetRect, после чего его можно разделить на отдельные участки для различных элементов. Система разметки не обращается к управляющим индексам, поэтому можно размещать несколько элементов на одном прямоугольнике (или наоборот). Иногда такой подход может работать гораздо быстрее, чем в случае полностью автоматического размещения.

Заметим, что разметку не следует использовать в том случае, если применяются PropertyDrawer; вместо этого следует использовать прямоугольник, передаваемый перегрузке PropertyDrawer.OnGUI(). Причина в том, что сам класс Editor не использует разметку, а вместо этого вычисляет простой прямоугольник, который сдвигается вниз для каждого следующего свойства. Поэтому, если для PropertyDrawer используется разметка, Editor не будет знать о свойствах, извлеченных перед данным, и выберет неправильную позицию.

Использование сериализованных свойств

Осталось добавить еще несколько штрихов, чтобы привести созданные нами элементы к стандарту качества Unity.

Мы внедрим использование SerializedProperty. Подробный разбор сериализации подождет до следующей статьи; говоря обобщенно, интерфейс SerializedProperty позволяет получить доступ к любому свойству, к которому подключена система сериализации (загрузки и сохранения) Unity. Таким образом, доступна любая переменная из скриптов и из объектов Unity, видимая в Unity Inspector.

Этот интерфейс не только предоставляет доступ к значению переменной, но и к различной информации о ней: например, отлично ли значение в данный момент от изначального значения, которое переменная имела в шаблоне, или состояние переменной-структуры (переменной с дочерними полями) в окне Inspector (свернута или развернута). Кроме того, SerializedProperty включает любые изменения значения переменной, производимые пользователем, в системы Undo и «dirtying» (пометка измененной переменной или сцены, которые должны быть сохранены). При этом не используется управляемая версия объекта, что положительно сказывается на производительности. Поэтому использование SerializedProperty важно для обеспечения полноценного функционирования любых достаточно сложных элементов интерфейса.

Сигнатура методов класса EditorGUI, получающих объекты SerializedProperty в качестве аргументов, несколько отличается от обычной. Они ничего не возвращают, так как изменения вносятся напрямую в SerializedProperty. Улучшенная версия нашей полосы будет выглядеть так:

Теперь метод ничего не возвращает, а параметр «value» убран. SerializedProperty передается через параметр «prop». Мы будем извлекать значение плавающего числа с помощью prop.floatValue во время отрисовки полосы, и с помощью него же изменять значение во время перетаскивания ползунка.

Существуют и другие преимущества использования SerializedProperty в коде IMGUI. Допустим, значение prefabOverride показывает, было ли изменено значение свойства в шаблонном объекте. По умолчанию измененные свойства выделаются жирным шрифтом, но существует возможность установить другой стиль отображения с помощью GUIStyle.

Другая немаловажная возможность — мультиобъектное редактирование, то есть отображение сразу нескольких значений с помощью одного элемента. Если значение EditorGUI.showMixedValue установлено в TRUE, то элемент используется для отображения нескольких значений.

Использование обоих описанных механизмов требует установки контекста для свойства с помощью EditorGUI.BeginProperty() и EditorGUI.EndProperty(). Обычно, если метод элемента принимает аргумент класса SerializedProperty, он сам должен вызывать BeginProperty и EndProperty. Если же он принимает «чистые» значения — как, например, метод EditorGUI.IntField, который принимает int и не работает со свойствами — то вызовы BeginProperty и EndProperty должны содержаться в коде, вызывающем этот метод (в таком случае сам метод и не сможет вызвать BeginProperty, поскольку не имеет значения SerializedProperty для него).

Заключение

Мы надеемся, что эта статья поможет пользователям разобраться в основах IMGUI.

Были рассмотрены далеко не все возможности по настройке редактора. Отдельной статьи заслуживает система SerializedObject / SerializedProperty, а также преимущества и недостатки CustomEditor по сравнению с EditorWindow и PropertyDrawer, и обработка Undo, и многие другие аспекты. Но основы IMGUI крайне важны для раскрытия потенциала Unity по созданию индивидуальных инструментов, которые служат для расширения возможностей разработчиков, и поэтому мы начали именно с них.

Комментарии закрыты.

  1. Having used both UI variants (immediate & retained), I still like the retained mode a lot better. Why?
    — Standardized architecture & patterns. Anyone can read and understand that.
    — Object-oriented design, with all the known advantages.
    — Vastly reduced need for re-drawing
    — Doesn’t require a per-frame update at all, only reacts to changes
    — Having backing data duplicated on the UI can actually be an advantage (think forms with cancel buttons)
    — This idiom works on desktop (Swing, SWT…), on the web (e.g. GWT, Vaadin), in a game engine (Unity), across networks (see web), you name it.

    To me, the immediate mode thing just seems hacky, as if someone just didn’t want to bother with coding it in a clean way. I can see that the tooling is sometimes bad. But that doesn’t mean that the idiom is bad.

  2. Any chance you could post the source code?

  3. Very informative. Wish I’d had this ~6 months ago. Time for re-factor me thinks.

  4. Great post! Really appreciate the deepness of the dive :)

  5. Good to see some Editor information. Small example projects would be nice. And yes, please do a deep dive into serialization as it is used in custom inspectors.

    Oh, and promote the xxxScope methods, didn’t see those until I was done with my asset. So much cleaner code. :)

  6. Great post. Solid Information about the IMGUI was always in short supply, this should definitely go into the official docs or tutorials in some way. More like this please, especially about the more obscure and complex topics like custom GUIs with handles.

  7. IMGUI is quite under appreciated. Besides for it working great for editor code, I use it all the time for some quick on screen debug feedback. Probably the most obvious is a FPS counter, but it works well when you want other info as well that you don’t want to dig through the console for. I use GUILayout.Box(someString) (as opposed to GUILayout.Label) most of the time so the text contrasts regardless of BG color.

    While I don’t use it much anymore, it isn’t too bad for in game either if you:

    A) Don’t use GUILayout
    and
    B) Don’t make calls for things like buttons in a long list that are off-screen. For instance, just kill the loop as soon as you’re no longer on screen. No need to check 1000 elements that are off screen.

  8. really nice write up. Covers all the major points perfectly.

    Maybe a candidate for getting into the unity manual?

    1. Hi Nicholas,

      Yes we’ll definitely look at getting as much of this into the right places in the manual (and scriptref)!

      1. Any ETA on getting this documentation in? I’ve been asking for documentation updates of the IMGUI system for years at this point… The current documentation is horrific and offers zero guidance to anyone attempting to do more than GUILayout.Button(...). I mean, check out the documentation for GUIUtility.GetStateObject(). This blog entry is the first time I’ve ever seen any information about it — and it’s super important for creating powerful, complex GUIs!

        IMGUI is marked as legacy in the docs, but it most certainly isn’t that: it’s the primary (only) way to create Editor Extensions and interface utilities. Currently developers have to jump through some serious hoops to get things working and even once they do they’re more hobbling than anything…