Привет, друзья! Сегодня мы поговорим об очень важной, и часто недооцениваемой части компьютерных игр - графическом пользовательском интерфейсе. Мы не будем сейчас наводить красоту, а лишь разберёмся, как можно получить обратную связь от пользователя и наоборот, как пользователю что-то сообщить. Например - имя другого пользователя. Но... Обо всём по порядку.
Внимание!!! Длиннопост!
Для начала сделаю небольшое отступление. Я снова отстаю от графика, который вам пообещал. А всё потому, что написание поста занимает очень продолжительное время =) Для примера, на написание предыдущего я потратил 4 часа. И речь сейчас идёт не о работе в юнити, а исключительно о подготовке скринов и гифок, о написании текста и т.п. И дело в том, что всё, что мы будем сегодня рассматривать я сделал уже через день после написания предыдущего поста. ПОЭТОМУ! Я буду думать над упрощением подачи информации. Скорее всего буду дробить всё на маленькие темы, чтобы успевать давать эту информацию.
В общем, не обессудьте. Я готов делиться информацией. Но слишком мало свободного времени на это. Вероятно, я буду кодить и публиковать всё в репозитории на Bitbucket. А уже когда будет время, самое интересное из этого буду описывать здесь, на пикабу.
И так, начнём...
(Не ищите сакрального смысла в этой картинке, это лишь пример из интернетов.)
Наша цель сегодня - авторизовать пользователя, просто попросив его представиться и вывести над ним имя, которое будет видно другим игрокам. В обоих случаях нам будет необходимо взаимодействовать с такой чудесной вещью, как Unity UI, который за последнее время обзавёлся множеством фич и вполне себе удобен для использования прямо из коробки.
По способу размещения, интерфейс можно разделить на два вида: экранный и глобальный. И для обеих задач нам потребуются разные типы интерфейсов. Давайте начнём с самого известного - экранного. И сделаем окно, которое будет просить ввести имя пользователя. Я это реализовал вот в таком виде:
Но у вас это может выглядеть как угодно. Данное окошко состоит аж из семи элементов. Я не буду рассказывать обо всех, но акцентирую ваше внимание на необходимых.
Итак. Чтобы создать подложку для нашего окна - щёлкаем правой кнопкой мыши по окну иерархии и создаём UI -> Image. Давайте сразу назовём этот объект "StartWindow", чтобы потом нам было проще ориентироваться в сцене. В сцене вы, возможно, не заметите изменений. Поэтому давайте перейдём в окно Game, в котором у нас появился белый квадрат. Он и будет нашей подложкой. Сразу скажу, что верхнюю и нижнюю полосы я делал точно так же. Давайте кликнем на созданный нами объект в окне иерархии и посмотрим, что там у нас есть на нем в инспекторе и...
О боже! Где наш любимый трансформ?! А хотя нет. Вот он. Только называется он теперь Rect Transform и имеет уже несколько другой функционал. Давайте коротко его рассмотрим.
Первое, что бросается в глаза - непонятный квадрат слева. Спешу вас обрадовать. Это очень важный квадрат. Он позволяет задать выравнивание объекта, относительно экрана. Думаю вы уже догадались, что в отличие от нашего игрового пространства, которое везде будет выглядеть одинаково, экраны пользователей имеют ОЧЕНЬ разнящиеся параметры. Поэтому мы должны дать знать нашему элементу, как себя вести, если экран игрока будет отличаться от окна, в котором мы настраиваем положение элементов UI. В нашем случае всё просто - наше окно должно быть всегда центрировано. Поэтому тыкаем в середину.
Далее - справа от квадрата, который нам уже знаком, красуются две строки, которые тоже нам будут нужны. Первая - позволяет задать смещение элемента относительно той позиции, которую вы выбрали для него "ориентиром". Вторая - отвечает за размер окна. Поиграйтесь с этими значениями. Сделайте красиво и пойдём дальше. Начнём в нашем окне писать.
Для того, чтобы разместить надпись в нашем свеженьком окошке - жмём правую клавишу мыши и создаём UI -> Text. Не торопитесь настраивать его положение. Чуть ниже, в компоненте Text, вы сможете указать сам текст, подгрузить кастомные шрифты и даже помучать их параметры, включая цвет и размер (привет лиге веб дизайнеров, вы не одни играетесь со шрифтами). Настраиваем всё на свой вкус и только теперь задаём положение.
Чтобы избежать удивлённых возгласов начинающих девелоперов, которые решили установить очень уж большой шрифт или уместить всю свою биографию в этом элементе... При превышении размера, заданного в Rect Transform, текст по умолчанию обрезается. Но не пугайтесь, это можно исправить.
Обратите внимание на на пункты Horizontal Overflow и Vertical Overflow. У обоих есть выпадающие списки и если у обоих выбрать Overflow - ваша проблема решится. Теперь, с этими знаниями, сделайте так же все надписи, которые посчитаете нужными.
Поехали дальше. Давайте сделаем что-то более интерактивное, чем простой текст и создадим элемент UI -> InputField. Как можно понять из названия - это поле для ввода (ваш КЭП). Это более сложный элемент уже за счёт того, что при его создании он притянул к себе дочерними ещё аж два объекта. Все они позволяют настроить это поле так, как вы посчитаете нужным. Но я хочу обратить ваше внимание на поле OnEndEdit, которое находится в инспекторе у объекта InputField. Именно оно позволяет создать весь интерактив, но мы вернёмся к нему чуть позже. А сейчас - время кодить!
Прошло уже много времени и я не совсем помню, на чём мы остановились. Поэтому буду повествовать в режиме "что вижу, о том и пою". Для начала давайте создадим скрипт GameManager. Угадайте в какой папке =) Класс GameManager, в последующем, будет управлять игровым процессом. И не пугайтесь тому, что я пишу в коде сейчас (я в тот раз получил претензию на глупый контроллер). Мы начинаем с простого и будем двигаться к сложному. Поэтому ещё сотни раз всё будем усложнять и, как следствие, рефакторить.
И так, вот содержание класса:
Я не буду описывать переменные, а что делают методы - скажу. Самое первое, что у нас выполняется при старте - метод старт (хейтеры и умники, спокойнее, я имею в виду конкретный класс). В нём мы получаем компонент, управляющий нашим игроком и отключаем его. Это сделано для того, чтобы до входа в игру мы не могли крутить игроком и тем более бегать. Далее идёт публичный класс JoinGame, который принимает один параметр - строку name. Обращаю ваше внимание на то, что метод должен быть именно публичным, ибо мы будем обращаться к нему извне. Данный класс сразу же хватает переданное имя пользователя и записывает его в переменную playerName. Откуда метод её получает - пусть пока останется загадкой. Далее мы возвращаем контроль над персонажем в руки нашему пользователю и выключаем окошко авторизации. Всё, мы вошли в игру.
Теперь к вопросу об имени пользователя. Давайте вернёмся в Unity и снова тыкнем на InputField, который мы создали. Там, в инспекторе, у него есть поле On End Edit, которое я уже упоминал. Нажимаем плюсик под ним и в левое поле перетаскиваем объект GameManager из иерархии. А в выпадающем меню, из списка методов выбираем наш JoinGame.
Что же мы сейчас сделали? Всё просто. Когда пользователь завершит ввод и нажмёт Enter, наше поле для ввода самостоятельно оповестит GameManager о том, что ник введён. Сделает он это, обратившись к методу JoinGame и передав ему строку, введённую пользователем.
Чудесно. С этим разобрались. Мы научились выводить экранный интерфейс и даже передавать с его помощью данные в наш код. Но вот мы представились, а как узнать, как зовут остальных игроков? Здесь тоже всё достаточно просто. Давайте знакомиться с интерфейсом, размещаемым в мировом пространстве!
Давайте создадим объект в сцене. Простой шарик. И назовём его Opponent. Этот шарик будет символизировать какого-то другого игрока, играющего с нами по сети. Теперь создадим ещё один объект. UI-> Canvas. Этот объект является своего рода полотном, на котором отрисовываются все элементы UI. И вы можете увидеть, что один такой объект у нас уже есть. Его нам добавила Unity, когда мы создавали фон для нашего окна. Но этот нам так же необходим. Потому, что он будет отрисовывать графику не в окне, а в игровом пространстве.
Для того, чтобы иметь возможность перемещать этот объект по сцене, нам необходимо найти компонент Canvas в инспекторе и в RenderMode изменить ScreenSpace на WorldSpace. Теперь давайте сделаем ему дочерним объект UI -> Text, который будет выводить имя игрока. Теперь вам необходимо установить эти объекты в удобное для вас положение.
У меня это выглядит вот так. И, как только мы всё разместили, давайте сделаем из нашего Canvas-а префаб, перетащив его левой кнопкой мыши в папку Resources. Удалим его со сцены и потом создадим ещё один C# скрипт. Назовём его NetworkPlayerManager. Данный класс будет отвечать за поведение всех игроков, которыми не управляет наш пользователь. То есть за всех оппонентов, играющих с нами по сети. И что же делает наш код...
Для начала (21 строка) мы создаём на сцене копию префаба с Canvas-ом, который мы только что создали. И следующей строкой помещаем его дочерним к нашему интернет-игроку. Далее (24 строка) мы получаем ссылку на компонент текст, который будет выводить ник и присваиваем её переменной labelName. И далее просто ищем в сцене камеру и берём ссылку на её трансформ.
В апдейте мы задаём позицию нашего лейбла и вращаем его в сторону ПРОТИВОПОЛОЖНУЮ игроку. Почему так? Потому, что ось Z у графических элементов направлена от взгляда пользователя в глубину. И если повернуть эту ось на игрока, мы увидим обратную сторону нашего текста. Чтобы этого избежать, давайте сразу вращать правильно.
И далее. В методе FixedUpdate мы обновляем текст надписи. Зачем мы её обновляем? Ну... Пользователь может захотеть сменить ник (когда мы дадим ему такую возможность) и тогда мы не будем наблюдать старый ник до конца сессии. А вот так часто ник обновлять действительно не имеет смысла. И потом мы приурочим это дело к какому нибудь более редкому игровому событию.
Ну вот и всё! Если вы дочитали до этого места, значит вы научились работать с новым графическим интерфейсом в Unity. А так же узнали, как с помощью интерактивных элементов UI передавать параметры в игровые скрипты. В будущем это ещё не раз пригодится.
Спасибо, что дочитали этот пост. И отдельное спасибо, что остаётесь на связи. У меня уже более 1000 подписчиков и я знаю, что вы ждёте новых постов. Но... Работа не даёт публиковаться часто =) Для тех, кто хочет видеть обновления чаще - есть группа в контакте. Созданная специально для того, чтобы держать в курсе особо интересующихся. Я стараюсь делиться там всякими плюшками. А теперь, поскольку код будет опережать посты, там будет информации немного больше.
Подписывайтесь, чтобы всегда быть в курсе событий и не забывайте ставить плюсы, это лучшая благодарность для автора. Увидимся в следующих постах!