В этой статье рассматриваются способы работы с системой ввода в XR Interaction Toolkit.
Если хотите посмотреть ролик, то вот ссылки:🔗 https://youtu.be/zwDE4aYnxu4 альтернативная ссылка: 🔗 https://rutube.ru/video/8fe514d875b85a9ccd7588541f34feaa/
Открываем Unity Hub и создаем новый проект, в качестве шаблона будем использовать VR. Я использую версию Unity 6000.3.12f1.

На стандартной сцене у нас есть игрок, представленный как XR Origin Hands (XR Rig), переименуем его в Player для удобства.

Откроем стандартную папку с сэмплами (Samples -> XR Interaction Toolkit -> 3.4.1 -> Starter Assets), и здесь у нас есть XRI Default Input Actions, в котором у нас прописаны стандартные действия для обработки кнопок контроллеров. Мы не будем разбирать все действия, наша задача — понять, как их обрабатывать с помощью кода.


По запросу «XR Input unity» можем найти документацию, где расписаны различные инпуты для каждого поддерживаемого устройства.

Для примера, давайте создадим NPC, у меня есть шлем Oculus, и я хочу сделать так, чтобы диалог с NPC запускался тогда, когда игрок подходит в триггерную зону и нажимает кнопку B на контроллере. Текст с подсказкой, на какую кнопку необходимо нажать будет скрыт, также нам понадобится AudioSource для запуска озвучки диалога.
Создадим скрипт NPC, накинем его на NPC и откроем его в IDE.

Объявим необходимые объекты и методы:.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class NPC : MonoBehaviour
{
[SerializeField] private GameObject _hintText;
private AudioSource _audioSource;
private void Awake()
{
_audioSource = GetComponent<AudioSource>();
}
private void OnTriggerEnter(Collider other)
{
}
private void OnTriggerExit(Collider other)
{
}
}По умолчанию в дефолтных Actions нигде не прописана реакция на кнопку B, ее необходимо прописать самостоятельно. В Action Map XRI Right Interaction создадим новый Action и назовем его B Button.

В Action Type у нас стоит Button, поскольку нам необходимо считывать только нажатие на кнопку. Теперь найдем нужный Binding, для этого можно обратиться к документации, для окулуса, кнопка B запрограммирована на secondaryButton (B на правом контроллере, а Y на левом).

Выбираем XR Controller -> XR Controller (Right Hand) -> Usages -> SecondaryButton. Обязательно сохраните ассет, нажав кнопку Save Assets.

В скрипте создаем сериализуемое поле типа InputActionProperty, в которое мы передадим нашу кнопку. Не забудьте подключить соответствующий using.

Теперь в Unity можем назначить кнопку.

Вернемся к скрипту.
Теперь нам необходимо понять, нажата ли кнопка, это можно сделать несколькими способами, первый способ я никогда не использовал, но, он может быть полезен в тех случаях когда нам нужно отслеживать, зажата ли кнопка, например, в игре Assassins Creed Nexus для активации действий необходимо зажимать кнопку.

Давайте создадим корутину, назовем ее ListenBButtonClick, в ней запускаем бесконечный цикл while и проверяем, нажата ли кнопка, с помощью метода IsPressed() у BButton, внутри условия будет некая логика, например, мы будем запускать озвучку диалога и скрывать hintText. А если звук завершился, то будем показывать hintText.
private IEnumerator ListenBButtonClick()
{
while (true)
{
if (_bButton.action.IsPressed())
{
_audioSource.Play();
_hintText.SetActive(false);
}
if (!_audioSource.isPlaying)
{
_hintText.SetActive(true);
}
yield return null;
}
}Единственный нюанс заключается в том, что звук будет перезапускаться при каждом нажатии на кнопку, чтобы это исправить создадим логическую переменную _canSpeak, изначально она true и диалог будем запускать если нажата кнопка и можно говорить.
bool _canSpeak = true;
private IEnumerator ListenBButtonClick()
{
while (true)
{
if (_bButton.action.IsPressed() && _canSpeak)
{
_canSpeak = false;
_audioSource.Play();
_hintText.SetActive(false);
}
if (!_audioSource.isPlaying)
{
_canSpeak = true;
_hintText.SetActive(true);
}
yield return null;
}
}Запустим корутину в OnTriggerEnter() и остановим в OnTriggerExit().
private void OnTriggerEnter(Collider other)
{
_hintText.SetActive(true);
StartCoroutine(ListenBButtonClick());
}
private void OnTriggerExit(Collider other)
{
_hintText.SetActive(false);
StopAllCoroutines();
}Тестировать будем в симуляции, для этого нам нужно открыть Edit→Project Setting и на вкладке XR Interaction Toolkit импортировать XR Interaction Simulator:

Давайте запустим игру и кратко разберем, как работает симулятор. Используя WASD мы можем передвигаться, а зажав ПКМ – вращаться. Нажав Y откроется меню с подсказками по управлению симулятором. Чтобы активировать правую руку, необходимо нажать «]«.

Теперь, нажав X мы можем увидеть Controller Actions в котором четко показано, что для нажатия кнопки B необходимо нажать кнопку 2 на клавиатуре.
В данном конкретном случае корректнее было бы отслеживать нажатие кнопки через подписку на событие. Уберем вызов и отмену корутины в методах OnTriggerEnter() и OnTriggerExit(), соответственно.
У любых кнопок есть поле action, у которого есть события: кнопка начала нажиматься (started), нажалась (performed), начала нажиматься но не до конца была отжата (canceled).

Нас интересует событие performed, ставим плюс равно и нажимаем Tab для автоматической генерации кода, называем этот метод BButtonPress, например.

Пропишем небольшую логику в BButtonPress:
private void BButtonPress(InputAction.CallbackContext obj)
{
if (_canSpeak)
{
_canSpeak = false;
_audioSource.Play();
_hintText.SetActive(false);
}
}Создадим новую корутину, которая будет отслеживать, закончился диалог или нет:
private IEnumerator DialogueProcess()
{
while (true)
{
if (!_audioSource.isPlaying)
{
_canSpeak = true;
_hintText.SetActive(true);
yield return true;
}
yield return null;
}
}Не забуем ее запустить
private void BButtonPress(InputAction.CallbackContext obj)
{
if (_canSpeak)
{
_canSpeak = false;
_audioSource.Play();
_hintText.SetActive(false);
StartCoroutine(DialogueProcess());
}
}Также мы должны отписаться от события performed в OnTriggerExit():
private void OnTriggerExit(Collider other)
{
_hintText.SetActive(false);
_bButton.action.performed -= BButtonPress;
}Весь код скрипта NPC сейчас выглядит следующим образом:
using System.Collections;
using UnityEngine;
using UnityEngine.InputSystem;
public class NPC : MonoBehaviour
{
[SerializeField] private InputActionProperty _bButton;
[SerializeField] private GameObject _hintText;
private AudioSource _audioSource;
bool _canSpeak = true;
private void Awake()
{
_audioSource = GetComponent<AudioSource>();
}
private void OnTriggerEnter(Collider other)
{
_hintText.SetActive(true);
_bButton.action.performed += BButtonPress;
}
private void BButtonPress(InputAction.CallbackContext obj)
{
if (_canSpeak)
{
_canSpeak = false;
_audioSource.Play();
_hintText.SetActive(false);
StartCoroutine(DialogueProcess());
}
}
private void OnTriggerExit(Collider other)
{
_hintText.SetActive(false);
_bButton.action.performed -= BButtonPress;
}
private IEnumerator DialogueProcess()
{
while (true)
{
if (!_audioSource.isPlaying)
{
_canSpeak = true;
_hintText.SetActive(true);
yield return true;
}
yield return null;
}
}
}Итак, мы научились отслеживать клик по кнопке, подписываясь на события и используя метод IsPressed().
Теперь представьте, что мы создаем игру, в которой игрок будет представлять из себя мехатрона, и мы бы хотели управлять мехом, используя стики контроллера, т.е. нам необходимо знать как считывать значения со стика.
Для примера, в том же скрипте NPC уберем всё лишнее и создадим новый InputActionProperty, назовем его _rightStick. В Update() у стика будем считывать значения, представленные типом Vector2.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.InputSystem;
public class NPC : MonoBehaviour
{
[SerializeField] InputActionProperty _rightStick;
private void Update()
{
Debug.Log(_rightStick.action.ReadValue<Vector2>());
}
}Вернемся в движок и назначим соответствующий Action, в нашем случае это будет Thumbstick на правой руке.

Двойным кликом откроем его и рассмотрим повнимательнее. Он будет возвращать нам некое значение, представленное Vector 2, а кнопка контроллера представляет собой Primary2DAxis.

Запустим симуляцию. Выбираем правую руку. В консоли видим, что сейчас у нас по X и Y нулевые значения. Максимальное отведение стика влево (J) будет -1,0; вправо (L) — 1;0, вверх (I) и вниз (K), соответственно, 0;1 и 0;-1.

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

Контакты
Если у вас есть какие-либо замечания или полезные дополнения к статье, можете написать мне в VK.
VK – https://vk.com/zabelyaevroman
Youtube – https://www.youtube.com/@vrfordev
Rutube – https://rutube.ru/channel/38871248/

