Краткий обзор нового Unity UI с примерами организации интерфейса



Пожалуй, для многих новость о выходе новой системы Unity UI (далее просто UI) не показалась чем-то значимым. Как минимум — по причине её сырости, как максимум — из-за существования NGUI. Я поначалу не стал исключением, но открытие исходного кода UI системы (1) под либеральной лицензией MIT/X11, а так же энтузиазм разработчиков Unity Technologies заставили меня изменить мнение.

На мой взгляд, новый UI принесет нам довольно много плюсов:

  • Вполне достойный инструмент из коробки;
  • Возможность более глубоко понимать работу UI благодаря наличию исходников;
  • Общепринятый механизм внесения изменений в исходный код UI — fork/pull request;
  • Здоровая конкуренция между разными системами UI в конечном итоге принесет свои плоды в виде более качественных и удобных инструментов для Unity, а возможно и демпинг цен;
  • Тесное взаимодействие команды разработки UI и ядра Unity уже сейчас приносят плоды в виде оптимизаций производительности, да и в будущем надеюсь они будут идти нога в ногу.


Руководствуясь этими мыслями было решено разрабатывать интерфейс игры, используя новый UI. Как показало время, решение было оправдано, игра увидела свет и благополучно функционирует на Android девайсах. К слову сказать, финальное решение было после анонса патча Unity 4.6.1 (2), который включал тонну фиксов и импрувов UI.

Давайте перейдем к конкретике. Начну с общего представления, сформировавшегося после раскуривания скудной документации, просмотра видео-уроков и раскопки исходного кода.

Концепция UI


Подход к организации интерфейса переработан впринципе. То есть если вы ранее работали с GUI, в новом UI вы почти не найдете ничего похожего. И наоборот — если вы работали с NGUI, то найдете очень много общего. В целом ее можно назвать типичной для сред с инструментами визуальной разработки.

В основе понимания новой концепции лежат три компонента: Canvas, Rect Transform и Event Trigger.

Canvas — является контейнером для всех элементов UI и определяет режим рендера. Таких контейнеров на сцене может быть более одного (3).

Rect Transform — этот компонент позволяет задать положение и размер игрового объекта, используя удобные визуальные контролы. Он вводит понятия ширины и высоты, а не только масштаб (4).

Unity Event — доработанная система событий, а конкретно компонент Event -> Event Trigger который включает компоненты визуального управления вызовом событий (5).

По мере знакомства с UI я создавал для себя возможные варианты использования. Давайте на них и разберем эту всю кухню. В конце статьи вы можете найти демо-проект, на который я буду ссылаться в тексте. К сожалению, этим примерам далеко до готовых рецептов, так как они не достаточно проработаны, плюс заточены под 2D игры, хотя местами вполне подойдут и для 3D.

Пример #1


Цели:
1. Тайловый фон;
2. Фиксированное положение контролов в независимости от разрешения и соотнешения сторон экрана;
3. Контролы должны быть пропорциональны размеру экрана.

Вариант решения:
1. Добавляем на сцену Canvas и выбираем режим рендера Screen space — Overlay, этот режим будет автоматически подстраивать его размер под размер камеры;
2. Добавим тайловый фон, для этого нам всего-то и нужно, что добавить нашему Canvas новый UI компонент — Image, выбрать нужный спрайт и изменить поле Image Type в значение Tiled;
3. Выбираем в окне Game удобное нам разрешение, например, 480x800 и добавляем на наш Canvas — контейнер необходимые контролы, выставляем им нужные позиции;
4. Далее идет новая магия UI, под названием якоря! Название говорит само за себя, они позволяют закреплять ряд характеристик игрового объекта, используя компонент Rect Transform. Изменить их можно в окне редактирования сцены, включив новый режим, либо в инспекторе. Обратите внимание, что иконка в инспекторе компонента Rect Transform — кликабельна и открывает окно выбора предустановленных положений и вариантов поведения, а если зажать кнопку Shift, то еще и положение точки опоры;
5. Довершает картину один из компонентов Canvas, который добавляется к нему по умолчанию Layout -> Canvas Scaler, а именно один из его режимов — Scale With Screen Size. Этот режим позволяет задать Reference Resolution, в нашем случае это будет 480x800.



Пример #2


Цели:
1. Ориентация строго портретная;
2. Универсальный для всех соотношений сторон фон, который всегда отображается на весь экран, а если необходимо обрезается только снизу;
3. Фиксированное положение контролов в независимости от разрешения и соотнешения сторон экрана;
4. Контролы должны быть пропорциональны размеру экрана.



Вариант решения:
1. Добавляем на наш Canvas — контейнер объект UI -> Image, выбираем нужный спрайт и тип изображения Simple;
2. Вся магия сводится к расположению якорей и точки опоры таким образом, что бы они лежили по центру верхнего ребра Canvas;
3. В компонент Canvas Scaler нужно выставить значение Match равное 0.

Пример #3


Цели:
1. Ориентация строго портретная;
2. Тайловый фон, который не скейлится;
3. Фиксированное по размеру игровое поле, например 400x400;
4. Фиксированное положение контролов в независимости от разрешения и соотнешения сторон экрана;
5. Контролы должны быть пропорциональны размеру экрана.



Вариант решения:
1. Можно, конечно, реализовать фон как Sky Box или игровым объектом типа Quad с затайленой текстурой и размерами заведомо больше экрана. Но мы пойдем UI-ным путем и создадим Canvas, аналогичный примеру #1, только выставим тип рендера Screen Space — Camera и пропишем нашу главную камеру в поле Render Camera;
2. Теперь создадим еще один Canvas с типом рендера World Space, пропишем камеру и поставим значение Order in Layer равное 1, что бы этот слой рисовался поверх предыдущего Canvas у которого это значение равно 0;
3. Советую вам при рендере World Space _заранее_ ставить значение масштаба Canvas таким образом, что бы у вас не было проблем с размером шрифтов;
4. Контролы раставляем уже известным нам способом.

Пример #4


Цели:
1. Создать скроллер;
2. Контент не должен быть виден за пределаеми скролера;
3. Он должен скейлиться вместе с экраном и липнуть к нижнему краю.



Вариант решения:
1. Нам необходим игровой объект которому мы добавим компонент UI -> Scroll Rect, самое важное поле которого — Content, задав которое мы указываем контейнер содержащий игровые объекты (например надцать изображений игровых уровней размером 100х100, которые заведомо не влезают в размер экрана);
2. Так же нам будет полезен компонент UI -> Mask (обратите внимание, что также необходимо вставлять пустой компонент Image), который клипнет контент выходящий за пределы игрового объекта, на который он добавлен;
3. Можно так же создать объект UI -> Scrollbar и привязать его к нашему Scroll Rect

Пример #5


Несколько мелкий приятностей:
1. Как расширить список обрабатываемых событий, например обрабатывать события StartDrag, EndDrag и т.д. используя компонент Event Trigger;
2. Применение элемента UI -> Toggle для реализации наград в виде Звезд с удобным скриптингом вида isOn = true;
3. Простейший вариант Попап окошек с использованием компонента Canvas Group и старого доброго аниматора без единой строчки кода.

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

Файл проекта с примерами


www.dropbox.com/s/0d48cf04lekpfoe/DemoProjectUIv4_6_1.unitypackage?dl=0

Сноски


1. bitbucket.org/Unity-Technologies/ui/overview
2. unity3d.com/unity/whats-new/unity-4.6.1
3. docs.unity3d.com/Manual/class-Canvas.html
4. docs.unity3d.com/Manual/class-RectTransform.html
5. docs.unity3d.com/Manual/SupportedEvents.html

Similar posts

AdBlock has stolen the banner, but banners are not teeth — they will be back

More
Ads

Comments 25

    –2
    Вроде все хорошо, пока не вспомнишь:
    1. насколько часто выходят фиксы юнити (не чаще раза в 2-3 месяца) и ngui (бывало по нескольку раз на неделе для важных фиксов).
    2. Ngui — это не только рендер, но и куча хелперов обвязки. С этим у штатного гуя все плохо, надо все писать с нуля, либо портировать с нгуя, но зачем, если можно брать и пользоваться.
    3. Атласирование — уже разрешили делать на basic-версии юнити?
    4. Порядок отрисовки согласно порядку созданя GameObject — это… это прекрасно. Идиот, такое придумавший, однозначно никогда не имел опыта работы с динамическим гуем. Теперь порядок создания виджетов прибивается гвоздями или его придется каким-то образом контролировать относительно других виджетов.
      0
      1. После беты rc1 уже не так часто. До этого часто меняли архитекуру UI и если перескачить через ступеньку, UI мог слететь нафик.
      2. Базовый набор хелперов есть — есть Layout'ы, Drag&Drop, ContentSizeFitter — вобщем вполне самодостаточный базовый набор, чтоб без кодинга собрать не особо извращённый UI.
      3. Если на про не хватает — атласы можно напрячь делать художника или сделать небольшую тулзу, собирающую спрайты в один атлас.
      4. Порядок отрисовки зависит от sibling index — тоесть как оно в дереве иерархии висит.
      У Transform есть набор функций для его модификации — SetAsFirstSibling, SetAsLastSibling, SetSiblingIndex

      В целом на новоый UI получилось полностью перенести то, что было сделанно на NGUI, и успешно доделывать новые экраны на UI — пока не поподалось задачи, чтоб не вышло её реализовать.

      Порой приходится поломать мозг, прежде чем получается заставить всё это работать как задуманно — я привык несколько по другому мыслить в рамках UI (раньше в основном писал на C++ и там у нас была своя WYSWYG система UI), особенно когда выстроишь запутанную иерархию из лайутов, контент сайз фиттеров и т.п.
        –1
        3. Если на про не хватает — атласы можно напрячь делать художника или сделать небольшую тулзу, собирающую спрайты в один атлас.

        Атласы иногда приходится пересобирать довольно часто и не все художники имеют доступ к коду. Да, написать можно что-угодно, но зачем, если есть нгуй :)
        4. Порядок отрисовки зависит от sibling index — тоесть как оно в дереве иерархии висит.

        Это я в курсе. Вот пример:
        *) магазин предметов, все предметы инстанцируются динамически, ничего в сцене нет в момент старта.
        *) каждый предмет представляет собой иерархию виджетов (спрайты подложки, иконки, оверлейной иконки, дополнительных штук), содержащих данные из разных атласов (например, атлас контролов-кнопок и атлас иконок предметов), порядок отрисовки четко настроен так, чтобы виджеты из одинаковых атласов рисовались в непрерывном пространстве в сцене — получается хорошая оптимизация по батчингу. Здесь слово «сцена» — не ошибка. Циферки порядка отрисовки имеют сквозную нумерацию в пределах панели и это позволяет гибко настраивать рендер.
        *) настроенная иерархия предмета сохранена как префаб с управляющим классом (допустим, VisualStoreItem) с последующей загрузкой и инстанцированием, остается только поменять родителя и восстановить скейл на тот, что в префабе. Все циферки порядка отрисовки сохраняются как были настроены, ничего больше делать не нужно: ни отслеживать порядок элемента в префабе, ни в контейнере предметов магазина.
        Вопрос — как это сделать в новом гуе без контроля порядка отрисовки всей иерархии виджетов в каждом инстанцируемом предмете?
        В целом на новоый UI получилось полностью перенести то, что было сделанно на NGUI

        Пока нет, исключительно базовый функционал 3.0.х с кучей особенностей.
          0
          3. — у кого нет про, тем наверно будет не так удобно

          4. Сиблинг влияет на то, кто кого перекрывает. А вот в каком порядке оно именно отправляется в GPU — это как-то не заботит — настроек, чтоб как-то на это влиять в UI нет. Судя по всему он сам сортирует и оптимизирует, т.к. с переделкой на новый ui fps ни как не просел (к примеру HUD использует спрайті из нескольких разных атласов).

          Мне кажется они улучшили систему батчинга (детально не изучал этот вопрос — просто по ощущениям использования — т.к. раньше, к примеру, системы частитц его рвали часто и фпс просаживали, теперь с этим получше стало).
            0
            А вот в каком порядке оно именно отправляется в GPU — это как-то не заботит

            Заботит. На сложных гуях можно сократить количество отрисовок до 2 раз путем ручного указания «как поздно» будет отрисован виджет.
            Мне кажется они улучшили систему батчинга

            К сожалению, в автоматическом режиме это не сделать — transparency геометрия может быть отрисована исключительно back-to-front и просто так группировать геометрию, разнесенную по «глубине» отрисовки нельзя — будут ошибки рендера. Поэтому отрисовка будет группировать только последовательные куски, принадлежащие одному атласу. Как только находится виджет из другого атласа — батч ломается. Это относится так же к нгую, но там можно руками подхачить циферки глубины отрисовки, в том числе и в префабе.
              0
              я к тому, что проблему с порядком отправки в рендер решает Unity, и что там под капотом — скрыто.

              У NGUI есть плюс, в том что его все внутренности висходниках — можно было посмотреть, или при желании поправить (не правил, но идею генерации геометрии брал от туда, для проекта другого, где не было NGUI).
              Но опять же — пока что переделка интерфейса с NGUI на UI ни как не повлиял на производительность, даже учитывая то, что хад в 3l игре где-то из 4х аталсов сейчас рисуется.
              ВОбщем по результатам опыта ещё на бэте я решил, что new UI имеет права на жизнь, и пока что проблем с производительностью в нём не испытываю. Может какой-то специфический случай всё и испортит.

              Если уже весь проект на NGUI и в высокой степенью готовности, то смысла переезжать нет конечно.
                –1
                Из личного опыта, хочу посоветовать избегать оптимизаций основаных на «недокументированных» возможностях технологий… что будет если изменится батчинг в юнити или изменится АПИ NGUI? Сколько займет трудозатрат добавить новые фичи? Что будет если количество элементов вырастет в двое и оптимизация уже не поможет?
                У вас наверняка были причины так сделать, но дабы уберечь других, скажу — старайтесь участвовать в обсуждении требований к интерфейсу своего проекта, добавляйте асинхронную подгрузку элементов, разбивайте элементы на часто/редко используемые, группируйте. Подходов борьбы с этим зло много, главное вовремя его заметить!

                И подытожу, все конечно зависит от конкретных задач, но с новым Unity UI я пока видел только прирост производительности и надеюсь эта тенденция в будущем будет только улучшаться!
                  0
                  хочу посоветовать избегать оптимизаций основаных на «недокументированных» возможностях технологий…

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

                  Она не может не помочь, если есть понимание того, как работает рендер юнити.
                  все конечно зависит от конкретных задач, но с новым Unity UI я пока видел только прирост производительности

                  Нужно уметь использовать инструмент и понимать как оно работает дальше. Пока производительность повышается за счет исправления багов и начальной попытки батчинга (эпичный по длине фиксов changelog к 4.6.1).
                  Из личного опыта

                  Мое основное направление — исключительно мобильные платформы, где производительность наиболее критична. Основное требование работодателя — должно работать без лагов от iPhone4/Tegra2 до топовых девайсов. Мне, как indie-девелоперу, ничего от прошки на мобилках не нужно — пока штатный гуй будет требовать для атласирования прошку, я буду использовать готовое и проверенное годами решение с уже обкатанной механикой использования.
                    0
                    Вы забываете, что это все можно сказать и про NGUI, а внесение изменений под свои нужды, концептуально ничем не отличается в этих продуктах, я даже лучше предпочту форк-пулл реквест.

                    У такого рода оптимизаций, как колдовство над батчингом, почти всегда есть порог, после которого они уже не приносят прироста производительности. Даже если нет возможности ограничить отображаемое количество элементов UI на низкопроизводительных девайсах, я предпочту разделить иерархию по слоям, каждый их них положить в отдельный атлас и включать слои по мере надобности/видимости. Все это поддерживается в Unity из коробки.

                    Я с недавних пор тоже инди-девелопер и тоже предпочитаю использовать проверенные решения, но всегда открыт для «пощупать что-то новое» и особенно готов поддержать инициативы открытия исходного кода!
              –1
              PS: UI точно также и делаем с префабами, как у вас — только в нашем случае это карты с развитой иерархией виджетов.
              Инстанциировал префаб, подцепил его к родителю, а на родителе layout сам выстроил карты в панели согласно её настроек. А сиблингом играться приходится не часто — например аватары (опять же составные обьекты из префабов) стоят с перекрытием, и бросить активную аватару, говорящего персонажа на передний план.
          0
          Спасибо за отличную статью. Пока материалов по новой UI практически нет, и ваша статья хорошо восполняет эту нишу.

          Я не смог разобраться, как передать параметр в событие нажатия UI-кнопки. Так и пришлось делать разные обработчики по каждую кнопку. Может, вы сталкивались с этим?
            0
            Мне в начале помогли вот эти два туториала (помимо стандартных туторов из елпа юнити):
            Базовые концепции — www.youtube.com/watch?v=JrlFMwLI1fw
            Лейауты — www.youtube.com/watch?v=DAdW_K44Dao

            Если у вас функция обработчик принимает string/int/bool/float то его в настройках оработчика клика можно вписать.
            Ну а если обьект — то можно его туда просто перетянуть.

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

            Кстати недавно тут кто-то писал статью о создании простой системы событий в Unityю
              0
              Как это сделано в нгуе:
              *) допустим, вешаем компонент кнопки — UIButton.
              *) настраиваем вызов колбек-метода по нажатию.
              *) в самом колбеке в момент нажатия можно потрогать статик свойство UIButton.current — там будет содержаться UIButton, вызвавший колбек. С него уже можно получить любые компоненты через gameObject или как-то еще.
              Это справедливо для всех компонентов нгуя — везде при вызове колбека можно узнать, кто вызывал. Как оно сделано в штатном гуе сказать не могу, не было нужды пользоваться им.
                0
                Точно такое же реализуется и в новом UI. Достаточно создать публичный метод с аргументом GameObject, а на кнопке в этот параметр указать саму кнопку. Или использовать в качестве аргумента int и на кнопке прописать его номер. Но лично мне проще при динамической генерации кнопок какие-то свойства закладывать в name и уже внутри обработчика определить допустим его порядковый номер.
                  0
                  Если навешивать события програмно, то в новом UI вообще никаких проблем:
                  button.onClick.AddListener(delegate{
                       Debug.Log(button);
                  });
                  


                  Но с новой системой событий, которая для триггеров, передает данные объектом BaseEventData (http://docs.unity3d.com/ScriptReference/EventSystems.BaseEventData.html). В нем нас интересует свойство selectedObject — ссылка на объект который инвокнул событие. Тоесть можно делать вот так:
                  public void MethodName(BaseEventData baseEventData) {
                       Debug.Log(baseEventData.selectedObject.name);
                  }
                  
                0
                Рад, что она вам пригодилась!

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

                Но на мой взгляд это пол дела, если уже делать все красиво, то лучше навешивать события програмно при создании/инициализации. Например используя docs.unity3d.com/ScriptReference/Events.UnityEvent.html

                C#
                button.onClick.AddListener(delegate{methodName(arg);});
                


                или для Unity Script
                button.onClick.AddListener(function() {
                     var closuredArg = arg;
                     methodName(closuredArg);
                });
                

                0
                del
                  +2
                  Записываю потихоньку уроки по новому юай www.youtube.com/playlist?list=PLblKT4qEvf5MaHnsjJ9Qp-Y_SCkMDH0_A
                    +1
                    Не успел добавить…

                    Использвал офф тутор + справку. Хочу следующие уроки сделать уже ближе к практическим задачам.

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

                    В целом — новый UI хорош и последние демки/прототипы делаю на нем.
                      0
                      Круто! Это отличный материал, который очень многим поможет. Думаю все согласятся, что лучше их максимально популяризировать (это я намекаю на статью ;)
                      0
                      Отсмотрел все, понравилось несмотря на иногдашние слова типа, ну это я пока не понял как работает ) Я уже начала мини игрушку делать на Unity UI, очень все удобно )
                      +1
                      Одного не понимаю, почему они просто не купили/наняли NGUI с потрохами? Неужели не смогли договориться с разработчиком NGUI по деньгами? Во всяком случае, после использования NGUI это первое что приходит в голову.
                        +1
                        автор NGUI работал как раз над созданием Unity UI на первых порах. Он даже выступал с докладом на каком-то мероприятии. Почему он ушел — я не знаю, но до сих пор пилит и развивает свой продукт.
                          0
                          Потому, что денег NGUI приносит больше, и он понял, что если забьет на свой проект и перетащит все плюшки в родной UI, то основной проект умрет.
                        +1
                        Спасибо за статью, теперь с центровкой и масштабом проблем нет.

                        Only users with full accounts can post comments. Log in, please.