Телепортация игрока в Unity с OpenXR

от автора

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

Для работы над текущей задачей мы возьмем проект из предыдущего урока за основу, удалив из него скрипт HandCapsule. Это был демонстрационный скрипт, показывающий нам как работать с API OpenXR из Unity, и более он нам не потребуется — мы будем писать уже «по-настоящему».

Перед тем как начать, давайте отделим неподконтрольную нам часть от подконтрольной. Создадим дочерний для XR Rig объект Player Avatar, а в нём создадим объекты Left Hand и Right Hand. Рукам-капсулам мы зададим scale (0.05, 0.05, 0.05). В руках мы создадим объект Pointer и разместим его на «макушке» наших капсул и повернем его таким образом, чтобы forward Pointer’а смотрел наверх. Значения трансформа Pointer’а: поворот по x -90, координаты (0, 1, 0).

Hand

Создадим класс Hand — основной скрипт для всех работы с инпутом контроллеров и взаимодействия с виртуальным миром. В начале класса мы объявим поля TargetTransform, чтобы понимать, какое местоположение сейчас у «настоящего» контроллера, InputDeviceManager из прошлой статьи, чтобы взаимодействовать с инпутом, и флаг IsLeftHand, чтобы понимать, какой InputDevice нам брать для обработки инпутов. Также, заведем свойство InputDevice, возвращающее конкретный для данной руки InputDevice из InputDeviceManager.

  public Transform TargetTransform;   public InputDeviceManager InputDeviceManager;   public bool IsLeftHand;    public InputDevice InputDevice => IsLeftHand ? InputDeviceManager.LeftController : InputDeviceManager.RightController;

Данный скрипт навесим на оба объекта рук: Left Hand и Right Hand, и проставим им все необходимые ссылки, а флаг IsLeftHand выставим в true для Left Hand.

Компонент Hand для левой руки
Компонент Hand для левой руки

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

private void LateUpdate() {     transform.position = TargetTransform.position;     transform.rotation = TargetTransform.rotation; }

Hand будет выполнять на данном этапе роль обработчика событий с инпута соответствующего ему контроллера, потому давайте заведем четыре события, необходимые нам для реализации телепортации игрока и его поворота: TeleportPressed, TeleportReleased, TurnLeft и TurnRight. Делегат для них будем использовать void HandInteraction(Hand hand) — его я положил в отдельный файл, но в скрипте приведу рядом с событями.

public delegate void HandInteraction(Hand hand);  public event HandInteraction TeleportPressed; public event HandInteraction TeleportReleased;  public event HandInteraction TurnLeft; public event HandInteraction TurnRight;

Немного о событиях:

  • TeleportPressed будем вызывать тогда, когда стик только отклонился вперёд.

  • TeleportReleased будем вызывать тогда, когда стик перешел из состояния «отклонен вперед» в нейтральное.

  • TurnLeft и TurnRight будем вызывать тогда, когда стик отклонился влево или вправо соответственно.

Реализуем обработку данных событий:

private void Update() {     ProcessThumbstick(); }  private void ProcessThumbstick() {     _prevThumbstickAxis = _thumbstickAxis;     InputDevice.TryGetFeatureValue(CommonUsages.primary2DAxis, out _thumbstickAxis);          const float teleportThreshold = 0.66f;     if (_prevThumbstickAxis.y < teleportThreshold && _thumbstickAxis.y >= teleportThreshold)     {         TeleportPressed?.Invoke(this);     }     else if (_prevThumbstickAxis.y >= teleportThreshold && _thumbstickAxis.y < teleportThreshold)     {         TeleportReleased?.Invoke(this);     }          const float turnThreshold = 0.66f;     if (_prevThumbstickAxis.x < turnThreshold && _thumbstickAxis.x >= turnThreshold)     {         TurnRight?.Invoke(this);     }     else if (_prevThumbstickAxis.x >= -turnThreshold && _thumbstickAxis.x < -turnThreshold)     {         TurnLeft?.Invoke(this);     } }

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

Игрок

Реализуем два простых метода на перемещение игрока и его поворот. Создадим класс Player и опишем методы для работы с Transform XR Rig’а:

public class Player : MonoBehaviour {     public void SetPosition(Vector3 position)     {         transform.position = position;     }      public void Rotate(float angle)     {         transform.Rotate(0, angle, 0);     } }

Сам компонент навешиваем на объект XR Rig. Вызов метод SetPosition переместит игрока в указанные координаты, а метод Rotate будет отвечать за поворачивание игрока по оси Y (в горизонтальной плоскости).

Телепортация

Реализация телепортации состоит из двух скриптов: Teleport.cs, реализующий логику телепортации и который мы напишем сами, и Arc.cs, занимающийся отрисовкой арки телепортации и который мы возьмем с gist.github.com Скрипт рисования арки является косметическим и вместо него можно было бы использовать простой LineRenderer, но я выбираю красивое и готовое решение для концентрации на логике работы с OpenXR.

Перейдем к самой логике телепорта.

Создадим новый Mono Behaviour и называем его Teleport. В нём вводим несколько публичных полей: ссылку на игрока, ссылку на руку для подписки на события стика, ссылка на арку для управления её отрисовкой, ссылка на точку, из которой будет рисоваться арка, и скорость луча (влияет на его дистанцию).

public Player Player; public Hand Hand; public Arc Arc; public Transform Source; public float TeleportVelocity = 10f;

Также, введём несколько приватных переменных: _arcEnabled для оптимизации работы с просчётом луча, _arcIsValid для присваивания результата просчёта луча и _lastArcTargetPosition для хранения последних координат, куда утыкался луч телепорта.

private bool _arcEnabled; private bool _arcIsValid; private RaycastHit _lastArcTargetPosition;

Далее, подпишемся на события Hand.TeleportPressed и Hand.TeleportReleased и напишем логику для них. На отведение стика мы будем включать луч и начинать его отрисовывать, а как только стик будет возвращаться в своё исходное положение, мы будем выключать отрисовку луча и телепортировать игрока в конечные координаты, если телепортировать туда было возможным.

private void OnEnable() {     Hand.TeleportPressed += StartTeleport;     Hand.TeleportReleased += FinishTeleport; }  private void OnDisable() {     Hand.TeleportPressed -= StartTeleport;     Hand.TeleportReleased -= FinishTeleport; }  private void StartTeleport(Hand hand) {     _arcEnabled = true;     Arc.Show(); }  private void FinishTeleport(Hand hand) {     _arcEnabled = false;     if (_arcIsValid && Arc.IsArcValid())     {         Player.SetPosition(_lastArcTargetPosition.point);     }     Arc.Hide(); }

Также напишем логику рисования луча. Скрипт отрисовки арки требует для отрисовки передачи каждый кадр местоположения, вращения и других параметров работы луча (SetArcData) и вызова самого метода отрисовки (DrawArc). Будем вызывать их в Update.

private void Update() {     if (_arcEnabled)     {         Arc.SetArcData(Source.position, TeleportVelocity * Source.forward, true, false, false);         _arcIsValid = Arc.DrawArc(out _lastArcTargetPosition);     } }

Навесим оба скрипта (Arc и Teleport) на объекты Left Hand и Right Hand и настроим их.

Пример настройки компонентов Arc и Teleport на примере левой руки. Материал для телепорта можно использовать стандартный.
Пример настройки компонентов Arc и Teleport на примере левой руки. Материал для телепорта можно использовать стандартный.

Поворот игрока

Создадим класс TurnPlayer — класс, ответственный за поворот игрока при отклонении стика влево и вправо.

Объявим необходимые поля для работы скрипта: ссылка на игрока, ссылка на руку и угол поворота игрока при отклонении стика.

public Player Player; public Hand Hand; public float Angle = 45f;

И опишем методы подписки на события Hand.TurnLeft и Hand.TurnRight, поворачивающие игрока на указанное в поле Angle значении:

private void OnEnable() {     Hand.TurnLeft += TurnLeft;     Hand.TurnRight += TurnRight; }  private void OnDisable() {     Hand.TurnLeft -= TurnLeft;     Hand.TurnRight -= TurnRight; }  public void TurnLeft(Hand hand) {     Player.Rotate(-Angle); }  public void TurnRight(Hand hand) {     Player.Rotate(Angle); }

Компонент TurnPlayer навешиваем на каждую руку (Left Hand и Right Hand) и настраиваем.

Компонент TurnPlayer на примере левой руки.
Компонент TurnPlayer на примере левой руки.

Ну и в конце концов, создадим тег TeleportArea и повесим его на нашу плоскость. Этот тег позволит нам разделять в дальнейшем области, доступные для телепортации и недоступные для неё.

В итоге у нас должна получиться механика, благодаря которой мы можем более менее удобно перемещаться по нашей сцене. Протестируем её: запускаем Play Mode и отклоняем стик вперёд — луч телепорта начинает рисоваться красным, если телепортироваться в указанную область нельзя, и зеленым, если перемещение возможно. На отклонение стика влево и вправо мы будем поворачиваться в соответствующую сторону.

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


Скоро в OTUS состоится бесплатное открытое занятие «AR сегодня: в развлечениях, в образовании, на работе». На нем узнаете, как AR технологии проникли во все сферы вашей жизни, и попробуете сами создать AR мини-игру. Регистрируйтесь по ссылке.


ссылка на оригинал статьи https://habr.com/ru/company/otus/blog/687286/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *