공간 매핑의 일반적인 문제 해결
Windows 혼합 현실 입력

Unity XR 입력

이 섹션에서는 가상 현실, 증강 현실, Windows Mixed Reality 애플리케이션을 위한 모든 Unity 지원 입력 기기의 정보를 제공합니다. 이 페이지는 다음 섹션으로 구성됩니다.

XR 플랫폼은 일반적으로 사용자 상호작용을 디자인할 때 사용할 수 있는 매우 다양한 입력 기능을 제공합니다. 포지션, 회전, 터치, 버튼, 조이스틱, 손가락 센서는 모두 특정 데이터 조각을 제공합니다.

한편 이러한 입력 기능에 액세스하는 방법은 XR 플랫폼마다 다릅니다. Vive와 Oculus Rift 간에는 미세한 차이가 있지만 데스크톱 VR 플랫폼과 모바일 플랫폼(예: Daydream)은 서로 현저하게 다릅니다.

Unity는 InputFeatureUsage라고 불리는 C# 구조체를 제공합니다. 이 구조체는 물리 기기 요소(예: 버튼, 트리거)의 표준 집합을 정의하여 플랫폼 애그노스틱 방식으로 사용자 입력에 액세스하도록 지원합니다. 이를 통해 이름을 기준으로 입력 타입을 빠르게 식별할 수 있습니다. 각 InputFeatureUsage의 정의는 XR.Input.CommonUsages을 참조하십시오.

InputFeatureUsage는 공통 입력 액션 또는 타입에 해당합니다. 예를 들어 Unity는 사용하는 XR 플랫폼에 관계없이 trigger라고 불리는 InputFeatureUsage를 검지손가락으로 제어되는 단일 축 입력으로 정의합니다. InputFeatureUsage를 사용하여 이름을 기준으로 trigger 상태를 가져올 수 있으므로 기존 Unity 입력 시스템에 대해 축(또는 일부 XR 플랫폼의 버튼)을 설정할 필요가 없습니다.

XR 입력 매핑

다음 표에는 표준 컨트롤러 InputFeatureUsage 이름과 인기 XR 시스템의 컨트롤러에 매핑되는 방식이 나와 있습니다.

InputFeatureUsage FeatureType Legacy Input Index [L/R] WMR Oculus GearVR Daydream OpenVR (Full) Vive OpenVR
(
Oculus)
OpenVR (WMR)
primary2DAxis 2D 축 [(1,2)/(4,5)] 조이스틱 조이스틱 조이스틱 터치패드 [트랙패드/조이스틱] 트랙패드 조이스틱 조이스틱
trigger [9/10] 트리거 트리거 트리거 트리거 트리거 트리거 트리거 트리거
grip [11/12] 그립 그립 그립 그립 그립 그립 그립
indexTouch [13/14] 검지 - 인접 터치
thumbTouch [15/16] 엄지 - 인접 터치
secondary2DAxis 2D 축 [(17,18)/(19,20)] 터치패드 터치패드
indexFinger [21/22] 검지
middleFinger [23/24] 중지
ringFinger [25/26] 약지
pinkyFinger [27/28] 소지
combinedTrigger [3/3] 연결 트리거 연결 트리거 연결 트리거 연결 트리거 연결 트리거 연결 트리거
primaryButton 버튼 [2/0] [X/A] 기본 기본 기본 [Y/B] 메뉴
primaryTouch 버튼 [12/10] [X/A] - 터치
secondaryButton 버튼 [3/1] [Y/B] 대체 대체 [B/A]
secondaryTouch 버튼 [13/11] [Y/B] - 터치
gripButton 버튼 [4/5] 그립 - 누르기 그립 - 누르기 그립 - 누르기 그립 - 누르기 그립 - 누르기 그립 - 누르기 그립
triggerButton 버튼 [14/15] 트리거 - 누르기 검지 - 터치 트리거 - 누르기 트리거 - 누르기 트리거 - 누르기 트리거 - 누르기 트리거 - 터치 트리거 - 누르기
menuButton 버튼 [6/7] 메뉴 시작 (6)
primary2DAxisClick 버튼 [8/9] 터치패드 - 클릭 썸스틱 - 클릭 터치패드 - 클릭 터치패드 - 클릭 StickOrPad - 누르기 StickOrPad - 누르기 StickOrPad - 누르기 터치패드 - 클릭
primary2DAxisTouch 버튼 [16/17] 터치패드 - 터치 썸스틱 - 터치 터치패드 - 터치 터치패드 - 터치 StickOrPad - 터치 StickOrPad - 터치 StickOrPad - 터치 터치패드 - 터치
thumbrest 버튼 [18/19] 조이스틱 - 클릭 ThumbRest - 터치

InputFeatureUsage의 정의는 XR.Input.CommonUsages를 참조하십시오.

입력 기기 액세스

InputDevice는 컨트롤러, 휴대폰, 헤드셋 등과 같은 모든 물리적 기기를 나타내며 기기 추적, 버튼, 조이스틱, 기타 입력 컨트롤에 대한 정보를 보관할 수 있습니다. InputDevice API에 대한 자세한 내용은 InputDevice를 참조하십시오.

XR.InputDevices 클래스를 사용하여 현재 XR 시스템과 연결된 입력 기기(예: 컨트롤러, 트래커 등)에 액세스할 수 있습니다. 연결된 모든 기기의 리스트를 가져오려면 InputDevices.GetDevices를 사용하십시오.

var inputDevices = new List<UnityEngine.XR.InputDevice>();
UnityEngine.XR.InputDevices.GetDevices(inputDevices);

foreach (var device in inputDevices)
{
    Debug.Log(string.Format("Device found with name '{0}' and role '{1}'", device.name, device.role.ToString()));
}

InputDevice는 XR 시스템이 연결을 해제하기 전까지 모든 프레임에서 유효합니다. InputDevice.IsValid 프로퍼티를 사용하여 InputDevice가 여전히 활성화된 컨트롤러를 나타내는지 확인하십시오.

역할별로 입력 기기에 액세스

기기 역할은 입력 기기의 일반적인 기능을 설명합니다. InputDeviceRole 열거형을 사용하여 기기 역할을 지정하십시오. 정의된 역할은 다음과 같습니다.

  • GameController: 콘솔 스타일 게임 컨트롤러
  • Generic: HMD, 모바일 기기 등과 같은 핵심 XR 기기를 나타내는 기기
  • HardwareTracker: 추적 기기
  • LeftHanded: 왼손잡이 사용자를 위한 기기
  • RightHanded: 오른손잡이 사용자를 위한 기기
  • TrackingReference: Oculus 추적 카메라 등과 같은 다른 기기를 추적하는 기기

기본 XR SDK가 이러한 역할을 보고하며, 다른 공급자들은 다른 방식으로 자체 기기 역할을 정리할 수 있습니다. 또한 사용자가 양쪽 손을 바꾸어 사용할 수 있으므로 할당된 역할이 사용자가 입력 기기를 조작하는 데 사용하는 손과 일치하지 않을 수 있습니다. 예를 들어 사용자는 Daydream 컨트롤러를 왼손잡이 또는 오른손잡이로 설정해야 하지만, 간단히 컨트롤러를 반대쪽 손으로 조작할 수도 있습니다.

GetDevicesWithRole은 특정 InputDeviceRole이 포함된 모든 기기의 리스트를 제공합니다. 예를 들어 InputDeviceRole.GameController를 사용하여 연결된 GameController 기기를 모두 가져올 수 있습니다.

var gameControllers = new List<UnityEngine.XR.InputDevice>();
UnityEngine.XR.InputDevices.GetDevicesWithRole(UnityEngine.XR.InputDeviceRole.GameController, gameControllers);

foreach (var device in gameControllers)
{
    Debug.Log(string.Format("Device name '{0}' has role '{1}'", device.name, device.role.ToString()));
}

XR 노드별로 입력 기기에 액세스

XR 노드는 XR 시스템의 물리적 레퍼런스 포인트를 나타냅니다. 사용자의 머리 포지션, 오른손과 왼손, 추적 레퍼런스(예: Oculus 카메라)는 모두 XR 노드입니다. XRNode 열거형은 이용 가능한 노드를 정의합니다. 다음은 정의된 노드의 예입니다.

  • CenterEye: 사용자 동공 사이의 중간 지점.

  • GameController: 콘솔 스타일 게임 컨트롤러. 다수의 게임 컨트롤러 노드가 존재할 수 있습니다.

  • HardwareTracker: 대개 사용자 또는 물리 제품에 연결되는 하드웨어 추적 기기. 다수의 하드웨어 트래커 노드가 존재할 수 있습니다.

  • Head: 사용자 머리의 중앙 지점(XR 시스템의 계산에 기반)

  • LeftEye: 사용자의 왼쪽 눈

  • LeftHand: 사용자의 왼쪽 손

  • RightEye: 사용자의 오른쪽 눈

  • RightHand: 사용자의 오른쪽 손

  • TrackingReference: Oculus 카메라 등과 같은 추적 레퍼런스 포인트. 다수의 추적 레퍼런스 노드가 존재할 수 있습니다.

InputDevices.GetDevicesAtXRNode를 사용하여 특정 XRNode와 연결된 기기 리스트를 가져옵니다. 예를 들어 다음은 왼손잡이용 컨트롤러를 가져오는 방법의 예시입니다.

var leftHandDevices = new List<UnityEngine.XR.InputDevice>();
UnityEngine.XR.InputDevices.GetDevicesAtXRNode(UnityEngine.XR.XRNode.LeftHand, leftHandDevices);

if(leftHandDevices.Count == 1)
{
    UnityEngine.XR.InputDevice device = leftHandDevices[0];
    Debug.Log(string.Format("Device name '{0}' with role '{1}'", device.name, device.role.ToString()));
}
else if(leftHandDevices.Count > 1)
{
    Debug.Log("Found more than one left hand!");
}

입력 기기의 입력 기능에 액세스

특정 InputDevice에서 입력 기능(예: 트리거 버튼의 상태)을 읽을 수 있습니다. 예를 들어 오른쪽 트리거 버튼의 상태를 읽으려면 오른손잡이용 기기의 인스턴스를 먼저 가져옵니다(InputDeviceRole.RightHanded 또는 XRNode.RightHand 사용). 올바른 기기가 있는 경우 InputDevice.TryGetFeatureValue 함수를 사용하여 현재 상태에 액세스하십시오.

TryGetFeatureValue()는 기능의 현재 값에 액세스하려고 시도하지만, 현재 기기가 특정 기능을 지원하지 않거나 기기가 유효하지 않은 경우(컨트롤러 비활성화) 액세스에 실패할 수 있습니다. 이 함수는 특정 기능 값을 검색해서 가져오면 true를 반환하고 실패하면 false를 반환합니다.

특정 버튼, 터치 입력 또는 조이스틱 축 값을 가져오려면 CommonUsages 클래스를 사용하십시오. CommonUsages는 포지션, 회전 등과 같은 추적 기능을 비롯하여 각 InputFeatureUsageXR 입력 매핑 테이블에 포함합니다. 다음 예제 코드는 CommonUsages.triggerButton을 사용하여 플레이어가 현재 특정 InputDevice 인스턴스의 트리거 버튼을 당기고 있는지 감지합니다.

bool triggerValue;
if (device.TryGetFeatureValue(UnityEngine.XR.CommonUsages.triggerButton, out triggerValue) && triggerValue)
{
    Debug.Log("Trigger button is pressed");
}

또한 InputDevice.TryGetFeatureUsages 함수를 사용하여 기기에서 제공하는 전체 InputFeatureUsage 리스트를 가져올 수도 있습니다. 이 함수는 기능을 설명하는 타입 및 이름 프로퍼티가 포함된 InputFeatureUsage 항목 리스트를 반환합니다. 다음 예시에는 특정 입력 기기에서 제공된 모든 부울 기능이 열거되어 있습니다.

var inputFeatures = new List<UnityEngine.XR.InputFeatureUsage>();
if (device.TryGetFeatureUsages(inputFeatures))
{
    foreach (var feature in inputFeatures)
    {
        if (feature.type == typeof(bool))
        {
            bool featureValue;
            if (device.TryGetFeatureValue(feature.As<bool>(), out featureValue))
            {
                Debug.Log(string.Format("Bool feature '{0}''s value is '{1}'", feature.name, featureValue.ToString()));
            }
        }
    }
}

primaryButton 예시

다양한 컨트롤러 설정을 통해 다양한 기능에 액세스할 수 있습니다. 예를 들어 한 시스템에 여러 개의 컨트롤러가 있거나, 여러 개의 시스템에 여러 개의 컨트롤러가 있거나, 여러 개의 SDK가 포함된 동일한 컨트롤러에 여러 개의 버튼이 있을 수 있습니다. 이러한 다양성은 광범위한 XR 시스템에 대한 입력 지원을 더욱 어렵게 만듭니다. Unity InputFeatureUsage API를 사용하면 XR 플랫폼 애그노스틱 방식으로 입력을 쉽게 가져올 수 있습니다.

다음 예시에서는 어느 컨트롤러 또는 입력 기기가 제공하든지 관계없이 primaryButton이라고 불리는 InputFeatureUsage에 액세스합니다. 예시에는 primaryButton에 사용할 수 있는 기기를 검색하는 클래스가 포함되어 있습니다. 이 클래스는 연결된 기기의 기능 값을 모니터링하며, 해당 값이 변하면 클래스가 UnityEvent를 디스패치합니다.

이 클래스를 사용하려면 씬의 모든 게임 오브젝트에 컴포넌트로 추가하십시오.

using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.XR;

[System.Serializable]
public class PrimaryButtonEvent : UnityEvent<bool>{}

public class PrimaryButtonWatcher : MonoBehaviour
{
    public PrimaryButtonEvent primaryButtonPress;
    
    private bool lastButtonState = false;
    private List<UnityEngine.XR.InputDevice> allDevices;
    private List<UnityEngine.XR.InputDevice> devicesWithPrimaryButton;
    
    void Start()
    {
        if(primaryButtonPress == null)
        {
            primaryButtonPress = new PrimaryButtonEvent();
        }
        allDevices = new List<UnityEngine.XR.InputDevice>();
        devicesWithPrimaryButton = new List<UnityEngine.XR.InputDevice>();
        InputTracking.nodeAdded += InputTracking_nodeAdded;
    }

    // check for new input devices when new XRNode is added
    private void InputTracking_nodeAdded(XRNodeState obj)
    {
        updateInputDevices();
    }

    void Update()
    {
        bool tempState = false;
        bool invalidDeviceFound = false;
        foreach(var device in devicesWithPrimaryButton)
        {
            bool primaryButtonState = false;
            tempState = device.isValid // the device is still valid
                        && device.TryGetFeatureValue(CommonUsages.primaryButton, out primaryButtonState) // did get a value
                        && primaryButtonState // the value we got
                        || tempState; // cumulative result from other controllers
            if (!device.isValid)
                invalidDeviceFound = true;
        }

        if (tempState != lastButtonState) // Button state changed since last frame
        {
            primaryButtonPress.Invoke(tempState);
            lastButtonState = tempState;
        }

        if (invalidDeviceFound || devicesWithPrimaryButton.Count == 0) // refresh device lists
            updateInputDevices();
    }

    // find any devices supporting the desired feature usage
    void updateInputDevices()
    {
        devicesWithPrimaryButton.Clear();
        UnityEngine.XR.InputDevices.GetDevices(allDevices);
        bool discardedValue;
        foreach (var device in allDevices)
        {
            if(device.TryGetFeatureValue(CommonUsages.primaryButton, out discardedValue))
            {
                devicesWithPrimaryButton.Add(device); // Add any devices that have a primary button.
            }
        }
    }
}

다음 PrimaryReactor 클래스는 PrimaryButtonWatcher를 사용하여 사용자가 기본 버튼을 누르는 시간을 감지하여 그에 대한 응답으로 부모 게임 오브젝트를 회전시킵니다. 이 클래스를 사용하려면 눈에 보이는 게임 오브젝트(예: 큐브)에 추가한 후 PrimaryButtonWatcher 레퍼런스를 감시자 프로퍼티로 드래그하십시오.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PrimaryReactor : MonoBehaviour
{
    public PrimaryButtonWatcher watcher;
    public bool IsPressed = false; // used to display button state in the Unity Inspector window
    public Vector3 rotationAngle = new Vector3(45, 45, 45);
    public float rotationDuration = 0.25f; // seconds
    private Quaternion offRotation;
    private Quaternion onRotation;
    private Coroutine rotator;

    void Start()
    {
        watcher.primaryButtonPress.AddListener(onPrimaryButtonEvent);
        offRotation = this.transform.rotation;
        onRotation = Quaternion.Euler(rotationAngle) * offRotation;
    }

    public void onPrimaryButtonEvent(bool pressed)
    {
        IsPressed = pressed;
        if (rotator != null)
            StopCoroutine(rotator);
        if (pressed)
            rotator = StartCoroutine(AnimateRotation(this.transform.rotation, onRotation));
        else
            rotator = StartCoroutine(AnimateRotation(this.transform.rotation, offRotation));
    }

    private IEnumerator AnimateRotation(Quaternion fromRotation, Quaternion toRotation)
    {
        float t = 0;
        while(t < rotationDuration)
        {
            transform.rotation = Quaternion.Lerp(fromRotation, toRotation, t/rotationDuration);
            t += Time.deltaTime;
            yield return null;
        }
    }
}

레거시 입력 시스템을 통한 XR 입력

XR 입력 매핑 표에 나온 적절한 레거시 입력 인덱스를 사용하면 레거시 입력 시스템을 통해 XR 입력 기능을 폴링할 수 있습니다. Edit > Settings > Input 에서 축 매핑을 생성한 후 입력 이름의 적절한 매핑을 플랫폼 기기 기능의 축 인덱스에 추가하십시오. 버튼 또는 축 값을 검색해서 가져오려면 Input.GetAxis 또는 Input.GetButton을 사용하여 현재 매핑된 축 또는 버튼 이름을 전달해야 합니다.

버튼 및 조이스틱 축을 사용하는 방법에 관한 자세한 내용은 기존의 게임 입력 문서를 참조하십시오.

햅틱스

햅틱 이벤트를 InputDevice에 전송할 수 있습니다. Unity는 진폭과 기간이 포함된 간단한 임펄스 또는 데이터 버퍼로서의 햅틱스를 지원합니다.

모든 플랫폼이 모든 햅틱 유형을 지원하지만, 햅틱 기능에 대해 기기에 쿼리할 수 있습니다. 다음 예시는 오른손잡이용 입력 기기를 가져온 후 해당 기기가 햅틱 기능을 지원하는지 확인하고, 지원하는 경우 임펄스를 재생합니다.

List<UnityEngine.XR.InputDevice> devices = new List<UnityEngine.XR.InputDevice>(); 

UnityEngine.XR.InputDevices.GetDevicesWithRole(UnityEngine.XR.InputDeviceRole.RightHanded, devices);

foreach (var device in devices)
{
    UnityEngine.XR.HapticCapabilities capabilities;
    if (device.TryGetHapticCapabilities(out capabilities))
    {
            if (capabilities.supportsImpulse)
            {
                uint channel = 0;
                float amplitude = 0.5f;
                float duration = 1.0f;
                device.SendHapticImpulse(channel, amplitude, duration);
            }
    }
}

  • 2019.1에서 새로운 기능 및 동작 추가됨 NewIn20191
공간 매핑의 일반적인 문제 해결
Windows 혼합 현실 입력