Телепортация игрока в 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.

Перед началом работы с инпутом, необходимо повторить функционал из предыдущего урока: перемещение капсул за руками. В прошлом уроке мы просто повесили капсулы на Tracked Pose, сейчас же объекты капсул существуют без какой-либо логики перемещения в пространстве. Напишем её, благо она очень простая: просто будем повторять в LateUpdate положение и вращение соответствующей Tracked Pose.
private void LateUpdate()
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)
Компонент TurnPlayer навешиваем на каждую руку (Left Hand и Right Hand) и настраиваем.

Ну и в конце концов, создадим тег TeleportArea и повесим его на нашу плоскость. Этот тег позволит нам разделять в дальнейшем области, доступные для телепортации и недоступные для неё.
В итоге у нас должна получиться механика, благодаря которой мы можем более менее удобно перемещаться по нашей сцене. Протестируем её: запускаем Play Mode и отклоняем стик вперёд — луч телепорта начинает рисоваться красным, если телепортироваться в указанную область нельзя, и зеленым, если перемещение возможно. На отклонение стика влево и вправо мы будем поворачиваться в соответствующую сторону.
На этом благодарю за внимание. В следующей статье разберем простую механику взаимодействия с предметами: прикосновение к ним, их подбирание и дальнейшее использование.
Скоро в OTUS состоится бесплатное открытое занятие «AR сегодня: в развлечениях, в образовании, на работе». На нем узнаете, как AR технологии проникли во все сферы вашей жизни, и попробуете сами создать AR мини-игру. Регистрируйтесь по ссылке.
как сделать телепорт объекта
я хочу сделать в главном меню игры облака которые типа плывут над океаном. и не знаю как зациклить их движение слева направо . вот код который я нашёл на передвижение объекта в пространстве , и хотел бы узнать лучший способ зацикливания их передвижения (чтобы они уходили в одну сторону и появлялись с другой)
public class небо : MonoBehaviour < public Vector2 direction; public float speed = 2; void FixedUpdate() < transform.Translate(direction.normalized * speed); >>
Отслеживать
user302909
задан 1 авг 2020 в 19:50
Dark Lord Tehnology Dark Lord Tehnology
35 6 6 бронзовых знаков
1 ответ 1
Сортировка: Сброс на вариант по умолчанию
Вы же правильно пишите, что для зацикливания нужно просто когда облако уйдет направо за экран перемещать его в левую часть экрана (точнее в левую часть за экраном, чтобы оно потихоньку выплыло). Что именно вам не понятно? Как это реализовать? Если да, то реализовать это можно с помощью условия если облако ушло за экран, то переместиться в левую часть экрана:
if (transform.position.x > Screen.width)
Поэкспериментируйте с числами, чтобы облако появлялось слева от экрана, но, чтобы его не была видно, т.к. оно должно медленно выплывать слева.
Гайды по Unity
Как правильно разрезать изображения на спрайты в Unity
Как найти объект по имени и тегу в Unity
Создание простого таймера в Unity
Как подключить рекламу в Unity Ads
Как настроить билд WebGL и загрузить игру в Яндекс Игры
Создание костной 2д анимации в Unity
Сохранение данных с помощью PlayerPrefs
Основы создания партиклов в Unity
Как получить доступ к переменной другого скрипта
Подключаем Yandex рекламу для Яндекс Игр на Unity
Поворот 2д персонажа в сторону его движения
2д стрельба в сторону курсора мыши на Unity
Проверка наличия земли под ногами персонажа перед прыжком
Создание игрового меню
Реализация Стрельбы
Создание полосы здоровья
Создаём простой инвентарь в Unity
Как определить позицию курсора мыши в Unity
Преследование вражеского персонажа за игроком
Как создать магазин ресурсов и предметов
Прыжок Персонажа
Создание простых диалогов
Как подключить внутриигровые покупки для Яндекс Игр
Способы изменения силы гравитации в Unity
Создаем простой телепорт в Unity
Работа с UI элементами в Unity
Создаём рандомный спавн объектов
Добавление звуков ходьбы в Unity
Подбор монет
Как сделать фоновую музыку в Unity
Создаём тайлмап и палитру тайлов
Персонаж 16
Интерфес 11
Анимация 3
Шаблоны Игр 2
Монетизация 5
Мультиплеер и Сеть 0
Сервисы & API & SDK 1
Виртуальная Реальность 0
Мы запускаем сбор на оплату сервера на Январь месяц, который необходим для работы сайта UnityHub.ru.
Мы нуждаемся в Вашей поддержке.
Телепортация Unity 3D
Мне нежно чтобы телепортация происходила только, если на этих координатах не будет препятствия (Стены).
Что нужно добавить для этого?
Дополнен 4 года назад
Какой скрипт нужно использовать чтобы проверить: телепортируется в коллайдер или нет?
Лучший ответ
создать там коллайдер сферу или куб, если не столкнулись ни с чем (или чем-то с тегом) то телепортировать
Владислав КапраловМастер (1980) 4 года назад
Какой скрипт нужно использовать чтобы проверить: телепортируется в коллайдер или нет?
Просто я недавно начал изучать программирование
Сергей Шиманский Мудрец (11183) использовать функцию OnTriggerEnter или OnTriggerStay или что то вроде того, весь скрипт все равно писать не буду)