Создание меню для игры в unity3d. Как создать внутриигровое меню в Unity

Для Андроид 19.05.2019
Для Андроид
  • Разработка под Android
    • Tutorial

    Как создать внутриигровое меню в Unity

    Unity позволяет не делать меню настроек, так как есть встроенный диалог настроек качества и разрешения перед запуском игры. Однако если вы хотите сделать вашу игру неповторимой и показать, на что способны, как дизайнер, то эта статья именно для вас. После прохождения этого туториала вы сможете создавать свое собственное меню с таким функционалом – настройки громкости, разрешения, качества изображения, переключения полноэкранного и оконного режима. Интересно? Тогда запускай Unity и поехали!
    Создание и дизайн главного меню и меню настроек
    1. Создаем две сцены: Menu и Game
    2. Открываем File->BuildSettings и перетаскиваем созданные сцены в Scenes in build.

    Теперь можем приступать к созданию пользовательского интерфейса. Откройте сцену Menu и добавьте Panel. Сразу добавляется Canvas(Холст) и дочерним объектом к нему добавляется Panel (Панель).


    Обратим внимание на инспектор для Canvas. А именно на компонент Canvas.


    Render Mode автоматически выставлен на Screen Space – Overlay.

    Немного информации:

    Screen Space – Overlay:

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

    Важен порядок размещения объектов в иерархии. Холст Screen Space – Overlay должен находиться в самом верху иерархии, иначе он может пропасть из виду.


    Screen Space – Camera:

    В таком случае, Холст прикрепляется в камере. Для такой настройки обязательно нужно указать камеру которой соответствует Холст. Как и в предыдущем пункте, Холст будет менять свой масштаб в зависимости от разрешения и размера экрана, а также от области видимости камеры.

    Так же для Холста с такими настройками важно размещение относительно других объектов. На переднем плане будут объекты, которые находятся ближе к камере, не зависимо от того, это UI или другие GameObjects.


    World Space:

    Холст размещается, как любой другой объект без привязки к камере или экрану, он может быть ориентирован как вам угодно, размер Холста задается с помощью RectTransform, но то, как его будет видно во время игры, будет зависеть от положения камеры.
    В данном задании мы будем использовать Screen Space – Overlay.
    Давайте настроим цвет панели. Можно также добавить картинку на фон. Для этого нужно обратить внимание на компонент Image в Инспекторе панели.


    Как видите, картинка добавляется в поле Source Image. Картинку можно просто перетащить из файлов проекта, либо кликнуть на кружочек справа от поля.

    Цвет выбирается в поле Color, либо с помощью инструмента пипетки.
    Важно знать, что пипетка позволяет выбрать цвет за пределами Unity.
    После того, как выбрали цвет или картинку, нам нужно разместить кнопки, которые всем будут управлять, а так же текст. Чтобы упростить себе задачу, для Panel мы добавим еще один компонент, который называется Vertical Layout Group. И сразу настроим его.

    Нам необходимо разместить все кнопки и текст по центру экрана. Для этого находим в компоненте Vertical Layout Group пункт Child Alignment и выбираем Middle Center. Теперь все наши элементы, будь то кнопки или текст, будут выравниваться по центру, независимо от разрешения экрана.

    Так же убираем галочки с ChildForceExpand. Должно получиться так:


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

    В нашем меню должны быть следующие кнопки:

    1. Start Game
    2. Settings
    3. Exit

    Сейчас, добавляем Text дочерним элементом нашей Панели. Можете заметить, как он прикрепляется по центру. Иногда для этого требуется созданный элемент в окне Scene просто передвинуть на Panel и тогда он выровняется. Настраиваем текст так, как хочется с помощью компонента Text(Script) в инспекторе.


    После добавляем Button. Кнопка добавится под текст.

    Разберем компонент Button нашей кнопки. Нас интересует Transition – то, как кнопка будет реагировать на действия мышки. В Unity реализовано несколько способов. Пока рассмотрим довольно простой Color Tint. Это значит, что кнопка будет менять свой цвет при определенных состояниях. Интерфейс для настройки довольно удобный. Настраиваем так, как нравится.


    Так же у объекта Button есть дочерний объект Text – это текст который будет отображаться на кнопке. В тексте прописываем Play.

    Кажется, Текст и Кнопки находятся слишком близко друг к другу.

    Что бы это исправить для нашего Текста Menu добавляем еще один компонент, который называется Layout Element. И ставим галочку напротив Ignore Layout.


    После этого выставляем Anchor на Middle-Center.


    Потом добавляем еще три кнопки и называем их Settings, Credits, Exit.
    Можно поменять размеры кнопок. На этом этапе меню выглядит так:

    Переходы между main menu и settings
    Что бы переходить на меню опций не обязательно делать новую сцену.
    Для начала создаем пустой GameObject (Create Empty) как Child нашего Холста. Назовем его Main menu. Потом нашу панель, со всеми инструментами сделаем дочерними элементами этого объекта. Должно получиться так:


    Выбираем наш MainMenu объект и сделаем его дубликат. С выбранным элементом нажимаем комбинацию клавиш Ctrl+D. У нас появится новый объект.


    Переименовываем новый объект в Settings. Для удобства управления инактивируем MainMenu.


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

    За контроль Fullscreen будет отвечать элемент Toggle.
    За громкость – Slider.
    За качество изображения и разрешение – Dropdown.

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

    Можно настроить Spacing в Vertical layout group, чтобы между элементами было немного пространства. Добавим на панель картинку и в итоге получим такой результат:

    Программирование кнопок
    Перейдем к написанию скрипта меню.

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

    Это мы и пропишем в нашем скрипте.

    Для MainMenu добавляем новый компонент MenuControls.cs и отрываем его.

    Первое что надо сделать – удалить существующие методы Start() и Update() – тут они нам не нужны.

    Using UnityEngine.SceneManagement;
    После этого напишем свой метод для нажатия кнопки Play. Метод должен быть public - нам нужно иметь возможность видеть его за пределами нашего скрипта.

    За загрузку сцены отвечает SceneManager и у него есть метод LoadScene. Существует несколько перегрузок метода. Можно передавать имя сцены, которую вы хотите загрузить. В нашем случае это сцена «Game».

    В итоге функция будет выглядеть следующим образом.

    Public void PlayPressed() { SceneManager.LoadScene("Game"); }
    Так же создаем метод для выхода из игры:

    Public void ExitPressed() { Application.Quit(); }
    Однако в Unity мы не увидим результата работы этого метода, так как подобное работает только в билде. Для того что бы проверить, что все работает правильно, добавляем в метод строчку

    Debug.Log("Exit pressed!");
    Теперь необходимо прикрепить события кнопок к этим методам. Выбираем кнопку Play и находим в инспекторе следующее:


    Это событие кнопки, которое по нажатию будет вызывать подписанные на событие методы. Добавляем метод нажимая на +.

    В появившееся окно нужно перетащить объект, в котором содержится нужный скрипт. В нашем случае это Main Menu.

    После этого нужно выбрать скрипт MenuControls и найти метод PlayPressed().


    Точно также делаем для кнопки Exit. Только теперь выбираем метод ExitPressed().
    Для кнопки Settings нам не нужно писать никакой код, так как некоторый функционал уже встроен.

    Суть в том, что мы будем активировать GameObject. На этом этапе у вас должен быть активным MainMenu, а Settings не должно быть видно. Так же видим, что когда мы активируем Settings, он полностью перекрывает Menu. Суть в том, что играет роль порядок расположения дочерних объектов Холста – в каком порядке они расположены в иерархии в том порядке они и будут прорисовываться. Поскольку Settings у нас находятся над Main Menu, то они перекрывают меню.
    Это мы и будем использовать.

    Выбираем кнопку Settings и в OnClick() перетаскиваем наш объект Settings. В функциях выбираем GameObject ->SetActive(); и ставим галочку. Вот так:


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

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

    Настройки
    Настройки полного экрана

    Первое что мы пропишем это переключение полноэкранного и оконного режимов.
    Нужно убрать галочку с пункта Is On нашего Toggle элемента.
    Создаем скрипт для объекта Settings. Назовем его Settings.cs.

    Для начала нам надо хранить переменную типа bool которая будет отображать текущее состояние – полноэкранный режим или нет. А потом, по изменению toggle эта переменная будет переключаться на противоположное значение.

    У экрана есть свойство Screen.fullScreen типа bool. Можно просто будем присваивать значение нашей переменной isFullScreen этому свойству.

    Код выглядит так:

    Public void FullScreenToggle() { isFullScreen = !isFullScreen; Screen.fullScreen = isFullScreen; }
    Увидеть результат можно только в билде. Давайте сейчас это сделаем. Учтите, что для того что бы билд был правильным нужно оставить активным только объект MainMenu, а Settings отключить. Если это сделано, то запускаем билд через File->BuildSettings и нажимаем кнопку Build.

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

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

    Добавим эти два элемента. Сначала добавляем AudioMixer. Правой кнопкой мыши в окне Project ->Create->AudioMixer.

    Называем его GameSettings. После этого открываем окно AudioMixer: Window->Audio Mixer (Ctrl + 8).

    Что бы контролировать параметры миксера через скрипт, их нужно сделать видимыми для этого скрипта. Эта процедура называется ExposeParameters. Для этого кликаем на Mixer и в инспекторе находим volume и кликаем правой кнопкой мыши. Выбираем Expose to script:


    Теперь в окне Audio Mixer обратите внимание на пункт Exposed Parameters в верхней левой части.
    Теперь там есть параметр. Кликаем на него и называем наш параметр masterVolume. Следует запомнить имя, которое ему присваиваем – его нужно будет указать в коде.

    Переходим в Settings.cs и создаем поле AudioMixer, чтобы получить ссылку на него в коде.

    Public AudioMixer am;
    потом создаем метод

    Public void AudioVolume(float sliderValue) { am.SetFloat("masterVolume", sliderValue); }
    Метод SetFloat будет принимать значения нашего слайдера и присваивать это значение параметру “masterVolume”.

    Осталось прикрепить наш метод к событиям слайдера. Находим в инспекторе слайдера поле On Value Changed и точно так же прикрепляем объект. Вот только теперь нам надо не просто выбирать метод из списка, а использовать поле Dynamic float. Как видите, там уже есть наш метод, и он будет получать переменную от самого слайдера. Также нужно не забыть перетащить AudioMixer в соответствующее поле в компоненте Settings.cs.


    Обратите внимание, что мы напрямую привязываем значение слайдера к значениям аудио-миксера. В аудио миксере громкость изменяется от -80 до 20. Нам же достаточно менять от -80(нет звука) до 0(нормальный звук). В настройках слайдера минимальное значение выставляем на -80, максимальное на 0.
    Теперь добавим звуки в нашу игру, чтобы проверить работу скрипта.
    На canvas добавим компонент Audio Source.
    Настроим его следующим образом:


    Audio Clip – саундтрек
    Output – Мастер нашего миксера (дочерний объект)
    Loop – поставить галочку – зациклить композицию, чтобы она играла постоянно.
    Качество изображения
    В Unity уже встроены настройки качества изображения. Edit->Project Settings->Quality. В инспекторе видим Quality settings. Их можно добавлять и настраивать.

    Особенностью работы с настройками качества является следующее:
    Каждой настройке соответствует индекс, который мы можем получить из Dropdown. Все что нужно сделать – переписать соответствующие пункты в нужные индексы в нашем UI элементе. Открываем его и в инспекторе находим Dropdown(Script) и в нем пункт Options. Дальше вписываем настройки в нужном порядке. У меня получилось так:


    Дальше нужно прописать код. Мы продолжаем дополнять методами наш скрипт Settings.cs
    Создаем метод, который будет принимать int – индекс выбранного пункта.

    Public void Quality(int q) { QualitySettings.SetQualityLevel(q); }
    Сохраняем скрипт и подключаем метод к событию на нашем меню. На этот раз это событие Dropdown – On Value Changed.

    Поскольку наш метод будет получать значение от самого UI элемента, то мы выбираем название метода из группы Dymanic int. по аналогии с предыдущим пунктом.

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

    Первое что нам понадобится – массив типа Resolution где мы будем хранить значения разрешений экрана.

    Однако для пунктов выпадающего списка тип – string. Поэтому создаем список List<> в который мы будем сохранять значения возможных разрешений. Для работы со списками необходимо подключить:

    Using System.Collections.Generic;
    Также нам понадобится ссылка на соответствующий Dropdown. Для работы с UI элементами следует также прописать:

    Using UnityEngine.UI;
    В скрипте получим следующие поля:

    Resolution rsl; List resolutions; public Dropdown dropdown;
    Инициализацию и заполнение проводим в методе Awake. Этот метод вызывается при запуске объекта, соответственно выполняется раньше, чем все остальные методы.

    Получаем значения и каждое из них добавляем в List в формате ширина*высота. После этого очищаем список Dropdown и заполняем его новыми опциями.

    Public void Awake() { resolutions = new List(); rsl = Screen.resolutions; foreach (var i in rsl) { resolutions.Add(i.width +"x" + i.height); } dropdown.ClearOptions(); dropdown.AddOptions(resolutions); }
    Теперь нужно создать метод, который будет менять разрешение экрана. Как и в предыдущих пунктах – принимать значение будем от UI элемента. Создаем функцию, которая принимает int

    Public void Resolution(int r) { Screen.SetResolution(rsl[r].width, rsl[r].height, isFullScreen); }
    В SetResolution необходимо передать параметры – ширина, высота и булевскую переменную, отвечающую за полный экран. У нас такая уже есть – это isFullScreen. Передаем ее в функцию.
    Дальше не забываем подключить к соответствующему событию наш метод Resolution из группы Dynamic Int, а так же добавить ссылку на нужный Dropdown.


    Готово. Теперь можно использовать это меню вместо скучного дефолтного лаунчера. Однако, чтобы был толк нужно отключить его запуск.

    Edit->ProjectSettings-> Display Resolution Dialog-Disabled


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

    , Как добавить врагов , Контроллер врагов .
    5. Простейшая игра (3) : Система частиц , Добавление звуков , Создание текста на экране , Публикация .
    6. Трехмерный платформер. (в процессе создания)

    2. Интерфейс программы

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

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

    (Если эта тема не интересна, вы замечаете, что начинаете быстро утомляться при чтении, то можете пропустить целый раздел «Интерфейс», и сразу перейти к разделу «3. Создание простейшей игры ». Многим трудно даётся теория, гораздо эффективнее получается обучение сразу на практике. В 3-ей главе вы сразу же будете видеть результат своей работы, и обучение начнет приносить положительные эмоции. Но чтобы всё осознать, вам придётся периодически возвращаться ко второму разделу).

    Ну а мы продолжаем.

    Общий вид

    Рабочее окно Unity разбито на 6 взаимосвязанных областей. (По умолчанию в Unity включается вид «Default» или «Tall». Если у вас другой вид рабочего окна, то можете переключить его в меню «Layout» в верхнем правом углу окна).



    Перечень всех рабочих областей окна Unity:


    1. Main menu
    (Главное меню)
    Строчка текста сверху, где располагаются все команды , доступные в программе. Многие команды продублированы кнопками и меню в рабочих областях, поэтому главное меню не обязательно использовать.
    2. Project View
    (Обзор проекта)
    Список, в котором показаны все используемые файлы в игре: файл сцены, файл программного кода, графические и аудиофайлы.
    3. Hierarchy
    (Иерархия)
    Список, где перечислены все объекты , добавленные на сцену. Здесь можно работать с объектами, копировать их, переименовывать, удалять.
    4. Scene
    (Сцена)
    Область, где отображается игровой мир или игровая сцена. Здесь мы можем добавлять новые объекты, перетаскивать их, менять вид.
    5. Game
    (Игра)
    Область предпросмотра, где видно, как сцена будет выглядеть в игре . Здесь можно настраивать различные настройки экрана и видеорежима.
    6. Inspector
    (Инспектор)
    Список, состоящий из нескольких различных по виду разделов. Показывает все свойства выбранного объекта : размеры, модели, текстуры, скрипты.

    Подробнее о каждой области будет описано ниже.

    2.1. Главное меню (Main menu)


    Главное меню Unity3D стандартно располагается в верхнем левом углу окна. Здесь есть обычные команды, типа «сохранить», «загрузить», но есть и уникальные для этой программы строчки.


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

    2.2. Обзор проекта (Project View)


    Каждый проект содержит папку «Assets». Содержимое этой папки представлено в области «Project View». Это ресурсы игры: файлы-скрипты, 3D-модели, текстуры, аудиофайлы, префабы (объекты, которые можно клонировать).


    (Перемещение файлов можно выполнять в папке и через стандартный Проводник Windows, щёлкнув правой кнопкой мыши по нему и выбрав «Reveal in Exlorer». Но при перемещении ресурсов в Проводнике Windows будут потеряны все ссылки между объектами. Лучше всегда перемещать файлы внутри проекта только в «Project View»).


    Добавить новый ресурс в проект можно двумя способами:

    Перетащить файл из Проводника в Project View.

    Команда «Assets» > «Import New Assets».


    Проекты игры состоят из одного или нескольких файлов сцены. Каждая отдельная сцена – это отдельный уровень игры. Сцены так же хранятся в папке «Assets» и отображаются в «Project View».


    Некоторые игровые ресурсы не являются файлами, и создаются непосредственно в Unity. Для создания ресурса используется выпадающее меню «Create» в «Project View» или (ПКМ) > «Create».

    Меню «Create» позволяет добавлять в проект скрипты, префабы, папки и прочее. Любой ресурс или папку можно переименовать, нажав «F2» или сделав два клика по имени. Если зажать «Alt», то при раскрытии директории будут раскрыты и все поддиректории.

    2.3. Иерархия (Hierarchy)


    Иерархия содержит все объекты (GameObject) открытой сцены. Объекты, добавляемые в сцену или удаляемые из неё, отображаются или наоборот перестают отображаться в Hierarchy.


    Наследование (Parenting). В иерархии Unity объектам можно задавать наследование. Любой объект может быть дочерним по отношению к другому. Дочерний объект будет двигаться и вращаться вместе с родителем. Для создания дочерней связи достаточно перетащить объект на «родителя» в Hierarchy.

    2.4. Сцена (Scene View)


    Игровая сцена используется для расстановки объектов на уровне (окружение, персонажи, камеры, системы частиц и прочее). Сцена может являться игровым уровнем, главным меню, заставкой.


    В 2D режиме управление интуитивно понятно. В 3D режиме есть множество приёмов для перемещения по сцене:

    * Зажатый ПКМ активирует режим свободного полёта.

    * Перемещаться можно клавишами WASD на манер игры в жанре FPS.

    * Выберите объект в списке иерархии и нажмите «F». Вид сцены будет центрирован и масштабирован по выбранному объекту.

    * При зажатом Alt, ЛКМ будет крутить камеру вокруг текущей точки опоры.

    * При зажатом Alt, СКМ будет перемещать камеру.

    * При зажатом Alt, ПКМ будет масштабировать вид сцены.

    * Альтернативный режим перемещений - клавиша Q.

    2.5. Игровой вид (Game View) Сайт сайт


    Game View - предпросмотр игры (рендер из игровой камеры).

    В расположении окон по умолчанию «Игровой вид» отсутствует, для его включения нужно выбрать вкладку «Game» над игровой сценой. В других режима окон «Игровой вид» занимает своё отдельное место.


    Три кнопки в верхней части окна Юнити отвечают за управление предпросмотром игры: «Play», «Pause» и «Step». (Все изменения, произведённые во время предпросмотра, сбрасываются при выходе из него. Исключением являются изменения в префабах).


    Game View Control Bar

    Первое выпадающее меню в «Game View» - это контроль пропорций изображения (Aspect Drop-down). На некоторых дисплеях это соотношение отличается от стандартного 4:3 (например, на широкоформатных мониторах - 16:10).

    Кнопка «Gizmos» включает отображение контейнеров гизмо в «Game View».

    Последняя кнопка - «Stats». Она показывает статистку рендеринга (Rendering Statistics), полезную при оптимизации.

    2.6. Инспектор (Inspector)


    GameObject – это любой объект в игре. Следует понимать, что сам по себе объект ничего не делает. Ему требуются специальные свойства, прежде чем он станет персонажем, окружением или визуальным эффектом.

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


    Перечень объектов содержится в области «Hierarchy». Если в списке иерархии мы выберем любой объект, то в области «Inspector» (Инспектор) отобразятся все свойства этого конкретного объекта. Здесь же можно редактировать все эти свойства или добавлять новые.


    Объект может содержать в себе такие типы компонентов: расположение в пространстве (Transform), меши (meshes), скрипты (scripts), звуки, ИС (Lights) и другие элементы.

    Status Bar

    Строка состояния (Status Bar) расположена в нижней части окна редактора. Она отображает ошибки компиляции и логи дебага. Если возникают проблемы с игрой, стоит заглянуть в строку состояния. Двойное нажатие на неё вызовет окно консоли (Console), в котором отображаются все ошибки.

    2.7. Настройка рабочего окна

    Можно настраивать расположение (Layout) секторов, перетаскивая их за закладки. Если перетащить закладку в область закладок уже существующего окна, то она будет добавлена к присутствующим там закладкам. Также можно прикрепить сектор к краю экрана или краю другого сектора.

    Сектор может быть пристыкован к одной из сторон существующего окна.

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

    Плавающее окно редактор похоже на главное окно, но не имеет панели инструментов (Toolbar).

    Когда расположение секторов задано, его можно сохранить и загрузить в нужный момент через выпадающее меню Layout (Save и Load).

    Полностью настроенное расположение секторов.

    В любое время можно правым кликом по закладке вызвать меню с дополнительными возможностями, такими как максимизация сектора или добавление новых закладок.

    Достижение "Почётный читатель сайт"
    Понравилась статья? В благодарность можно поставить лайк через любую социальную сеть. Для вас это - один клик, для нас - очередной шаг вверх в рейтинге игровых сайтов.
    Достижение "Почётный спонсор сайт"
    Для особо щедрых есть возможность перевести деньги на счет сайта. В этом случае вы можете повлиять на выбор новой темы для статьи или прохождения.
    money.yandex.ru/to/410011922382680

    Unity позволяет не делать меню настроек, так как есть встроенный диалог настроек качества и разрешения перед запуском игры. Однако если вы хотите сделать вашу игру неповторимой и показать, на что способны, как дизайнер, то эта статья именно для вас. После прохождения этого туториала вы сможете создавать свое собственное меню с таким функционалом – настройки громкости, разрешения, качества изображения, переключения полноэкранного и оконного режима. Интересно? Тогда запускай Unity и поехали!
    Создание и дизайн главного меню и меню настроек
    1. Создаем две сцены: Menu и Game
    2. Открываем File->BuildSettings и перетаскиваем созданные сцены в Scenes in build.

    Теперь можем приступать к созданию пользовательского интерфейса. Откройте сцену Menu и добавьте Panel. Сразу добавляется Canvas(Холст) и дочерним объектом к нему добавляется Panel (Панель).


    Обратим внимание на инспектор для Canvas. А именно на компонент Canvas.


    Render Mode автоматически выставлен на Screen Space – Overlay.

    Немного информации:

    Screen Space – Overlay:

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

    Важен порядок размещения объектов в иерархии. Холст Screen Space – Overlay должен находиться в самом верху иерархии, иначе он может пропасть из виду.


    Screen Space – Camera:

    В таком случае, Холст прикрепляется в камере. Для такой настройки обязательно нужно указать камеру которой соответствует Холст. Как и в предыдущем пункте, Холст будет менять свой масштаб в зависимости от разрешения и размера экрана, а также от области видимости камеры.

    Так же для Холста с такими настройками важно размещение относительно других объектов. На переднем плане будут объекты, которые находятся ближе к камере, не зависимо от того, это UI или другие GameObjects.


    World Space:

    Холст размещается, как любой другой объект без привязки к камере или экрану, он может быть ориентирован как вам угодно, размер Холста задается с помощью RectTransform, но то, как его будет видно во время игры, будет зависеть от положения камеры.
    В данном задании мы будем использовать Screen Space – Overlay.
    Давайте настроим цвет панели. Можно также добавить картинку на фон. Для этого нужно обратить внимание на компонент Image в Инспекторе панели.


    Как видите, картинка добавляется в поле Source Image. Картинку можно просто перетащить из файлов проекта, либо кликнуть на кружочек справа от поля.

    Цвет выбирается в поле Color, либо с помощью инструмента пипетки.
    Важно знать, что пипетка позволяет выбрать цвет за пределами Unity.
    После того, как выбрали цвет или картинку, нам нужно разместить кнопки, которые всем будут управлять, а так же текст. Чтобы упростить себе задачу, для Panel мы добавим еще один компонент, который называется Vertical Layout Group. И сразу настроим его.

    Нам необходимо разместить все кнопки и текст по центру экрана. Для этого находим в компоненте Vertical Layout Group пункт Child Alignment и выбираем Middle Center. Теперь все наши элементы, будь то кнопки или текст, будут выравниваться по центру, независимо от разрешения экрана.

    Так же убираем галочки с ChildForceExpand. Должно получиться так:


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

    В нашем меню должны быть следующие кнопки:

    1. Start Game
    2. Settings
    3. Exit

    Сейчас, добавляем Text дочерним элементом нашей Панели. Можете заметить, как он прикрепляется по центру. Иногда для этого требуется созданный элемент в окне Scene просто передвинуть на Panel и тогда он выровняется. Настраиваем текст так, как хочется с помощью компонента Text(Script) в инспекторе.


    После добавляем Button. Кнопка добавится под текст.

    Разберем компонент Button нашей кнопки. Нас интересует Transition – то, как кнопка будет реагировать на действия мышки. В Unity реализовано несколько способов. Пока рассмотрим довольно простой Color Tint. Это значит, что кнопка будет менять свой цвет при определенных состояниях. Интерфейс для настройки довольно удобный. Настраиваем так, как нравится.


    Так же у объекта Button есть дочерний объект Text – это текст который будет отображаться на кнопке. В тексте прописываем Play.

    Кажется, Текст и Кнопки находятся слишком близко друг к другу.

    Что бы это исправить для нашего Текста Menu добавляем еще один компонент, который называется Layout Element. И ставим галочку напротив Ignore Layout.


    После этого выставляем Anchor на Middle-Center.


    Потом добавляем еще три кнопки и называем их Settings, Credits, Exit.
    Можно поменять размеры кнопок. На этом этапе меню выглядит так:

    Переходы между main menu и settings
    Что бы переходить на меню опций не обязательно делать новую сцену.
    Для начала создаем пустой GameObject (Create Empty) как Child нашего Холста. Назовем его Main menu. Потом нашу панель, со всеми инструментами сделаем дочерними элементами этого объекта. Должно получиться так:


    Выбираем наш MainMenu объект и сделаем его дубликат. С выбранным элементом нажимаем комбинацию клавиш Ctrl+D. У нас появится новый объект.


    Переименовываем новый объект в Settings. Для удобства управления инактивируем MainMenu.


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

    За контроль Fullscreen будет отвечать элемент Toggle.
    За громкость – Slider.
    За качество изображения и разрешение – Dropdown.

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

    Можно настроить Spacing в Vertical layout group, чтобы между элементами было немного пространства. Добавим на панель картинку и в итоге получим такой результат:

    Программирование кнопок
    Перейдем к написанию скрипта меню.

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

    Это мы и пропишем в нашем скрипте.

    Для MainMenu добавляем новый компонент MenuControls.cs и отрываем его.

    Первое что надо сделать – удалить существующие методы Start() и Update() – тут они нам не нужны.

    Using UnityEngine.SceneManagement;
    После этого напишем свой метод для нажатия кнопки Play. Метод должен быть public - нам нужно иметь возможность видеть его за пределами нашего скрипта.

    За загрузку сцены отвечает SceneManager и у него есть метод LoadScene. Существует несколько перегрузок метода. Можно передавать имя сцены, которую вы хотите загрузить. В нашем случае это сцена «Game».

    В итоге функция будет выглядеть следующим образом.

    Public void PlayPressed() { SceneManager.LoadScene("Game"); }
    Так же создаем метод для выхода из игры:

    Public void ExitPressed() { Application.Quit(); }
    Однако в Unity мы не увидим результата работы этого метода, так как подобное работает только в билде. Для того что бы проверить, что все работает правильно, добавляем в метод строчку

    Debug.Log("Exit pressed!");
    Теперь необходимо прикрепить события кнопок к этим методам. Выбираем кнопку Play и находим в инспекторе следующее:


    Это событие кнопки, которое по нажатию будет вызывать подписанные на событие методы. Добавляем метод нажимая на +.

    В появившееся окно нужно перетащить объект, в котором содержится нужный скрипт. В нашем случае это Main Menu.

    После этого нужно выбрать скрипт MenuControls и найти метод PlayPressed().


    Точно также делаем для кнопки Exit. Только теперь выбираем метод ExitPressed().
    Для кнопки Settings нам не нужно писать никакой код, так как некоторый функционал уже встроен.

    Суть в том, что мы будем активировать GameObject. На этом этапе у вас должен быть активным MainMenu, а Settings не должно быть видно. Так же видим, что когда мы активируем Settings, он полностью перекрывает Menu. Суть в том, что играет роль порядок расположения дочерних объектов Холста – в каком порядке они расположены в иерархии в том порядке они и будут прорисовываться. Поскольку Settings у нас находятся над Main Menu, то они перекрывают меню.
    Это мы и будем использовать.

    Выбираем кнопку Settings и в OnClick() перетаскиваем наш объект Settings. В функциях выбираем GameObject ->SetActive(); и ставим галочку. Вот так:


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

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

    Настройки
    Настройки полного экрана

    Первое что мы пропишем это переключение полноэкранного и оконного режимов.
    Нужно убрать галочку с пункта Is On нашего Toggle элемента.
    Создаем скрипт для объекта Settings. Назовем его Settings.cs.

    Для начала нам надо хранить переменную типа bool которая будет отображать текущее состояние – полноэкранный режим или нет. А потом, по изменению toggle эта переменная будет переключаться на противоположное значение.

    У экрана есть свойство Screen.fullScreen типа bool. Можно просто будем присваивать значение нашей переменной isFullScreen этому свойству.

    Код выглядит так:

    Public void FullScreenToggle() { isFullScreen = !isFullScreen; Screen.fullScreen = isFullScreen; }
    Увидеть результат можно только в билде. Давайте сейчас это сделаем. Учтите, что для того что бы билд был правильным нужно оставить активным только объект MainMenu, а Settings отключить. Если это сделано, то запускаем билд через File->BuildSettings и нажимаем кнопку Build.

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

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

    Добавим эти два элемента. Сначала добавляем AudioMixer. Правой кнопкой мыши в окне Project ->Create->AudioMixer.

    Называем его GameSettings. После этого открываем окно AudioMixer: Window->Audio Mixer (Ctrl + 8).

    Что бы контролировать параметры миксера через скрипт, их нужно сделать видимыми для этого скрипта. Эта процедура называется ExposeParameters. Для этого кликаем на Mixer и в инспекторе находим volume и кликаем правой кнопкой мыши. Выбираем Expose to script:


    Теперь в окне Audio Mixer обратите внимание на пункт Exposed Parameters в верхней левой части.
    Теперь там есть параметр. Кликаем на него и называем наш параметр masterVolume. Следует запомнить имя, которое ему присваиваем – его нужно будет указать в коде.

    Переходим в Settings.cs и создаем поле AudioMixer, чтобы получить ссылку на него в коде.

    Public AudioMixer am;
    потом создаем метод

    Public void AudioVolume(float sliderValue) { am.SetFloat("masterVolume", sliderValue); }
    Метод SetFloat будет принимать значения нашего слайдера и присваивать это значение параметру “masterVolume”.

    Осталось прикрепить наш метод к событиям слайдера. Находим в инспекторе слайдера поле On Value Changed и точно так же прикрепляем объект. Вот только теперь нам надо не просто выбирать метод из списка, а использовать поле Dynamic float. Как видите, там уже есть наш метод, и он будет получать переменную от самого слайдера. Также нужно не забыть перетащить AudioMixer в соответствующее поле в компоненте Settings.cs.


    Обратите внимание, что мы напрямую привязываем значение слайдера к значениям аудио-миксера. В аудио миксере громкость изменяется от -80 до 20. Нам же достаточно менять от -80(нет звука) до 0(нормальный звук). В настройках слайдера минимальное значение выставляем на -80, максимальное на 0.
    Теперь добавим звуки в нашу игру, чтобы проверить работу скрипта.
    На canvas добавим компонент Audio Source.
    Настроим его следующим образом:


    Audio Clip – саундтрек
    Output – Мастер нашего миксера (дочерний объект)
    Loop – поставить галочку – зациклить композицию, чтобы она играла постоянно.
    Качество изображения
    В Unity уже встроены настройки качества изображения. Edit->Project Settings->Quality. В инспекторе видим Quality settings. Их можно добавлять и настраивать.

    Особенностью работы с настройками качества является следующее:
    Каждой настройке соответствует индекс, который мы можем получить из Dropdown. Все что нужно сделать – переписать соответствующие пункты в нужные индексы в нашем UI элементе. Открываем его и в инспекторе находим Dropdown(Script) и в нем пункт Options. Дальше вписываем настройки в нужном порядке. У меня получилось так:


    Дальше нужно прописать код. Мы продолжаем дополнять методами наш скрипт Settings.cs
    Создаем метод, который будет принимать int – индекс выбранного пункта.

    Public void Quality(int q) { QualitySettings.SetQualityLevel(q); }
    Сохраняем скрипт и подключаем метод к событию на нашем меню. На этот раз это событие Dropdown – On Value Changed.

    Поскольку наш метод будет получать значение от самого UI элемента, то мы выбираем название метода из группы Dymanic int. по аналогии с предыдущим пунктом.

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

    Первое что нам понадобится – массив типа Resolution где мы будем хранить значения разрешений экрана.

    Однако для пунктов выпадающего списка тип – string. Поэтому создаем список List<> в который мы будем сохранять значения возможных разрешений. Для работы со списками необходимо подключить:

    Using System.Collections.Generic;
    Также нам понадобится ссылка на соответствующий Dropdown. Для работы с UI элементами следует также прописать:

    Using UnityEngine.UI;
    В скрипте получим следующие поля:

    Resolution rsl; List resolutions; public Dropdown dropdown;
    Инициализацию и заполнение проводим в методе Awake. Этот метод вызывается при запуске объекта, соответственно выполняется раньше, чем все остальные методы.

    Получаем значения и каждое из них добавляем в List в формате ширина*высота. После этого очищаем список Dropdown и заполняем его новыми опциями.

    Public void Awake() { resolutions = new List(); rsl = Screen.resolutions; foreach (var i in rsl) { resolutions.Add(i.width +"x" + i.height); } dropdown.ClearOptions(); dropdown.AddOptions(resolutions); }
    Теперь нужно создать метод, который будет менять разрешение экрана. Как и в предыдущих пунктах – принимать значение будем от UI элемента. Создаем функцию, которая принимает int

    Public void Resolution(int r) { Screen.SetResolution(rsl[r].width, rsl[r].height, isFullScreen); }
    В SetResolution необходимо передать параметры – ширина, высота и булевскую переменную, отвечающую за полный экран. У нас такая уже есть – это isFullScreen. Передаем ее в функцию.
    Дальше не забываем подключить к соответствующему событию наш метод Resolution из группы Dynamic Int, а так же добавить ссылку на нужный Dropdown. 

    В один прекрасный момент вам приспичило создать меню. Контекстное или слитое c общим меню Unity. И тут вы поняли что ничего о том как это делается и не знаете. Или знаете не всё. Данная статья - обзор по способам построить меню в Unity. Этих способов благо достаточно - и каждый из них специфичен.

    В чем сабж статьи?
    Если вдруг вам приходится заниматься редакторами профессионально, как мне на день насущный, то данные знания для вас просто обязательны - меню применяются повсеместно. Когда я начинал только изучать это мне приходилось пилить велосипедные комбобоксы из юнити-вики, потому что я не знал как делать это правильно. Способ тот кривой и вам на такие вещи я натыкаться не советую - почему бы не начать сразу делать их правильно?

    Итак, способы

    ContextMenu

    Самый первый способ, с которым, вы вероятно даже сталкивались - это ContextMenu .
    Следующий пример демонстрирует принцип работы данного атрибута:

    Using UnityEngine; public class MyClass: MonoBehaviour { void SayMessage() { Debug.Log ("I love you, DevTribe"); } }

    В результате мы получим пункт меню в следующем месте:

    При нажатии на данный пункт выполнится наш метод SayMessage() .

    Однако, данный способ применяется в очень узком месте:

    • только в скриптах, наследуемых от класса Behaviour (всех сценариях и компонентах)

    Способ применения: когда нужно быстренько вызвать какой-нибудь метод из компонента. В таких случаях переписывать инспектор объекта с целью добавить пару кнопок - нецелесообразно. Да и некоторые пунктики лучше иметь именно там.

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

    ContextMenuItem

    Данный способ похож на предыдущий - это тоже атрибут. Но пишется он не туда и не так.
    Пример написания:

    Using UnityEngine; public class MyClass: MonoBehaviour { public string ff; void SayMessage() { Debug.Log("I love you, DevTribe"); } }

    Данный способ добавляет контекстное меню для нашего поля(в примере - поле ff). При щелчке правой кнопкой мыши мы увидим меню с данным пунктом:

    Первым параметром мы указываем название поля, вторым - название метода, который будет вызван.
    В остальном требования те же:

    • используется только в скриптах, наследуемых от класса Behaviour (всех сценариях и компонентах)
    • только для методов экземпляра (никакого "static"!)
    • метод не должен иметь параметров

    Способ применения: аналогично первому способу, но с учетом того, что выполнение метода относится к определенному полю, а не компоненту в целом.

    Этот способ всё так же лепится в игровую сборку.
    Для данного способа чуть ниже (в разделе "Параметры строки меню") будет расписано чуть подробней.

    Лично по мне - данный способ весьма не очевиден, на практике я им ни разу и не пользовался. Однако раз уж статья про меню - странно было бы упустить этот способ, тем более что в документации нет примера на C# (хотя, он и очевиден, но принцип действия не очень).

    Внимание
    Все последующие способы являются расширением редактора и относятся только к сборке редактора. Это говорит о том, что скрипты, исполняющие данный код должны лежать в папке /Assets/Editor/ вашего проекта .

    MenuItem

    С атрибутами, однако, мы еще не закончили. Остался у нас в запасе один, о котором я умолчал. Это атрибут MenuItem , добавляющие пункты в основное меню редактора.

    Пример кода:

    Using UnityEditor; using UnityEngine; public static class MyClass { static void SayMessage() { Debug.Log("I love you, DevTribe"); } }

    Данный способ приведет к следующему результату:

    Естественно, у данного способа есть свои ограничения, но они отличаются от тех, что вы прочли ранее:

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

    Способ применения: используется повсеместно - для открытия окон, для исполнения последовательности действий.
    Кроме того данный способ имеет немного "читерства" и гибкой настройки, о которой я напишу чуть ниже в разделе "Параметры строки меню".

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

    Помимо этого данный атрибут может по желанию реализовывать несколько параметров:
    isValidateFunction - помечает функцию как функцию-валидатор. Это значит, что данная функция не показывает пункт меню, а проверяет его доступность. Такая функция должна возвращать bool , говорящий, задисейблена данная кнопка или активна. Используется в паре с обычным пунктом меню (не валидатором с точно таким же путем, включая дополнительные параметры).
    Пример:

    Static bool CanBeShow() { return "ILove".Length == 5; } static void ShowMessage() { Debug.Log("I love you, DevTribe"); }

    Опять же, про это почему то в документации особо-то и не расписывали.

    priority - порядок пункта меню. Обычно стандартные элементы отсчитывают порядок от 0 и выше и, следовательно, указав "-1" мы установим наш пункт в самом верху списка.

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

    Так же в рамках данного способа вы можете изменять контекстные меню инспектора, с таким же результатом как у способа ContextMenu . Однако, тут есть свой сахар - вы можете изменять пункты уже написанных компонентов, таких как Transform, Rigidbody и так далее.
    Для этого способа зарезервировано меню CONTEXT, после которого вы вводите название нужного вам компонента:

    Using UnityEditor; using UnityEngine; public static class MyClass { static void ShowMessage(MenuCommand command) { Debug.Log("I love you, DevTribe"); } }

    Как вы могли заметить, я применил в функции параметр - он не обязателен, но возможен при таком раскладе. Данный параметр содержит в себе поле "context", содержащее ссылку на компонент.
    У данного способа так же нет ограничений в виде конкретного типа - например в качестве пути вы можете указать CONTEXT/MonoBehaviour/ и прикрепить пункт сразу ко всем наследникам данного класса. Указывать можно любой тип, наследуемый от Component (включая сам тип Component ).
    Помимо "CONTEXT/" вы так же можете использовать следующие "специальные пути":

    • "Assets/" - для добавления пункта в контекстное меню в окне ассетов
    • "Assets/Create/" - для добавления пункта в меню кнопки "Create" в окне с ассетами

    GUI методы

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

    • EditorGUILayout.Popup - строковый попап. Принцип работы: узнаем индекс строки из массива
    • EditorGUILayout.IntPopup - числовой попап. Принцип работы: узнаем индекс числа из массива, при том можно подменить числа "строковым отображением" (показывать текст, подразумевать число)
    • EditorGUILayout.EnumPopup - попап для перечислений. Принцип работы: указываем текущее значение перечислителя, получаем новое.
    • EditorGUILayout.MaskField - попап, позволяющий выбрать несколько значений, компонуя их в маску (обычно так делают выборки слоев/тегов)

    В случае с отображением "строки" опять же применимы правила форматирования строки, такие как сепараторы и косая черта, о которых я писал выше.

    Особо за них говорить нечего, всё можно узнать в документации.

    ShowAsDropDown

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

    Public class AutocompleteMenu: EditorWindow { public int visibleCount = 6; public static void Open(Vector2 position, OnControlEventDelegate onControlEvent, params GUIContent variants) { var window = CreateInstance(); window.variants = variants; window.onControlEvent = onControlEvent; window.ShowAsDropDown(new Rect(position.x, position.y, 1, 1), new Vector2(200, 20*window.visibleCount)); } public delegate void OnControlEventDelegate(Event @event); public OnControlEventDelegate onControlEvent; }

    Соответственно далее в такое окно дописывается метод OnGUI где рисуется абсолютно кастомный попап под наши потребности.
    Метод "ShowAsDropDown" просит указать место своего появления в абсолютных координатах. Часто нам нужно отображать такой попап на месте щелчка мышью. Такие координаты мыши можно достать следующей командой:

    Var screenMousePos = GUIUtility.GUIToScreenPoint(Event.current.mousePosition);

    Соль этого способа в том, что вид попапа может быть любым - вы можете сделать, например, выбор иконок или оформить окно по своему желанию.

    PopupWindow

    Данный способ по сути является автоматизацией предыдущего, предоставляя методы OnOpen /OnClose и заготовленный метод Show .
    Суть в том чтобы пронаследовать наш класс от класса PopupWindow . Однако запомните - данная реализация ограничена в плане возврата обратного значения. Если вы используете попап для конкретных целей - данное окно вам подойдет, но для универсальных попапов желательно использовать ShowAsDropDown .

    DisplayPopupMenu

    Следующий способ - обычный попап, который используется во всех стандартных способах. По сути он лежит "в корне" работы GUI методов, которые я описывал немногим выше.
    Этот способ использует делегат для обратного вызова.
    Соль в вызове метода , при вызове которого мы указываем ссылку на метод. Если вы хотите запаковать вызов пункта в метод без делегата вам понадобится создать класс аля:

    Public class PopupCallback { public static PopupCallback instance; public int controlId; public bool change; public delegate void SetValueDelegate(object data); public static void Init(int controlId) { instance = new PopupCallback { controlId = controlId }; } public static bool Its(int controlId) { return instance.w(x => x.change) && instance.controlId == controlId; } public object obj; public static EditorUtility.SelectMenuItemFunction SetValueStandart(int controlId) { Init(controlId); return instance._SetValue; } public static SetValueDelegate SetValue(int controlId) { Init(controlId); return instance._SetValue; } public void _SetValue(object userData, string options, int selected) { change = true; obj = selected; } public void _SetValue(object data) { change = true; obj = data; } public static object GetValue() { var result = instance.obj; instance = null; return result; } public static T GetValue() { var result = (T)instance.obj; instance = null; return result; } }

    Данный класс содержит методы, необходимые для манипуляций с данными попапа. Если коротко - то в коде GUI вы должны завести id для вашего контрола и затем вызвать EditorUtility.DisplayPopupMenu . В данный метод вы подставляете на место делегата метод PopupCallback.SetValueStandart . После проверяете, равен ли id инстанса в PopupCallback вашему id (это уже идет код чтобы отловить обратный вызов). В положительном случае - достаете из инстанса данные.
    Не совсем очевидно, но это именно то что делают Unity для формирования таких методов.

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

    GenericMenu

    Завершает данный список класс GenericMenu . Его синтаксис необычайно прост - вы создаете экземпляр этого класса и добавляете через методы новые пункты. По завершению вызываете ShowAsContext . Каждый пункт может иметь свой обратный вызов, что упрощает создание меню с командами.

    Кастомный попап без фокуса

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

    PopupWindowWithoutFocus.cs

    Using System; using System.Collections.Generic; using System.Reflection; using UnityEditor; using UnityEngine; internal class PopupWindowWithoutFocus: EditorWindow { // Fields private Rect m_ActivatorRect; private const float m_BorderWidth = 1f; private Vector2 m_LastWantedSize = Vector2.zero; private PopupLocationHelper.PopupLocation m_LocationPriorityOrder; private PopupWindowContent m_WindowContent; private static Rect s_LastActivatorRect; private static double s_LastClosedTime; private static PopupWindowWithoutFocus s_PopupWindowWithoutFocus; // Methods private PopupWindowWithoutFocus() { hideFlags = HideFlags.DontSave; } private void FitWindowToContent() { Vector2 windowSize = m_WindowContent.GetWindowSize(); if (m_LastWantedSize != windowSize) { m_LastWantedSize = windowSize; Vector2 minSize = windowSize + new Vector2(2f * m_BorderWidth, 2f * m_BorderWidth); Rect rect = PopupLocationHelper.GetDropDownRect(m_ActivatorRect, minSize, minSize, null, m_LocationPriorityOrder); var baseType = GetType().BaseType; if (baseType != null) { var fieldInfo = baseType.GetField("m_Pos", BindingFlags.Instance | BindingFlags.NonPublic); if (fieldInfo != null) fieldInfo.SetValue(this, rect); } var vector3 = new Vector2(rect.width, rect.height); maxSize = vector3; base.minSize = vector3; } } public static void Hide() { if (s_PopupWindowWithoutFocus != null) { s_PopupWindowWithoutFocus.Close(); } } private void Init(Rect activatorRect, PopupWindowContent windowContent, PopupLocationHelper.PopupLocation locationPriorityOrder) { m_WindowContent = windowContent; m_WindowContent.GetType() .GetProperty("editorWindow") .GetSetMethod(true) .Invoke(m_WindowContent, new object {this}); m_ActivatorRect = GUIToScreenRect(activatorRect); m_LastWantedSize = windowContent.GetWindowSize(); m_LocationPriorityOrder = locationPriorityOrder; Vector2 minSize = windowContent.GetWindowSize() + new Vector2(m_BorderWidth * 2f, m_BorderWidth * 2f); position = PopupLocationHelper.GetDropDownRect(m_ActivatorRect, minSize, minSize, null, m_LocationPriorityOrder); ShowPopup(); Repaint(); } internal static Rect GUIToScreenRect(Rect guiRect) { Vector2 vector = GUIUtility.GUIToScreenPoint(new Vector2(guiRect.x, guiRect.y)); guiRect.x = vector.x; guiRect.y = vector.y; return guiRect; } public static bool IsVisible() { return (s_PopupWindowWithoutFocus != null); } private void OnDisable() { s_LastClosedTime = EditorApplication.timeSinceStartup; if (m_WindowContent != null) { m_WindowContent.OnClose(); } s_PopupWindowWithoutFocus = null; } private void OnEnable() { s_PopupWindowWithoutFocus = this; } private static bool OnGlobalMouseOrKeyEvent(EventType type, KeyCode keyCode, Vector2 mousePosition) { if (s_PopupWindowWithoutFocus != null) { if ((type == EventType.KeyDown) && (keyCode == KeyCode.Escape)) { s_PopupWindowWithoutFocus.Close(); return true; } if ((type == EventType.MouseDown) && !s_PopupWindowWithoutFocus.position.Contains(mousePosition)) { s_PopupWindowWithoutFocus.Close(); return true; } } return false; } internal void OnGUI() { FitWindowToContent(); var rect = new Rect(m_BorderWidth, m_BorderWidth, position.width - (2f * m_BorderWidth), position.height - (2f * m_BorderWidth)); m_WindowContent.OnGUI(rect); GUI.Label(new Rect(0f, 0f, position.width, position.height), GUIContent.none, "grey_border"); } private static bool ShouldShowWindow(Rect activatorRect) { if (((EditorApplication.timeSinceStartup - s_LastClosedTime) < 0.2) && !(activatorRect != s_LastActivatorRect)) { return false; } s_LastActivatorRect = activatorRect; return true; } public static void Show(Rect activatorRect, PopupWindowContent windowContent) { Show(activatorRect, windowContent, null); } internal static void Show(Rect activatorRect, PopupWindowContent windowContent, PopupLocationHelper.PopupLocation locationPriorityOrder) { if (ShouldShowWindow(activatorRect)) { if (s_PopupWindowWithoutFocus == null) { s_PopupWindowWithoutFocus = CreateInstance(); } s_PopupWindowWithoutFocus.Init(activatorRect, windowContent, locationPriorityOrder); } } } internal static class PopupLocationHelper { private static Type _typePopupLocationHelper; private static Type typePopupLocationHelper { get { if (_typePopupLocationHelper == null) _typePopupLocationHelper = typeof(EditorGUI).Assembly.GetType("UnityEditor.PopupLocationHelper"); return _typePopupLocationHelper; } } private static Type _typeContainerWindow; private static Type typeContainerWindow { get { if (_typeContainerWindow == null) _typeContainerWindow = typeof(EditorGUI).Assembly.GetType("UnityEditor.ContainerWindow"); return _typeContainerWindow; } } private static MethodInfo _methodFitWindowRectToScreen; private static MethodInfo methodFitWindowRectToScreen { get { if (_methodFitWindowRectToScreen == null) _methodFitWindowRectToScreen = typeContainerWindow.GetMethod("FitWindowRectToScreen", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); return _methodFitWindowRectToScreen; } } private static MethodInfo _methodFitRectToScreen; private static MethodInfo methodFitRectToScreen { get { if (_methodFitRectToScreen == null) _methodFitRectToScreen = typeContainerWindow.GetMethod("FitRectToScreen", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); return _methodFitRectToScreen; } } // Methods private static Rect FitRect(Rect rect, object popupContainerWindow) { if (popupContainerWindow != null) { return (Rect)methodFitWindowRectToScreen.Invoke(popupContainerWindow, new object {rect, true, true}); } return (Rect) methodFitRectToScreen.Invoke(null, new object {rect, true, true}); } public static Rect GetDropDownRect(Rect buttonRect, Vector2 minSize, Vector2 maxSize, object popupContainerWindow) { return GetDropDownRect(buttonRect, minSize, maxSize, popupContainerWindow, null); } public static Rect GetDropDownRect(Rect buttonRect, Vector2 minSize, Vector2 maxSize, object popupContainerWindow, PopupLocation locationPriorityOrder) { if (locationPriorityOrder == null) { PopupLocation locationArray1 = new PopupLocation; locationArray1 = PopupLocation.Above; locationPriorityOrder = locationArray1; } List rects = new List(); PopupLocation locationArray = locationPriorityOrder; for (int i = 0; i < locationArray.Length; i++) { Rect rect; switch (locationArray[i]) { case PopupLocation.Below: if (!PopupBelow(buttonRect, minSize, maxSize, popupContainerWindow, out rect)) { break; } return rect; case PopupLocation.Above: if (!PopupAbove(buttonRect, minSize, maxSize, popupContainerWindow, out rect)) { goto Label_0079; } return rect; case PopupLocation.Left: if (!PopupLeft(buttonRect, minSize, maxSize, popupContainerWindow, out rect)) { goto Label_0099; } return rect; case PopupLocation.Right: if (!PopupRight(buttonRect, minSize, maxSize, popupContainerWindow, out rect)) { goto Label_00B9; } return rect; default: { continue; } } rects.Add(rect); continue; Label_0079: rects.Add(rect); continue; Label_0099: rects.Add(rect); continue; Label_00B9: rects.Add(rect); } return GetLargestRect(rects); } private static Rect GetLargestRect(IEnumerable rects) { var rect = new Rect(); foreach (Rect rect2 in rects) { if ((rect2.height * rect2.width) > (rect.height * rect.width)) { rect = rect2; } } return rect; } private static bool PopupAbove(Rect buttonRect, Vector2 minSize, Vector2 maxSize, object popupContainerWindow, out Rect resultRect) { Rect rect = new Rect(buttonRect.x, buttonRect.y - maxSize.y, maxSize.x, maxSize.y); float num = 0f; rect.yMin -= num; rect = FitRect(rect, popupContainerWindow); float a = Mathf.Max((buttonRect.y - rect.y) - num, 0f); if (a >= minSize.y) { float height = Mathf.Min(a, maxSize.y); resultRect = new Rect(rect.x, buttonRect.y - height, rect.width, height); return true; } resultRect = new Rect(rect.x, buttonRect.y - a, rect.width, a); return false; } private static bool PopupBelow(Rect buttonRect, Vector2 minSize, Vector2 maxSize, object popupContainerWindow, out Rect resultRect) { var rect = new Rect(buttonRect.x, buttonRect.yMax, maxSize.x, maxSize.y) { height = maxSize.y + k_SpaceFromBottom }; rect = FitRect(rect, popupContainerWindow); float a = Mathf.Max((rect.yMax - buttonRect.yMax) - k_SpaceFromBottom, 0f); if (a >= minSize.y) { float height = Mathf.Min(a, maxSize.y); resultRect = new Rect(rect.x, buttonRect.yMax, rect.width, height); return true; } resultRect = new Rect(rect.x, buttonRect.yMax, rect.width, a); return false; } private static bool PopupLeft(Rect buttonRect, Vector2 minSize, Vector2 maxSize, object popupContainerWindow, out Rect resultRect) { var rect = new Rect(buttonRect.x - maxSize.x, buttonRect.y, maxSize.x, maxSize.y); const float num = 0f; rect.xMin -= num; rect.height += k_SpaceFromBottom; rect = FitRect(rect, popupContainerWindow); float a = Mathf.Max((buttonRect.x - rect.x) - num, 0f); float width = Mathf.Min(a, maxSize.x); resultRect = new Rect(rect.x, rect.y, width, rect.height - k_SpaceFromBottom); return (a >= minSize.x); } private static bool PopupRight(Rect buttonRect, Vector2 minSize, Vector2 maxSize, object popupContainerWindow, out Rect resultRect) { var rect = new Rect(buttonRect.xMax, buttonRect.y, maxSize.x, maxSize.y); const float num = 0f; rect.xMax += num; rect.height += k_SpaceFromBottom; rect = FitRect(rect, popupContainerWindow); float a = Mathf.Max((rect.xMax - buttonRect.xMax) - num, 0f); float width = Mathf.Min(a, maxSize.x); resultRect = new Rect(rect.x, rect.y, width, rect.height - k_SpaceFromBottom); return (a >= minSize.x); } // Properties private static float k_SpaceFromBottom { get { if (Application.platform == RuntimePlatform.OSXEditor) { return 10f; } return 0f; } } // Nested Types public enum PopupLocation { Below, Above, Left, Right } }

    В качестве примера используйте этот код:

    Using UnityEditor; using UnityEngine; public class ExampleUnfocusWindow: PopupWindowContent { public static void Open() { Open(new Vector2()); } public static void Open(Vector2 position) { var locationPriorityOrder = new PopupLocationHelper.PopupLocation; locationPriorityOrder = PopupLocationHelper.PopupLocation.Left; locationPriorityOrder = PopupLocationHelper.PopupLocation.Right; PopupWindowWithoutFocus.Show(new Rect(position.x, position.y, 6, 6), new ExampleUnfocusWindow(), locationPriorityOrder); } public override Vector2 GetWindowSize() { return new Vector2(200, 200); } public override void OnOpen() { base.OnOpen(); } public override void OnClose() { base.OnClose(); } public override void OnGUI(Rect rect) { GUI.Box(rect, "Hello world"); } }

    Настройка строки меню

    Некоторые из указанных выше способов поддерживают расширенное форматирование для строки.
    Если вы укажете в качестве пункта пустую строку, вместо неё будет задействован сепаратор (вертикальная черта).
    Если вы разделите части строки дробной чертой / , вы создадите подпункт с иерархией.

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

    % - ctrl
    # - shift
    & - alt
    _ - отсутствие спецсимвола

    Таким образом, если вы хотите назначить пункту меню клавишу G вы должны написать что-то подобное следующему:

    Так же пункты можно комбинировать, чтобы добиться сочетания аля "ctrl+shift".

    Естественно, что не всем клавиши можно представить буквой, потому для некоторых клавиш используются специальные ключевые слова LEFT, RIGHT, UP, DOWN, F1 .. F12, HOME, END, PGUP, PGDN.

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

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

    Заготовки using UnityEditor; using UnityEngine; public class Popup: EditorWindow { public static void Open(Vector2 size) { var window = CreateInstance(); var screenMousePos = GUIUtility.GUIToScreenPoint(Event.current.mousePosition); window.ShowAsDropDown(new Rect(screenMousePos.x, screenMousePos.y, 1, 1), size); } }

    Сейчас уже даже школьнику известно, что Unity - потрясающий игровой движок, способный удовлетворить любые прихоти как разработчика, так и геймера. Единственной Ахиллесовой пятой Unity был старый GUI (графический пользовательский интерфейс).

    Старая система GUI была очень мудреной системой, которая требовала чтобы весь код GUI был написан с помощью специальной функции OnGUI . Это был программеро-ориентированный, подход в отличие от визуального ориентированной природы самой Unity . Сам код работал медленно и неэффективно, а на то, чтобы сваять интерфейс, приходилось тратить много времени и сил. Конечно, были инструменты сторонних разработчиков, такие как NGUI, которые пытались решить эту проблему, но за то, чтобы начать пользоваться ими нужно было расстаться с n-й суммой денег.

    К счастью, разработчики в Unity Technologies прислушались к народным мольбам и, после двух лет разработки, они предоставили в Unity 4.6 совершенно новый GUI, который мы и рассмотрим в этом уроке. На протяжении трех занятий мы будем создавать новый графический интерфейс к игре, созданной в наших предыдущих сериях уроков Ваяем игру в стиле Jetpack Joyride в Unity 2D .

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

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


    Однако, просто добавление нескольких кнопок будет не достаточным чтобы освоить новую систему с графическим интерфейсом. Чтобы добавить изюминку к игре и привлечь ваших пользователей вы будете

    • Анимировать кнопки
    • Создать выезжающее окно настроек.
    • Использовать более новые элементы управления GUI, такие как Text, Slider, Panel, Mask и т.д.

    Создание сцены с меню в Unity

    Скачайте по указанной выше ссылке проект Rocket Mouse и разархивируйте его. Откройте проект в Unity с помощью меню File\Open Project… . Кроме того, откройте папку Assets\Scenes в папке проекта и дважды щелкните на сцену RocketMouse.unity чтобы открыть ее в Unity .

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

    Создадим новую пустую сцену. Для этого выберите в меню File\New Scene . Лучше сохранить сцену прямо сразу, поэтому откройте диалоговое окно Save Scene (Сохранить сцену), выбрав в меню File\Save Scene . Затем введите MenuScene в качестве имени сцены и сохранить его в папке Scenes рядом со сценой RocketMouse.


    Посмотрите на Браузер проекта (Project browser) чтобы убедиться, что в одной папке лежат две сцены.


    Примечание: Если у вас возникнут вопросы при открытии проекта, создании, сохранении сцены, или добавления ассетов, то перейдите к уроку Rocket Mouse . Там вы найдете подробные инструкции, часто в виде анимированных GIF изображений, которые не включены в эту статью.

    Импорт изображений и шрифтов

    Распакуйте архив с ассетами. В нем вы найдете две папки: Menu и Fonts . Выберите обе папки, а затем перетащите их в папку Assets в Браузере проекта. Подождите пока Unity обрабатывает файлы, после чего вы должны увидеть наши папки в Браузере проекта как показано на рисунке ниже:


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

    Добавление первого элемента пользовательского интерфейса

    Первый элемент, которым мы займемся - это фоновое изображение для сцены с меню. Выберите в верхнем меню Unity GameObejct/UI/Image чтобы добавить на сцену объект с именем Image . Вы должны увидеть его во вкладке Hierarchy (Иерархия), как дитя Canvas . Все элементы должны быть размещены на холсте. Если у вас его еще нет, Unity об этом позаботится. Чтобы гарантированно увидеть изображение на вкладке Scene (Сцена), выберите Image в Иерархии и задайте его Pos X и Pos Y равными нулю.

    Примечание: Я уже слышу вопросы типа: “Что такое Pos X?”, “Что такое Rect Transform и где старый добрый компонент Transform?” В этом мануале вы найдете всю интересующую вас информацию. Чтобы вам легче было во всем разобраться, мы будем рассматривать каждый аспект в контексте других объектов, участвующих в сцене. Таким образом, вы сможете увидеть, как они ведут себя при разных настройках. Такие свойства как Width , Height или Pos X не требует каких-либо объяснений.

    Вы установили правильные положение и размер нашего фона. Теперь посмотрите внимательно на вкладку Иерархия и вы увидите три новых объекта в сцене. Вот они:

    1. Image
    2. Canvas
    3. EventSystem

    Image - неинтерактивный элемент управления, который отображает текстуру спрайта и позволяет настроить множество опций. Например, вы можете закрасить изображение или поместить на него материалы. Вы даже можете контролировать полноту его отображения. Например, применить к нему анимацию появления по часовой стрелке. Canvas - корневой объект для всех элементов вашего пользовательского интерфейса. Как уже было сказано раньше, он создается автоматически, когда вы добавляете первый элемент интерфейса. Холст обладает множеством свойств, определяющих отображение интерфейса, и вы будете исследовать некоторые из них во время этого урока. EventSystem отвечает за обработку ввода, рейкастинг и события в сцене Unity. Это нужно для работы интерфейса, поэтому этот элемент добавляется автоматически.

    Настройки фонового изображения

    Вернемся к Image. Первое, что мы сделаем - переименуем Image в img_background . Теперь откроем Menu в поисковой строке проекта и найдем изображение menu_background . Перетащим его в поле Source Image компонента Image в инспекторе img_background .


    Теперь у вас есть фоновое изображение вместо белого изображения по умолчанию. Тем не менее, оно выглядит, мягко выражаясь, не айс, потому что изображение слишком мало, да и соотношение сторон является неправильным. Чтобы исправить, найти кнопку Set Native Size (Установить исходный размер) в Инспекторе и нажмите на нее, чтобы установить размер 1024 х 577.


    Тем не менее, есть еще несколько проблем:

    1. Фактический размер изображения 1136 x 640 , этого достаточно чтобы закрыть весь экран 4-дюймового iPhone, но не более.
    2. Уменьшив окно в режиме Scene, вы увидите, что холст (белый прямоугольник) закрывает только часть изображения. Если вы переключитесь в режим Game, вы увидите только часть фонового изображения, как будто камера находится слишком близко к объекту, чтобы отобразить его полностью.

    Примечание: оригинальная игра была разработана для айфонов с 3,5-х и 4-дюймовыми дисплеями. Вот почему графика игры поддерживает 1136 x 640 и 960 х 640 разрешения. Но вскоре вы увидите, как UI может адаптироваться к различным разрешениям экрана.

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

    Настройка импорта изображений

    Для вашего проекта вам понадобятся изображения немного большего размера, при этом нужно будет сделать так, чтобы Unity их не уменьшала. Откройте папку Menu папка в Браузере проетка и выберите изображение menu_background . В Инспекторе, выберите Default tab , установите Max Size (Максимальный размер) равным 2048 и нажмите кнопку Apply (Применить).

    Теперь выберите img_background в Иерархии, а затем нажмите кнопку Set Native Size снова. Ширина и высота должны измениться на правильные значения 1136 и 640, соответственно.

    Видите? Это было действительно легко исправить. Идем дальше!

    Использование Canvas Scaler

    Настало время настроить способ отображения. Прежде всего следует сказать, что то, как отображается игра на данном этапе - вовсе не баг. С точки зрения Unity, вид в режиме Game настроен на параметры 550×310, поэтому вы видите только часть изображения, которая помещается в окно игры.

    Если вы запустите игру на устройстве или просто растянете ваш вкладку Game до размеров фонового изображения, ты вы увидите его полностью.

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

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

    К счастью, специальный компонент приходит на помощь. Этот компонент называется Canvas Scaler . В предыдущих версиях Unity он назывался, Reference Resolution и его нужно было добавлять к холсту вручную. С появлением Unity 4.6 этот компонент теперь прикреплен к каждому Canvas. Выберите Canvas в Иерархии и тогда в Инспекторе вы должны увидеть компонент Canvas Scalar .


    Canvas Scalar имеет три режима работы:

    Constant Pixel Size : все элементы пользовательского интерфейса сохраняют размер пикселя независимо от размера экрана. Это дефолтный режим.

    Scale With Screen Size : Размеры и положение элементов пользовательского интерфейса определяются заданным разрешением. Если текущее разрешение выше, холст сохранит заданное разрешение, увеличив элементы в соответствии с новыми настройками.

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

    После добавления компонента, установите его Resolution (Разрешение) равным 1136 х 640. Кроме того, сдвиньте все Match Width или Match Height до упора вправо, или просто введите 1 в поле ввода.


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


    Измените разрешение вкладки Game чтобы увидеть, как ваша игра будет выглядеть в другом разрешении, например, на 3-5 дюймовом iPhone. Чудесно. Как видите, по-прежнему выглядит хорошо!


    Теперь переключитесь во вкладку Scene (Сцена) и вы увидите, что размер холста не меняется при изменении размера вкладки Сцена: боковые края экрана аккуратно обрезаются а центральная часть полностью видна. Это результат установки Настройте ширину и высоту в 1 (Настройте высоту)... Это отлично соответствует разрешению ваших целевых гаджетов.

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

    Добавление изображения с заголовком

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

    Откройте окно Scene и выберите GameObject\UI\Image в меню. Это позволит добавить еще одно белое изображение на сцену. Вот как это вышло у меня:


    Примечание: Если изображение окажется слишком далеко, просто установите значение 0 для свойств Pos X и Pos Y.

    Теперь попробуйте превратить этот белый прямоугольник в реальное изображение выполнив следующие действия:

    1. Выберите Image в Иерархии и переменуйте его в img_header .
    2. Откройте папку Menu в Браузере проекта и найдите картинку header_label .
    3. Перетащите изображения в поле Source Image инспектора.
    4. Нажмите на Set Native Size в Инспекторе.

    Как видите, это было достаточно просто. Теперь нужно работать с позиционированием с помощью компонента Rect Transform .

    Rect Transform, Anchors, Pivot и Вы)

    Если вы работали с Unity раньше, то вы, наверняка, знакомы с компонентом Transform . Если нет, то ничего страшного. Это просто инструмент, с помощью которого можно расположить, вращать и масштабировать объекты на сцене. Вот как это выглядит:

    Вы увидите Transform-компонент, когда выберите любой тип GameObject в Иерархии.

    Тем не менее, если вы выберите какой-либо элемент пользовательского интерфейса, например, img_header , вы увидите другой компонент с именем Rect Transform .

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

    Примечание: Вместо Pos X , Pos Y , Width и Height вы работаете с Left , Top , Right и Bottom .

    Вы задаетесь вопросом о настройке якорей, которые меняет внешний вид Rect Transform так кардинально? Вы найдете ответы, которые вы ищете в следующем разделе.

    Якоря

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

    Настраивая якоря, вы определяете несколько положений в родителе, как правило, по одному в каждом углу элемента интерфейса под названием Rect. При изменении размера родителя наш элемент будет стараться сохранить uniform distance to the anchor points, заставляя его перемещаться или изменять размер вслед за родителем.

    Чтобы посмотреть на разные Anchor Presets , просто выберите img_header в иерархии и кликните на прямоугольник прямо над полем Anchors в компоненте Rect Transform.

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

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

    Как вы видите, настройки якорей влияют на то, как элементы вашего интерфейса адаптируются к разному разрешению экрана.


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

    Манипулятор якорей

    На данном этапе наши якоря используют 4 треугольника. Вот как это выглядит при настройке top-center :


    Настраиваемые якоря

    Вы можете вручную передвинуть якоря в произвольное положение, т.к. Anchor Presets можно менять любым удобным вам образом.

    Примечание: Бывает так, что другой элемент интерфейса перекрывает иконку якоря, и вы не можете его выбрать. В таком случае просто вызовите иконку якоря, щелкнув на Anchor Preset (например, левая часть экрана). Иконка якоря появится в указанной части экрана, после чего вы сможете перемещать ее как угодно.


    Примечание: Как видите, изменение размера холста заставляет изображение передвигаться вправо. Но это перемещение незначительно по отношению к правому краю холста. Это объясняется тем, что якоря установлены на 25% ширины холста.

    Разделение якорей

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


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

    Rect Transform зависит от текущих настроек якоря

    В зависимости от настроек якоря, Rect Transform контролирует размер и положение элементов интерфейса разными способами.

    Ели вы поставите якорь на какую-то точку, без растягивания, вы увидите такие свойства, как Pos X , Pos Y , Width иHeight .

    Но если вы настроите якорь так, что это приведет к растягиванию элемента, вместо Pos X и Width у вас будут свойства Left иRight (при горизонтальном растяжении), а вместо Pos Y and Height - Top и Bottom (при горизонтальном растяжении).

    На этом скриншоте якорь img_header стоит на middle-stretch . Это значит, что изображение остается в центре холста вертикально и растягивается горизонтально.


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

    Точка опоры

    Осталось последнее не рассмотренное свойство компонента Rect Transform - Pivot (Точка опоры). Точка опоры является точкой, вокруг которой производятся все преобразования. Другими словами, определяя положение элемента, вы определяете его точку опоры. Если вы станете вращать элемент пользовательского интерфейса, то он будет вращаться вокруг этой точки.

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

    Примечание: Вы также можете установить точку опоры за пределами элемента интерфейса. В этом случае она будет за пределами значений (0,0) - (1,1). Это может вам пригодиться. Например, вам нужно, чтобы объект вращался вокруг определенной точки в сцене. Чтобы изменить точку опоры, вы должны убедиться, что кнопка Pivot/Center стоит на Pivot таким образом:

    Вы можете изменить Точку опоры в компоненте Rect Transform в Инспекторе или вы можете использовать Rect Tool .

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

    На первом изображении Точка опоры установлена в дефолтное значение (0.5, 0.5), которое является центром элемента пользовательского интерфейса. Установите позицию элемента в положение (0, 0), а якоря - top-left .


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

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


    Сложно объяснить, как точка опоры влияет на вращение и размер с помощью статичного изображения, поэтому вот несколько анимаций:


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


    Примечание: Удерживайте кнопку ALT при масштабировании вокруг точки опоры.

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

    Примечание: Еще одна важная вещь, которую вам следует уяснить - это то, что изменение размера элемента интерфейса, не влияет на его масштабирование . Но вы можете изменить его параметры - Width и Height или Top, Right, Left, Bottom.Эти свойства немного отличаются. Например, размер не может быть отрицательным, но масштаб может; с помощью отрицательных значений вы можете перевернуть элемент. В большинстве случаев вам достаточно только изменить размер ваших элементов пользовательского интерфейса.

    Размещение изображения с заголовом

    Уф! Теперь вы знаете все, что нужно, о Rect Transform, Anchors и Pivot. Поверьте, когда-нибудь вы скажете мне спасибо за то, что корпели над этим упражнением, ведь от того, насколько хорошо вы усвоили эти понятия, зависит интерфейс вашей игры. Теперь мы можем сконцентрироваться на создании сцены меню. Остальные разделы пролетят в мгновение ока. Все эти манипуляции окончательно измотали бедный маленький img_header . Пора поместить его на законное место и оставить в покое.


    Прежде чем продолжить, повторно включите img_background (если оно было отключено) чтобы увидеть границы холста. Затем выберите img_header в Иерархии и установить его свойства в Инспекторе следующим образом:

    1. Нажмите на Set Native Size чтобы сбросить размер, так как вы, наверняка, изменили его играя с Точкой опоры.
    2. Установите Якоря в top-center .
    3. Установите Pos X в 0 и Pos Y в -100 .

    Вы должны увидеть на Вашей вкладке Сцена что-то вроде этого:


    Отлично. Теперь оставьте заголовок в покое).

    Добавление кнопки Старт

    Теперь, когда ваше приложение имеет приятный фон с заголовком, пришло время добавить несколько кнопок. Выберите GameObject\UI\Button в меню. Это позволит добавить объект Button в сцену, при этом вы должны увидеть его в Иерархии. Если развернуть его в Иерархии, вы увидите, что кнопка содержит текстовую метку - об этом вы узнаете чуть позже.

    Взгляните на кнопку в инспекторе, и вы увидите знакомый компонент Image (Script). Помните, мы использовали его для добавления фона и метки хедера? Кроме того, существует компонент кнопки (Script). Другими словами, кнопка только изображение с прикрепленным скриптом, который содержит текст кнопки.

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

    Позиционирование кнопки

    Теперь займемся позиционированием и изменением размера кнопки. Выполните следующие действия:

    1. Выберите Button в Иерархии и переменуйте ее в btn_start .
    2. Установите Якоря Anchors в bottom-stretch , так как нам нужно чтобы кнопка растягивалась по горизонтали в случае изменения размера экрана.
    3. Установите значения Left и Right равными 400 .
    4. Установите Height равным 80 .
    5. Установите Pos Y равным 300 .

    Теперь выберите вложеннsq текстовый элемент и введите текст Start Game (Начать игру). Измените Font Size (размер шрифта) на 32 чтобы сделать текст кнопки больше.


    Это то, что вы должны увидеть в окне Scene (Сцена):


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

    9-фрагментное масштабирование

    Изображение для Button устанавливается так же, как изображение для Image. В конце концов, это практически один и тот же компонент. Но в отличие от изображений, которые редко масштабируются, кнопки часто бывают самых разных размеров. Конечно, можно создать фоновое изображение для каждого размера кнопки, но зачем же тратить драгоценное место? Вместо этого мы воспользуемся техникой, называемой 9-фрагментным масштабированием , которая позволяет обойтись одним маленьким изображением, которое масштабируется в соответствии с необходимым размером. Нет, это не магия. Вам не придется бросать свои изображения в волшебный фонтан прежде, чем вы сможете их использовать:] Этот метод делает это возможным, потому, что изображение не масштабируется равномерно. Есть девять зон, каждая из которых масштабируется по-разному.


    Это гарантирует, что изображение будет выглядеть хорошо в любом масштабе.

    Подготовка изображения для кнопок

    Прежде чем вы сможете использовать нарезанное изображение, необходимо установить эти 9 зон. Чтобы сделать это, откройте папку Menu Браузере проекта и выберите btn_9slice_normal image . В Инспектора Import Settings (Настройки импорта), устанавливается Format в Truecolor , а затем нажмите на кнопку Sprite Editor (Редактор спрайта), чтобы открыть вкладку Sprite Editor .

    Примечание: Установка формата на Truecolor не обязательна и не относится к масштабированию изображений. Но в моей практике были случаи нежелательного сжатия изображений, поэтому я пришел к выводу, что лучше этим не пренебрегать. Если вы используете собственные изображения и они выглядят нормально в сжатом формате, вам можно с этим не возиться.

    В Sprite Editor установите значение Border равнмы L:14 , R:14 , B:16 , T:16 , а затем нажмите на Apply !

    Повторите действие для изображений btn_9slice_highlighted и btn_9slice_pressed , которые вы будете использовать для разных состояний кнопок.

    Настройка изображений кнопок

    Подготовив все изображения, вы должны будете всего лишь перетащить их в соответствующие поля Инспектора. Выберите btn_start в иерархии и выполните следующие действия:

    1. Замените Image Type на Sliced в компоненте Image.
    2. Замените свойство Transition в компоненте Button на SpriteSwap .
    3. Перетащите btn_9slice_normal в Source Image в компоненте Image.
    4. Перетащите btn_9slice_highlighted в Highlighted Sprite в компоненте Button.
    5. Перетащите btn_9slice_pressed в Pressed Sprite в компоненте Button.

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

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

    Задание пользовательских шрифтов для кнопки

    Использовать пользовательские шрифты легко. Помните папку Fonts , которыу вы добавили к проекту? Теперь пришло время воспользоваться ей и использовать один из этих шрифтов. Выберите метку Text , встроенную в in the btn_start,в иерархии. Затем откройте папку Fonts в Браузере проектов и перетащите шрифт TitanOne-Regular в поле Font . Также установите Color в white (белый).

    Теперь запустите сцену и наслаждайтесь новой мега-кнопкой.

    Удаление прозрачности кнопки

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

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

    Нам потребуется всего несколько секунд чтобы отменить прозрачность. Выберите btn_start в Иерархии и дважды щелкните на свойство Color внутри компонента Image . В открывшемся диалоговом наборе выбора цвета установите A (альфа) равной 255 и закройте палитру цветов.


    Теперь он выглядит намного лучше!


    Добавление кнопки Settings (Настройка)

    Есть только несколько вещей, которые осталось сделать прежде чем перейти к следующей части и одна из них добавить кнопку Settings (Настройка). Вы, наверное, уже можете сделать это сами, зная размер и положение кнопки.

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

    Итак, вот свойства кнопки Settings:

    • Name: btn_settings
    • Rect Transform: Left и Right равны 450 , Height - 70 , а Pos Y - 180
    • Text: Settings

    Если у вас совсем ничего не получается, выполните следующие действия:


    Это то, что вы должны увидеть во вкладке Scene (Сцена) после добавления кнопки Settings (Настройки):


    Теперь нам осталось только нажать кнопку Start Game и запустить вторую сцену (это сама игра).

    Добавляем сцены в режим Build

    Прежде, чем вы сможете запустить свои сцены, вам нужно будет добавить их в список Scenes in Build в настройках проекта, чтобы они были включены в конечную версию. Для этого выберите File\Build Settings… чтобы открыть диалоговое окно Build Settings . Затем откройте папку Scenes в Браузере проекта и перетащите сперва MenuScene , а затем сцену RocketMouse в список Scenes in Build .

    Примечание: Важен порядок. Соблюдайте его!


    Потом закройте окно Build Settings.

    Создание UIManager

    Когда вы добавляете обработчик событий для кнопки, необходимо указать метод, который будет вызываться при нажатии на кнопку. Это означает, что вы должны прикрепить объект к скрипту так как вы не можете использовать статические методы. Создайте пустой игровой объект и прикрепите скрипт к нему. Этот скрипт будет содержать все методы вызываемыми UI элементами в MenuScene. Выберите GameObject\Create Empty в меню. Затем выберите GameObject в Иерархии и переименуйте в UIManager . После этого нажмите на Add Component в Инспекторе и выберите New Script . Назовите его UIManagerScript . Убедитесь, что установлен язык CSharp и нажмите на Create and Add (Создаать и добавить). Это то, что вы должны увидеть в Иерархии и Инспекторе:


    Сделайте двойной клик на UIManagerScript в инспекторе, чтобы открыть скрипт в MonoDevelop. После того, как сценарий загрузится, удалите методы Start и Update и добавьте метод StartGame следующим образом:

    Public void StartGame() { Application.LoadLevel("RocketMouse"); }

    Сохраните файл и убедитесь, что он не содержит ошибок, создав его в MonoDevelop. Для этого выберите Build\Build All в меню MonoDevelop.

    Вызов метода StartGame , когда игрок нажимает на кнопку

    Вернитесь к Unity и выполните следующие действия:

    1. Выберите btn_start в Иерархии и и прокрутите мышью по вкладке Инспекторе до списка On Click (Button) .
    2. Нажмите кнопку + чтобы добавить новый элемент.
    3. Затем перетащите UIManager из Иерархии на добавленный элемент в списке.
    4. Нажмите на выпадающий список выберите функцию. Прямо сейчас в нем установлено No Function .
    5. В открывшемся меню выберите UIManagerScript\StartGame () .

    Запустите сцену и нажмите на кнопку Start Game чтобы запустить сцену с игрой.

    Что дальше?

    Итак, Вы создали пользовательский интерфейс, добавили изображения кнопок и даже написал код, который запускает игру, когда вы нажимаете на кнопку! Так устроен интерфейс многих игр. Вы также узнали, что такое a lot Rect Transform, Anchors, Pivot и другие полезные штуки. Теперь, когда вы знаете, как они работают, вы сможете значительно ускорить работу над своим проектов, используя эти элементы. Вы можете загрузить готовый проект . В следующей части нашего урока вы узнаете как оживить элементы пользовательского интерфейса, создавать диалоги, использовать элементы управления такие как слайдер и переключатели, а в конце урока вы будете иметь сцену с рабочим меню! Отдохните и принимайтесь за



    Рекомендуем почитать

    Наверх