Unity3Dschool

обучение разработке игр

Управление в Android играх на Unity

Управление в Android играх на Unity

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

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

Но когда доходит до создания игры под мобильные устройства, эти все способы программирования не подходят. Input.GetAxis тут не работает. Начнем разбираться как это чинить.

Платформенно зависимая компиляция

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

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

Важными директивами есть следующие:

  • UNITY_STANDALONE - добавляется для  части скриптов, которые выполняются при запуске игры из системы Windows или Linux, или Mac OS X.
  • UNITY_WEBGL - работа с WebGL
  • UNITY_IOS - будет исполняться код для iOS платформы
  • UNITY_ANDROID - директива для платформы Android

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

Итак, как работать с этими директивами препроцессора и платформенно зависимой компиляцией? Синтаксис директив следующий.

#if - директива указывает условие при котором будет выполняться следующая часть кода. Если есть директива  #if , то должна быть и соответствующая закрывающая директива #endif, которая обозначает границу компилируемой части.

Также существует директива #elif, которую можно воспринимать как else if. Работает схожим образом. Также отдельно существует директива #else, но она обязательно должна идти последней и после нее обязательно указывать #endifВозможно это выглядит странно и страшно, но на самом деле это работает по принципу условий if - else в привычном программировании. Только тут присутствует не игровая логика, а скорее логика управления кодом. Итак, пример кода с директивами.

//Проверяем, что код запускается с самостоятельных ОС #if UNITY_STANDALONE         //код, который будет выполняться с компьютера. Тут работают Input.GetAxis =) #elif UNITY_IOS || UNITY_ANDROID        //блок который выполняется по условию “иначе”. Хорошей практикой будет учесть все возможные варианты. Тут учтены как Android так и iOS устройства. #endif //обязательно указывать по окончанию всех директив

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

Управление с touchscreen

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

Основы

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

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

Алгоритм состоит из следующий частей:

  1. Зарегистрировать хотя бы одно касание
  2. Сохранить интересующий нас элемент массива
  3. Получить фазу касания экрана и что-то сделать.

И не забываем про директивы и платформенно зависимую компиляцию. Это первое что мы сделаем в коде.

#if UNITY_IOS || UNITY_ANDROID #endif

Внутри этой директивы мы имеем право описать управление. Первое - удостовериться,что касание произошло. Input записывает количество касаний в Input.touchCount

   

Добавляем условие:

if (Input.touchCount > 0) {       //тут будет работать код }

Второе - получить первое в списке касание.Существует объект Touch, который содержит всю информацию об единичном касании. Чтобы получить нужную информацию используем метод Input.GetTouch(). Этот метод принимает как аргумент любое целое число - оно и будет показывать индекс искомого элемента в массиве. Про массивы и то как с ними работать можно найти в любом учебнике по с#Внутри предыдущего условия пишем:

Touch myTouch = Input.GetTouch(0);

Отлично, мы получили информацию о касании и сохранили её в переменную. Что мы можем извлечь из этого. 

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

 

Естественно имя переменной вы можете задавать сами.

  1. Фаза касания: их существует несколько. Найти их можно в свойстве myTouch.phase объекта Touch. Представляют собой перечисление TouchPhase. Варианты такие:
    1. Began - произошло начало касания
    2. Moved - пользователь водит пальцем по экрану
    3. Stationary - пользователь касается экрана, но не меняет положения касания
    4. Ended - касание прекратилось
    5. Canceled - система не может считать касание(например пользователь касается ладонью и невозможно определить количество контактов)
  2. Позиция на экране. Находится в свойстве myTouch.position. Также существует deltaPosition, которое показывает расстояние между позицией в текущем кадре и в предыдущем. Позиция выводится в координатах экрана, расчет которых проходит с левого нижнего угла.
  3. myTouch.tapCount - количество быстрых касаний. Можно использовать для программирования “дабл-клика”. Если касания происходят быстро несколькими пальцами, то они могут быть неправильно считаны, как касание одним пальцем.
  4. myTouch.fingerId - значение по которому можно отслеживать касание. Сохраняет свое значение в каждом кадре и поэтому является отличным источником для работы со сложными траекториями движения, анализе жестов. По сути, это уникальный Id для каждого конкретного жеста (не путать с порядковым номером касания). Отлично, с теорией разобрались. Рассмотрим два практических примера.

Пример 1. Получение координат точки на экране, куда пользователь указал в игре.

private void Update() { #if UNITY_IOS || UNITY_ANDROID if (Input.touchCount > 0) { Touch myTouch = Input.GetTouch(0); Vector2 positionOnScreen = myTouch.position; Debug.Log(positionOnScreen); } #endif }

Тут просто выводится в консоль значение позиции на экране. Позиция на экране задается через Vector2Однако в консоль будет выводится несколько сообщений, так как одно касание может длиться несколько кадров (мы пишем это в Update). Если нужна только точка в момент касания, то обращаемся к фазам касания. Код будет выглядеть так:

private void Update() { #if UNITY_IOS || UNITY_ANDROID if (Input.touchCount > 0) { Touch myTouch = Input.GetTouch(0); if (myTouch.phase == TouchPhase.Began) { Vector2 positionOnScreen = myTouch.position; Debug.Log(positionOnScreen); } } #endif }

Появилось только условие (выделено желтым). 

Пример 2. Касание по объекту на экране. Тут будет использован Raycast.

private void Update() { #if UNITY_IOS || UNITY_ANDROID if (Input.touchCount > 0) { Touch myTouch = Input.GetTouch(0); if (myTouch.phase == TouchPhase.Began) { //луч получаем из метода ScreenPointToRay, //луч "выпускается" от места касания на экране в ту сторону куда                       смотрит камера Ray ray = Camera.main.ScreenPointToRay(myTouch.position); //эта переменная нужна для получения информации об объекте RaycastHit hit; //получение информации и вывод названия объекта в консоль if (Physics.Raycast(ray, out hit)) Debug.Log(hit.transform.name); } } #endif }

Направление движения

С основами разобрались, теперь более сложные задачи, которые задаются разработчикам. Одна из таких задач - определение направления, куда пользователь провёл пальцем. Направление будем определять с помощью векторов, а именно - нормализации вектора. В разделе Основы упоминалось о свойстве deltaPosition. Его и будем использовать. Для примера представлен скрипт, который заставляет объект двигаться в заданном направлении бесконечно (или до изменения инструкций)

public class Mobileinput : MonoBehaviour { //создаем переменную для направления движения Vector2 moveDirection = Vector2.zero; private void Update() { #if UNITY_IOS || UNITY_ANDROID if (Input.touchCount > 0) { Touch myTouch = Input.GetTouch(0); if (myTouch.phase == TouchPhase.Moved) { //получаем изменение позиции с предыдущего кадра Vector2 positionChange = myTouch.deltaPosition; //приведение к координатам экрана positionChange.y = -positionChange.y; //получаем направление движения moveDirection = positionChange.normalized; } } #endif                       //вынесено за условие - так объект будет двигаться постоянно //умножаем на -1, чтобы не было инвертированного направления движения transform.position += (Vector3)moveDirection * -10f * Time.deltaTime; } }

Мультитач

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

private void Update() { #if UNITY_IOS || UNITY_ANDROID if (Input.touchCount > 0) { //Сохраняем сразу весь массив касаний Touch[] myTouches = Input.touches; //проходим в цикле по массиву заданное количество раз(количество касаний) for (int i = 0; i < Input.touchCount; i++) { //Делаем что-то с каждым Touch объектом Debug.Log(i + " " + myTouches[i].position); } } #endif } }

Input.touchCount - это количество элементов в массиве. Получение объекта по индексу  - myTouches[i]. И с этой конструкцией можно работать также как в предыдущем разделе мы работали с элементами Touch.

Акселерометр

Мобильные устройства позволяют считывать свое положение в трехмерном пространстве. В Unity для этого существуют методы. Прежде всего нужно разобраться с осями. Если вы возьмете телефон в руки и разместите его вертикально (портретная ориентация), то ось Y будет указывать вверх-вниз, ось X в стороны, а Z будет показывать глубину. Работа с акселерометром на самом деле более чем простая. За это отвечает Input.acceleration, который работает почти также как Input.GetAxis.

Рассмотрим пример. Заставим объект будет двигаться туда, куда наклонено устройство.

private void Update() { #if UNITY_IOS || UNITY_ANDROID //Получаем данные с устройства и сохраняем в Vector3 с учётом осей Vector3 dir = new Vector3(-Input.acceleration.y, 0, Input.acceleration.x); //получаем направление вектора dir.Normalize(); //Двигаем объект в ту же сторону transform.Translate(dir * 10f * Time.deltaTime); #endif }

Бонус

Небольшой совет о том, как проверять работу приложений. Естественно проверить корректность работы своей системы управления можно только на устройстве. И что же, всё время билдить миллионы версий игры и проверять работу? По счастью, нет, не нужно. Команда Unity позаботилась о своих пользователях выпустив приложение Unity Remote.

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

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