6. Элементы управления

6. Элементы управления

6.1.     Глобальный обработчик событий ввода

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

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

Обработка класса Input обычно выполняется в цикличной функции Update объекта.

6.1.1.     Способ №1 обработки нажатий клавиатуры

Выполнить код при нажатии указанной клавиши, можно через метод Input.GetKey().

 

Input.GetKey(KeyCode.W) // Нажата клавиша «W»

Input.GetKey(KeyCode.Space) // Нажат пробел

6.1.2.     Способ №2 обработки нажатий клавиатуры

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

 

public class ComponentName : MonoBehaviour

{

    public KeyCode moveLeft;

    public KeyCode moveRight;

 

    void Update() {}

}

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


Теперь можно выполнять код при нажатии на указанную клавишу через метод Input.GetKey().

 

using UnityEngine;

 public class ComponentName : MonoBehaviour

{

    public KeyCode moveRight;

    public KeyCode moveLeft;

 

    void Update()

    {

        if (Input.GetKey(moveRight))

        {

            transform.position = transform.position + new Vector3(0.20f, 0, 0);

        }

        else if (Input.GetKey(moveLeft))

        {

            transform.position = transform.position + new Vector3(-0.20f, 0, 0);

        }

    }

}

 

6.1.3.     Способ №3 обработки нажатий клавиатуры

Управление для клавиш клавиатуры и геймпада также можно настроить в Edit → Project Settings → Input Manager.

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

В параметре Positive Button указывается, через какую клавишу будет происходить событие. В параметр Alt Positive Button указывается альтеративная клавиша (например для клавиши перемещения можно использовать клавишу «w» и стрелку вверх).

Пример использования клавиши через код:

 

if (Input.GetButtonDown("Fire1"))

{

    // код при нажатии на клавишу с именем «Fire1»

}

 

6.1.4.     Обработка перемещения указателя мыши/геймпада/клавиш

Для чтения информации с оси указателя используйте Input.GetAxis с одним из следующих стандартных обозначений осей: "Horizontal" and "Vertical" для джойстика, A, W, S, D и кнопок стрелок. "Mouse X" and "Mouse Y" для отслеживания перемещения указателя мыши. Новые оси ввода могут быть добавлены в Менеджере Ввода. Используйте Input.GetAxis, если для отслеживания движения.

Например, такие параметры мыши, как:

Input.GetAxis("Mouse ScrollWheel") // вращение колеса мыши

Input.GetAxis("Mouse X")                   // ось X мыши

Input.GetAxis("Mouse Y")                   // ось Y мыши

 

6.1.5.     Обработки нажатий на сенсорный экран

Если в случае с кликами мыши в классе Input есть методы GetMouseButton (...Up...Down), то для тачей соответствующие методы отсутствуют. Здесь все даже проще. Разработчики предоставляют свойство touchCount (только для чтения), в котором хранится количество тачей в текущем кадре. То есть, чтобы считать глобальный тач, нам необходима всего одна строчка в методе Update:

       void Update()

       {

            if (Input.touchCount > 0) Debug.Log("Был тач/нажатие");

 

            int fingerCount = 0;

            foreach (Touch touch in Input.touches)

            {

                  if (touch.phase != TouchPhase.Ended && touch.phase != TouchPhase.Canceled)

                                   fingerCount++;

            }

            if (fingerCount > 0)

                  print("Пользователь нажал " + fingerCount + " пальцами");

       }

 

Как только пользователь задумает ткнуть пальцем в экран (чего мы, по сути, и ждем), выражение Input.touchCount > 0 в блоке условия вернет true и в консоль выведется сообщение.

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

Input.touches - Возвращает список объектов, отражающих состояние всех касаний за последний кадр. (Read Only) (Выделяет память под временные переменные, т.е. в куче создаются лишние объекты). Состояние каждого касания экрана описывается, соответствующим ему, объектом списка.

6.2.     Локальные события нажатия через Collider

Локальные события нажатия привязываются к конкретному объекту на сцене или элементу интерфейса. Обработка событий нажатий на объекте производится через обработки триггеров компонента Collider (коллайдер). При добавлении на объект компонента Collider в коде станут доступны функции обработчики событий.

OnMouseEnter() - функция вызывается при входе курсора мыши на игровой объект, или на экземпляр объекта GUIElement (игровой интерфейс).

OnMouseExit()-функция вызывается при уходе курсора мыши с игрового объекта или экземляра объекта GUIElement.

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

OnMouseDown() - функция вызывается при нажатии кнопки мыши на игровом объекте или элементе игрового интерфейса.

Здесь есть три важных момента:

·        Метод вызываются непосредственно в момент нажатия кнопки, а не отпускания или полного клика.

·        Метод реагирует только на левую кнопку мыши.

·        Срабатывает только при клике непосредственно на объект.

OnMouseUp() - функция вызывается при отпускании кнопки мыши, после нажатия ее на игровом объекте или элементе игрового интерфейса.

OnMouseUpAsButton() - функция вызывается, если игрок уведет курсор мыши с объекта или элемента игрового интерфейса, после того, как зажал кнопку мыши.

OnMouseDrag() - функция вызывается при нахождении курсора мыши с зажатой кнопкой на игровом объекте или элементе игрового интерфейса.

Пример обработчика нажатия мыши.

using UnityEngine;

using UnityEngine.EventSystems;

 

public class HandleScript : MonoBehaviour

{

     void OnMouseDown()

     {

         transform.localScale *= 1.1f;

     }

}

 

Важно: все события, связанные с кликами, движок Unity считывает, неявно используя Raycast’ы (лучи). Именно поэтому мы добавили на наш спрайт компонент 2D Box Collider. Если Вы еще не знаете, в чем суть работы Raycast’ов, обязательно почитайте в документации Unity.

Важно: получается, указанные методы срабатывают при тапе/клике именно по коллайдеру объекта, а не по мэшу(Mesh)/спрайту.

6.3.     Локальные события нажатия через EventSystems

Следующий способ обработать локальный тач тоже работает для обоих типов объектов, но немного отличается конфигурацией компонентов. Здесь мы с Вами будем уже работать с таким пространством имен, как UnityEngine.EventSystems. Если оно у Вас еще не подключено, самое время это сделать. Там, как я уже говорил, находятся необходимые нам интерфейсы и классы. Теперь обязательно добавьте на сцену объект EventSystem. Он находится во вкладке UI контекстного меню создания объекта.

Итак, чтобы считать тап по 2D объекту (не элементу UI), необходимо обязательно прикрепить к камере компонент Physics 2D Raycaster. Для данного компонента обязательным является присутствие компонента Camera. Поэтому совсем неудивительно, что мы цепляем его именно на объект Main Camera.

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

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

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

using UnityEngine;

using UnityEngine.EventSystems;

 

public class HandleScript : MonoBehaviour, IPointerDownHandler

{

     public void OnPointerDown(PointerEventData eventData)

     {

          Debug.Log(eventData.position);

     }

}

 

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

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

IPointerUpHandlerIPointerClickHandlerIPointerEnterHandler и IPointerExitHandler.

Как вы уже, наверное, заметили, на 3D объекты данный способ не распространяется. Для обработки событий на 3D объектах на объект-камеру необходимо также прикрепить компонент Physics Raycaser, остальное остается неизменным.

Чтобы обработать тачи по элементам UI, Вам необходим будет компонент Graphic Raycaster. Но для него обязательным является компонент Canvas. Это, думаю, тоже вполне логично. Если прикрепить его на объект Canvas, то методы рассмотренных нами интерфейсов позволят также обработать тачи по кнопкам, панелям, тогглам и т.д.

Итог по разделу о глобальных и локальных тачах.
  • Все тачи и клики обрабатываются неявным пусканием лучей из экрана в сцену. То есть определяется касание к коллайдерам объектов.
  • При использовании интерфейсов из пространства имен UnityEngine.EventSystems обязательно надо добавить объект EventSystem.
  • Physics 2D Raycaster – компонент для обработки тачей/кликов по 2D объектам.
  • Physics Raycaster – компонент для обработки тачей/кликов по 3D объектам.
  • Graphic Raycaster – компонент для обработки тачей/кликов по элементам UI. В отличие от предыдущих присутствует на объекте Canvas по умолчанию.

6.4. Обработка Swipe жестов

Как же выловить эти Swipe жесты и как определить их направление? На самом деле, это совсем несложно. Есть несколько интерфейсов, которые позволят нам это сделать:

   IDragHandlerIBeginDragHandlerIEndDragHandler.

Причем, они отлично подходят как для работы с Drag, так и Swipe жестами. Давайте почистим нашу сцену и приведем ее примерно вот к такому виду:

У нас на объекте Canvas есть красная панель размером на весь экран и внутри нее имеется еще одна небольшая панель. Мы с Вами будем считывать Swipe’ы по красной панели и в зависимости от их направления двигать зеленую. Аналогичные действия Вы потом сможете проделать не только с элементами UI, как в этом примере, но и с 2D и 3D объектами. Сейчас же будем использовать панели, так как это получится более наглядно.

Скрипт HandlerScript прикрепляем к внешней (красной) панели.

using UnityEngine;

using UnityEngine.EventSystems;

public class HandleScript : MonoBehaviour, IDragHandler, IBeginDragHandler

{

     public void OnBeginDrag(PointerEventData eventData)

     {

     }

    

     public void OnDrag(PointerEventData eventData)

  { 

     }

}

Вот как изначально должен выглядеть наш скрипт. Мы должны наследоваться от интерфейсов IDragHandler и IBeginDragHandler. Этого будет достаточно, чтобы считать Swipe.

Важно: необходимо обязательно реализовать интерфейс IDragHandler (пусть даже методом с пустым телом), чтобы срабатывали методы из интерфейсов IBeginDragHandler и IEndDragHandler.

Чтобы определить направление Swipe’а, мы будет использовать свойство delta в параметре eventData метода OnBeginDrag. В это свойство записывается разница позиций курсора между текущим и предыдущим кадрами. В момент начала Swipe’а запишем, какое значение этой дельты, и из этого уже определим, какое направление жеста.

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

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

using UnityEngine;

using UnityEngine.EventSystems;

 

public class HandleScript : MonoBehaviour, IDragHandler, IBeginDragHandler

{

     Transform green;   // здесь будет ссылка на компонент Transform зеленой панели.

    

     void Start()

     {

          green = transform.GetChild(0); // получаем ссылку на Transform зеленой панели.

     }

 

     public void OnBeginDrag(PointerEventData eventData)

     {

          if (Mathf.Abs(eventData.delta.x) > Mathf.Abs(eventData.delta.y))

          {

              if (eventData.delta.x > 0) Debug.Log("Right");

                   else Debug.Log("Left");

                    green.position += new Vector3(eventData.delta.x, 0, 0);

          }

          else

          {

                   if (eventData.delta.y > 0) Debug.Log("Up");

              else Debug.Log("Down");

              green.position += new Vector3(0, eventData.delta.y, 0);

          }

     }

 

     public void OnDrag(PointerEventData eventData)

     {

     }

}

 

Сначала проверили, дельта по какой оси больше – X или Y? Если по оси X, значит движение будет по горизонтали, если же по Y – значит, по вертикали. А там еще раз проверили направление.

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

6.5. Обработка Zoom’а

Вернем сцену к первоначальному состоянию. Чтобы считать жест zoom’а, нам необходимо обработать глобальные тачи, поэтому наш скрипт HandleScript можно прикрепить даже на камеру.

Код наш будет выглядеть вот так:

using UnityEngine;

 

public class HandleScript : MonoBehaviour

{

     public float sensitivity;

     Vector2 f0start;

     Vector2 f1start;

     void Update()

     {

          if (Input.touchCount < 2)

          {

              f0start = Vector2.zero;

              f1start = Vector2.zero;

          }

         if (Input.touchCount == 2) Zoom();

     }

 

     void Zoom()

     {

         if (f0start == Vector2.zero && f1start == Vector2.zero)

          {

                  f0start = Input.GetTouch(0).position;

                  f1start = Input.GetTouch(1).position;

          }

          Vector2 f0position = Input.GetTouch(0).position;

          Vector2 f1position = Input.GetTouch(1).position;

          float dir = Mathf.Sign(Vector2.Distance(f1start, f0start) - Vector2.Distance(f0position, f1position));

        

         transform.position = Vector3.MoveTowards(transform.position, transform.position + transform.forward, dir * sensitivity * Time.deltaTime * Vector3.Distance(f0position, f1position));

     }

}

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

Условная конструкция в методе Zoom для того, чтобы позиции тачей записались в свойства только один раз в момент касания к экрану.

Чувствительность zoom’а вы можете регулировать значением поля sensitivity прямо в окне Inspector. Попробуйте усовершенствовать этот скрипт сами. Допустим, сделать, чтобы сравнивались позиции не текущих и начальных тачей, а позиции тачей в этом и предыдущем кадре.

6.6. Пример вращения камеры вокруг объекта

Для создания функционала вращения камеры вокруг заданного объекта с помощью указателя мыши необходимо создать скрипт (например, OrbitCamera) и поместить его на объект Камеру.

Пример кода:

public class OrbitCamera: MonoBehaviour

{

    public Transform target; //Объект, вокруг которого вращаем

    public float distance = 5.0f; // Дистанция вращения

    public float xSpeed = 125.0f; // Чувствительность по оси X

    public float ySpeed = 50.0f; // Чувствительность по оси Y

    public float zoom = 0.25f; // чувствительность при увеличении, колесиком мышки

    public float zoomMax = 10; // макс. увеличение

    public float zoomMin = 3; // мин. увеличение

 

    private float x = 0.0f; // угол по y

    private float y = 0.0f; // угол по x

 

    void Start()

    {

        //Инициализация начальных углов

        var angles = transform.eulerAngles;

        x = angles.y;

        y = angles.x;

    }

 

    void LateUpdate()

    {

        //Выполняем каждый кадр

        if (target)

        {

            if (Input.GetAxis("Mouse ScrollWheel") > 0) distance += zoom;

            else if (Input.GetAxis("Mouse ScrollWheel") < 0) distance -= zoom;

            distance = Mathf.Clamp(distance, -Mathf.Abs(zoomMax), -Mathf.Abs(zoomMin));

 

            if (Input.GetKey(KeyCode.LeftShift))

            {

                //Преобразование перемещения мыши в угол поворота

                x += Input.GetAxis("Mouse X") * xSpeed * 0.02f;

                y -= Input.GetAxis("Mouse Y") * ySpeed * 0.02f;

 

            }

 

            //Вращение камеры

            var rotation = Quaternion.Euler(y, x, 0);

            transform.rotation = rotation;

 

            //Перемещение приближение/отдаление камеры

            var position = rotation * new Vector3(0.0f, 0.0f, distance) + target.position;

            transform.position = position;

        }

    }

}

 

Комментарии

Популярные сообщения из этого блога

Лабораторные работы

О курсе...