Canvas в Unity

Привет! У меня нашлась свободная минутка, так что я подумал, что пришло время поделиться своим опытом построения интерфейсов в Unity. Поэтому, кому интересна эта тема — прошу под кат.

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

Содержание

Игровые интерфейсы

Что такое Canvas?

Варианты отображения Canvas

Размещение элементов в Canvas

Разработка под экраны любых размеров

Создание кастомного селектора

Игровые интерфейсы

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

Но это радикальные методы. Понятно, что самый лучший интерфейс — это тот, который игрок не замечает вплоть до момента, когда он ему понадобиться. И количество патронов на самом оружии, и уровень жизни, который отображается прямо на протагонисте (привет, Dead Space!) — все это на совести геймдизайнера. Мы же, как разработчики, должны воплощать эти прихоти в жизнь. Какие же возможности для создания интерфейсов есть в Unity?

Что такое Canvas?

Canvas для Unity UI — это область, внутри которой размещаются все элементы пользовательского интерфейса. То есть, когда мы создаем новый игровой объект, то Сanvas для него создается автоматически.

Начиная с версии 4.6, для работы с элементами пользовательского интерфейса используется новая версия Unity UI. Как и во время работы с обычными объектами, элементы HUD организовывают в иерархии, корневым элементом которых является объект с компонентом Canvas. При этом порядок в иерархии определяет порядок рендера: объекты, которые находятся в самом низу, отрисовываются последними и, соответственно, располагаются поверх остальных объектов.

Не имеет значения, какой тип приложения вы разрабатываете — как 3d, так и 2d игры используют одну и ту же логику работы с UI. То же самое касается платформы. Не имеет значение, это Web с HTML5 или игра для Android — достаточно будет описать одно полотно Canvas.

Варианты отображения Canvas

За то, где именно будет отображаться Unity UI Canvas (в пространстве экрана или игрового мира) отвечает параметр Render Mode, у которого есть три режима:

  1. Screen Space — Overlay — Canvas рендерится поверх всех элементов сцены. Если провести аналогию, то Canvas с элементами — это как окно с «наклейками» в поезде. Мир за окном может меняться (движение камеры в Unity), но «наклейки» остаются неизменными. Этот режим изменяет размер Canvas, когда меняется размер экрана. Он идеально подходит для статической информации вроде счета или инструментов управления.
  2. Screen Space — Camera — это то же окно, но вместо «наклеек» на нем расположена объемная аппликация (не имеет значения, это 2d или 3d игра). Также размещается поверх остальных элементов сцены, но при этом на элементы Canvas распространяются законы перспективы (при условии, что тип проекции камеры — Perspective). Этот режим идеально подходит для меню или других элементов, которые должны выглядеть объемными.
  3. World Space — режим, в котором Canvas выступает элементом сцены и может быть дочерним по отношению к другим элементам Game Object. Этот режим идеально подходит для облачек с текстом персонажей, индикаторов патронов в оружии и т. п.

Размещение элементов в Canvas

Для удобства проектирования каждый элемент UI отображается в виде прямоугольника. Для манипуляций с ними используется два главных компонента, Rect Tool и Rect Transform. Последний — это новый компонент, у которого кроме уже указанных полей есть особенное поле Anchors (Якоря).

Rect Transform

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

Звучит сложно, не так ли? Но если провести 10 минут в редакторе, то все сразу станет на свои места.

Creating button in Unity

Следует также сказать, что кроме якорей у Rect Transform есть также параметры Min, Preferred и Flexible Width и Height, которые можно переопределить с помощью компонента Layout Element, но обычно я его не использую.

Layout Element (Script)

Кстати, новая версия Unity UI Canvas позволяет использовать несколько приятных особенностей. Одна из них — это возможность полностью отключить весь UI для упрощения работы с самой сценой. За это отвечает пункт Layer в меню редактора.

Turning off UI

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

При разработке под мобильные устройства (если вы разрабатываете не только под iOS) девелопер сталкивается с проблемой сегментированности, когда его игры могут запускать как на телефоне с 2-дюймовым экраном, так и на устройстве с диагональю 9 дюймов. И это я еще молчу про планшеты. Что делать в такой ситуации? У нас есть несколько вариантов.

Разработка под экраны любых размеров

Почему бы не использовать Anchors?

Первое, что приходит в голову — использовать якоря. Да, если у вас простые экраны без большого количества дочерних элементов, анимаций и остальных фич для пользователей, то почему бы и нет? Но, если вы используете такой тип верстки, следует помнить, что происходит изменение размера, а не визуальное растягивание (скейл) элементов. Поэтому в текстовых компонентах параметр Best fit становится активным, а во всех элементах появляется необходимость контролировать соотношение сторон и привязку к определенным частям экрана или другим элементам. Лично я так и начинал верстать — и решение нашлось (хотя оно и не очень элегантно, но зато работает).

В пакете UI есть компонент Aspect Ratio Fitter. Он работает достаточно просто: в зависимости от типа и изменений в размерах родительского элемента, данный компонент меняет размеры собственного Game Object, сохраняя соотношения сторон.

Aspect Ratio Fitter

Кстати, существует еще такой компонент, как Content Size Fitter, который работает «с другой стороны» — он изменяет размеры элемента в зависимости от его содержимого. Обычно он используется с дочерними Game Objects, которые содержат Text-компоненты, но также идеально работает с Layout Element и Grid Layout Group.

Content Size Fitter

Преимущества использования Canvas Scaler

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

У Canvas Scaler есть три режима работы:

  1. Constant Pixel Size — Game Object сохраняет свой размер в пикселях, игнорируя размеры экрана.
  2. Constant Physical Size — Game Object сохраняет свои физические размеры, игнорируя размеры экрана и его разрешение.
  3. Scale With Screen Size — Game Object растягивается вместе с экраном.

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

Canvas Scaler

Как я уже упоминал, у этого режима есть свое целевое разрешение, в соответствии с которым Screen Match Mode будет изменять текущее полотно Canvas. Сам Match mode может принадлежать к одному из следующих типов:

  1. Match Width Or Height — растягивает область Canvas в зависимости от соотношения ширины и/или высоты к реальным на устройстве.
  2. Expand — растягивает область Canvas таким образом, чтобы помещалась большая сторона (размер Canvas никогда не будет меньше указанного).
  3. Shrink — растягивает область Canvas таким образом, чтобы помещалась меньшая сторона (размер Canvas никогда не будет больше указанного).

И хотя у каждого из режимов есть свои преимущества, но все же моим фаворитом остается Match Width Or Height. У этого режима есть еще одна полезная особенность — для него можно указать, какая из осей будет целевой, и с какой осью будет осуществляться сравнение (вся область Canvas будет увеличиваться в зависимости от ширины, высоты или обоих этих показателей).

Для этого используется параметр Match, в котором через переменную float [0;1] задается соотношение влияния сторон. Таким образом, 0 — изменения будут подгоняться по ширине, а 1 — по высоте. Если целевое соотношение сторон находится в горизонтальной ориентации, то когда Match = 0, мы получаем эффект Expand, а когда Match = 1 — Shrink (в вертикальной ориентации все будет наоборот, 0 будет давать Shrink, 1 — Expand).

Последний параметр Canvas Scaler — это Reference Pixels per Unit, который определяет, сколько пикселей будет в одном юните UI-элемента. Его значение по умолчанию равно 100 метрам. Он работает с Image-компонента, поэтому если у вас каждый метр — это 100 пикселей изображения и вы используете физику Unity, то вам подойдет это значение.

Создание кастомного селектора

Как я уже говорил, в моем случае задача состояла в написании кастомного селектора для Facebook. То есть предполагалось наличие динамически сформированного списка, из которого элементы могли удаляться «на ходу». И хотя работа с Facebook API — это отдельный разговор, предлагаю завершить обзор возможностей нового UI, а точнее — Layouts.

Layout

Как видим, некоторые компоненты мы уже рассмотрели. Поэтому давайте по очереди знакомиться с теми, про которые мы еще ничего не знаем.

Canvas Group

Canvas Group

Этот компонент легко и просто настраивать, но, тем не менее, он очень действенный. Позволяет оперировать свойствами всех дочерних элементов объекта. Полезен, когда требуется сделать определенную панель неактивной или запретить реагировать на взаимодействие с пользователем (через Raycast).

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

И наконец-то самое интересное.

HorizontalLayoutGroup и VerticalLayoutGroup

Horizontal Layout Group

Два компонента размещают все дочерние компоненты горизонтально или вертикально друг за другом. Можно задать как отступы внутри корневого элемента, так и между дочерними элементами (padding и spacing). Кроме того, объекты можно отцентрировать относительно краев родительского элемента.

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

GridLayoutGroup

Grid Layout Group

Если вам нужно создать таблицу из однотипных элементов, то это просто дар богов! Как и у предыдущих элементов, у него есть поля padding и spacing, но при этом дальше нам требуется задать размеры дочернего элемента, в соответствие с которыми будут приводиться все объекты, добавленные к родительскому.

Дальше следует задать StartCorner, который будет определять позицию первого элемента, и StartAxis, который будет указывать на ось заполнения (по строкам или столбцам). Так, если выбрано Vertical, то элементы будут заполнять текущий столбец до тех пор, пока не достигнут границ родительского элемента, а затем перейдут на новый. С Horizontal все происходит наоборот — заполнение будет происходить по строкам. Задав, как и откуда добавлять элементы, логично будет указать их выравнивание.

Последний параметр — это Constraint, который определяет, будет ли у данной разметки определенное количество столбцов или строк или же этот показатель будет высчитываться, исходя и размеров родительского элемента. Стоит помнить, что в отличие от остальных компонентов разметки, GroupLayout переопределяет размеры дочерних элементов на Cell Size (его не интересуют показатели Min, Preferred и Flexible sizes).

Кроме того, существует возможность комбинировать GridLayoutGroup с ContantSizeFitter, однако у меня такой необходимости не было. Судя по официальной документации, с этим не должно возникать проблем.

Вот как выглядел конечный результат в моем случае:

Using custom selectors

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

Grid Layout Group

Кстати, элемент был сверстан отдельно от панели, которая должна была удерживать этот объект и дальше. Все, что от меня требовалось — скопировать размеры элемента в CellSize компонента GroupLayout. А вот хардово забить размеры элемента я смог с помощью CanvasScaler, которому задал тип растягивания Match Width Or Height.

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

So Long, and Thanks for All the Fish.

Нужен MVP, разработка под iOS, Android или прототип приложения? Ознакомьтесь с нашим портфолио и сделайте заказ уже сегодня!

Об авторе

Разработчик игр
Сергей стоял у истоков гейм-дева в студии, а также успешно обучал студентов-практикантов искусству создания игр.

Похожие статьи

Вернуться к списку записей К списку записей