
Продолжим серию статей про 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.

Перед началом работы с инпутом, необходимо повторить функционал из предыдущего урока: перемещение капсул за руками. В прошлом уроке мы просто повесили капсулы на 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 и настроим их.

Поворот игрока
Создадим класс 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) и настраиваем.

Ну и в конце концов, создадим тег TeleportArea и повесим его на нашу плоскость. Этот тег позволит нам разделять в дальнейшем области, доступные для телепортации и недоступные для неё.
В итоге у нас должна получиться механика, благодаря которой мы можем более менее удобно перемещаться по нашей сцене. Протестируем её: запускаем Play Mode и отклоняем стик вперёд — луч телепорта начинает рисоваться красным, если телепортироваться в указанную область нельзя, и зеленым, если перемещение возможно. На отклонение стика влево и вправо мы будем поворачиваться в соответствующую сторону.
На этом благодарю за внимание. В следующей статье разберем простую механику взаимодействия с предметами: прикосновение к ним, их подбирание и дальнейшее использование.
Скоро в OTUS состоится бесплатное открытое занятие «AR сегодня: в развлечениях, в образовании, на работе». На нем узнаете, как AR технологии проникли во все сферы вашей жизни, и попробуете сами создать AR мини-игру. Регистрируйтесь по ссылке.
ссылка на оригинал статьи https://habr.com/ru/company/otus/blog/687286/
Добавить комментарий