Version: 2023.2
移动键盘
2D game development

Unity XR 输入

Unity 用户手册的本节将介绍 Unity 对于虚拟现实、增强现实和 Windows Mixed Reality 应用程序支持的所有输入设备。本页涵盖以下主题:

XR 平台具有多种输入功能,您可以在设计用户交互时加以利用。您的应用程序可以使用某些特定数据,这些数据引用位置、旋转、触摸、按钮、游戏杆和手指传感器。但是,在不同平台之间访问这些输入功能可能有很大差别。例如,Vive 和 Oculus Rift 之间的差异很小,但是支持 VR 的桌面平台和 Daydream 等移动平台之间的差异则很大。

Unity 提供了一个名为 InputFeatureUsage 的 C# 结构,该结构定义了一组标准的物理设备控件(例如按钮和扳机键),以访问任何平台上的用户输入。这些控件可帮助您通过名称识别输入类型。有关每个 InputFeatureUsage 的定义,请参阅 XR.Input.CommonUsages

每个 InputFeatureUsage 对应一个常见的输入操作或类型。例如,Unity 将名为 triggerInputFeatureUsage 定义为食指控制的单轴输入(无论您使用哪种 XR 平台)。您可以使用 InputFeatureUsage 通过名称来获取 trigger 状态,因此无需为常规 Unity 输入系统设置一个轴(或某些 XR 平台上的按钮)。

XR 输入映射

下表列出了标准控制器 InputFeatureUsage 的名称以及它们与常见 XR 系统的控制器之间的映射关系:

InputFeatureUsage 功能类型 旧版输入索引__(左控制器/右控制器) | WMR__ Oculus GearVR Daydream OpenVR (Full) Vive 基于 OpenVR 的 Oculus 基于 OpenVR 的 WMR
primary2DAxis 2D 轴 [(1,2)/(4,5)] 触控板 游戏杆 触控板 触控板 触控板/游戏杆 触控板 游戏杆 游戏杆
trigger [9/10] 触发器 触发器 触发器 触发器 触发器 触发器 触发器 触发器
grip [11/12] 握把 握把 缓冲键 握把 握把 握把 握把
secondary2DAxis 2D 轴 [(17,18)/(19,20)] 游戏杆 触控板
secondary2DAxisClick 按钮 [18/19] 游戏杆 - 单击
primaryButton 按钮 [2/0] [X/A] - 按压 应用程序 主要 主要(三明治按钮)(1) 主要 (Y/B) 菜单
primaryTouch 按钮 [12/10] [X/A] - 触控
secondaryButton 按钮 [3/1] [Y/B] - 按压 备用 备用 (X/A)
secondaryTouch 按钮 [13/11] [Y/B] - 触控
gripButton 按钮 [4/5] 握把 - 按下 握把 - 按下 握把 - 按下 握把 - 按下 握把 - 按下 握把 - 按下
triggerButton 按钮 [14/15] 触发器 - 按下 触发器 - 按下 触发器 - 按下 触发器 - 按下 触发器 - 按下 触发器 - 按下 触发器 - 触控 触发器 - 按下
menuButton 按钮 [6/7] 菜单 开始(仅限左控制器)
primary2DAxisClick 按钮 [8/9] 触控板 - 单击 控制杆 - 按下 触控板 - 按下 触控板 - 按下 触控板/游戏杆 - 按下 触控板 - 按下 游戏杆 - 按下 触控板 - 按下
primary2DAxisTouch 按钮 [16/17] 触控板 - 触摸 控制杆 - 触控 触控板 - 触摸 触控板 - 触摸 触控板/游戏杆 - 触控 触控板 - 触控 游戏杆 - 触控 触控板 - 触摸
batteryLevel 电池电量
userPresence 按钮 用户存在状态 用户存在状态

(1) 三明治按钮是指 Vive 菜单按钮。为了更好地处理跨平台应用程序,此按钮映射到 primaryButton 而不是 menuButton。

有关每个 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()));
}

在 XR 系统断开与输入设备的连接之前,输入设备将跨帧保持有效。使用 InputDevice.IsValid 属性来确定 InputDevice 是否仍代表激活的控制器。

您可以通过以下方式访问输入设备:

  • 特征
  • 角色
  • XR 节点

按特征访问输入设备

设备特征描述了设备的功能或用途(例如,是否为头戴式)。InputDeviceCharacteristics 是一系列标志,可以添加到代码中,用于搜索符合特定规格的设备。可以按以下特征筛选设备:

设备 特征
HeadMounted 设备连接到用户的头部。它具有设备跟踪和眼球中心跟踪功能。此标志最常用于标识头戴式显示器 (HMD)。
Camera 设备具有摄像机跟踪功能。
HeldInHand 用户将设备握在手中。
HandTracking 设备代表物理跟踪的手。它具有设备跟踪功能,并且可能包含手和骨骼数据。
EyeTracking 设备可以执行眼球跟踪并具有 EyesData 功能。
TrackedDevice 可以在 3D 空间中跟踪设备。它具有设备跟踪功能。
Controller 设备具有按钮和轴的输入数据,并且可以用作控制器。
TrackingReference 设备代表静态跟踪参考对象。它具有设备跟踪功能,但该跟踪数据不应该更改。
Left 将此特征与 HeldInHand 或 HandTracking 特征组合使用,可以将设备标识为与左手关联。
Right 将此特征与 HeldInHand 或 HandTracking 特征组合使用,可以将设备标识为与右手关联。
Simulated6DOF 设备报告 6DOF 数据,但仅具有 3DOF 传感器。Unity 负责模拟位置数据。

底层 XR SDK 会报告这些特征。您可以使用 InputDevice.Characteristics 查找这些特征。设备可以并且通常应该具有多个特征,可以使用位标志来筛选和访问这些特征。

InputDevices.GetDevicesWithCharacteristics 提供了一种方法来搜索具有给定特征集的所有设备。例如,您可以使用以下代码搜索系统中可用的 Left、HeldInHand、Controller InputDevices:

var leftHandedControllers = new List<UnityEngine.XR.InputDevice>();
var desiredCharacteristics = UnityEngine.XR.InputDeviceCharacteristics.HeldInHand | UnityEngine.XR.InputDeviceCharacteristics.Left | UnityEngine.XR.InputDeviceCharacteristics.Controller;
UnityEngine.XR.InputDevices.GetDevicesWithCharacteristics(desiredCharacteristics, leftHandedControllers);

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

此函数找到的设备至少包含指定的特征,但可能也包含其他特征。例如,要查找惯用左手的控制器,您只能查找 InputDeviceCharacteristic.Left,而不能查找 InputDeviceCharacteristic.Controller。

按角色访问输入设备

设备角色描述输入设备的一般功能。请使用 InputDeviceRole 枚举来指定设备角色。定义的角色有:

角色 描述
GameController 游戏主机风格的游戏控制器。
Generic 代表核心 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 摄像机之类的跟踪参考)。

XRNode 枚举定义了以下节点:

XR 节点 描述
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!");
}

监听设备连接和断开连接

输入设备在各帧之间是一致的,但是可以随时连接或断开连接。为避免反复检查设备是否已连接到平台,请使用 InputDevices.deviceConnectedInputDevices.deviceDisconnected 在设备连接或断开连接时通知您的应用程序。这些还为您提供了有关新连接的输入设备的参考。

由于您可以在多个帧上保留这些引用,因此设备可能会断开连接,或者不再可用。要检查设备的输入是否仍然可用,请使用 InputDevice.isValid。访问输入设备的脚本在尝试使用该设备之前,应在每个帧的开头进行此检查。

访问输入设备上的输入功能

您可以从特定的 InputDevice 读取输入功能,例如扳机键按钮的状态。例如,要读取右扳机键的状态,请按照下列步骤操作:

  1. 使用 InputDeviceRole.RightHandedXRNode.RightHand 获取惯用右手设备的实例。
  2. 有了正确的设备后,请使用 InputDevice.TryGetFeatureValue 方法访问当前状态。

TryGetFeatureValue() 尝试访问功能的当前值,并根据情况返回不同的值:

  • 如果成功获取指定的功能值,则返回 true
  • 如果当前设备不支持指定的功能,或者该设备无效(即控制器不再处于激活状态),则返回 false

要获取特定的按钮、触摸输入或游戏杆轴值,请使用 CommonUsages 类。CommonUsages 包括 XR 输入映射表中的每个 InputFeatureUsage,以及诸如位置和旋转之类的跟踪功能。以下代码示例使用 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 可帮助您获得与平台无关的输入。

以下示例将访问名为 primaryButtonInputFeatureUsage,无论是由哪个控制器或输入设备提供。该示例包括一个类,该类在可用设备连接后扫描可用设备的 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<InputDevice> devicesWithPrimaryButton;

    private void Awake()
    {
        if (primaryButtonPress == null)
        {
            primaryButtonPress = new PrimaryButtonEvent();
        }

        devicesWithPrimaryButton = new List<InputDevice>();
    }

    void OnEnable()
    {
        List<InputDevice> allDevices = new List<InputDevice>();
        InputDevices.GetDevices(allDevices);
        foreach(InputDevice device in allDevices)
            InputDevices_deviceConnected(device);

        InputDevices.deviceConnected += InputDevices_deviceConnected;
        InputDevices.deviceDisconnected += InputDevices_deviceDisconnected;
    }

    private void OnDisable()
    {
        InputDevices.deviceConnected -= InputDevices_deviceConnected;
        InputDevices.deviceDisconnected -= InputDevices_deviceDisconnected;
        devicesWithPrimaryButton.Clear();
    }

    private void InputDevices_deviceConnected(InputDevice device)
    {
        bool discardedValue;
        if (device.TryGetFeatureValue(CommonUsages.primaryButton, out discardedValue))
        {
            devicesWithPrimaryButton.Add(device); // 添加任何具有主按钮的设备。
        }
    }

    private void InputDevices_deviceDisconnected(InputDevice device)
    {
        if (devicesWithPrimaryButton.Contains(device))
            devicesWithPrimaryButton.Remove(device);
    }

    void Update()
    {
        bool tempState = false;
        foreach (var device in devicesWithPrimaryButton)
        {
            bool primaryButtonState = false;
            tempState = device.TryGetFeatureValue(CommonUsages.primaryButton, out primaryButtonState) // 确实获得了值
                        && primaryButtonState // 我们获得的值
                        || tempState; // 来自其他控制器的累计结果
        }

        if (tempState != lastButtonState) // 自上一帧以来按钮状态已更改
        {
            primaryButtonPress.Invoke(tempState);
            lastButtonState = tempState;
        }
    }
}

以下 PrimaryReactor 类使用 PrimaryButtonWatcher 来检测您何时按下主按钮,然后为了响应按下操作而旋转其父级游戏对象。要使用该类,请将该类添加到可见的游戏对象(例如立方体),然后将 PrimaryButtonWatcher 引用拖到 Watcher 属性上。

using System.Collections;
using UnityEngine;

public class PrimaryReactor : MonoBehaviour
{
    public PrimaryButtonWatcher watcher;
    public bool IsPressed = false; // 用于在 Unity Inspector 窗口中显示按钮状态
    public Vector3 rotationAngle = new Vector3(45, 45, 45);
    public float rotationDuration = 0.25f; // 秒
    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;
        }
    }
}

访问手部跟踪数据

InputDevices 支持手部跟踪设备。手部跟踪设备始终:

手部跟踪数据由一个 Hand 对象以及一系列(最多 21个)Bone 输入功能组成。每个 Bone 都有一个位置和方向,以及对层级视图中其父级和任何子级骨骼的引用。Hand 对象可以获取根骨骼,也可以获取每根手指的骨骼列表。

When Hand.TryGetRootBone gets the root bone, it retrieves an object that represents a bone located just above the wrist. You can also get a list of bones that represents each individual finger. Calling Hand.TryGetFingerBones returns a list, from knuckle to tip, of the bones that represents that finger.

访问眼球跟踪数据

输入设备支持眼球跟踪设备以及手部跟踪设备。眼球跟踪包括左眼和右眼位置、用户在 3D 空间中正在注视的位置以及每只眼睛眨眼的次数。数据类型为 Eyes。要从设备中获取该数据,请使用 CommonUsages.eyesData

XRInputSubsystem 和 InputDevice 关联

Unity 提供了两个输入系统:旧版输入系统和 2019.2 中引入的 XR 插件架构。在新设置中,每个 InputDevice 都与一个 XRInputSubsystem 关联。这些子系统对象控制与任何特定输入设备都无关联的全局输入行为(例如,管理跟踪原点或将跟踪的设备重新居中)。

每个 InputDevice 都包含对其关联子系统的引用。如果设备来自集成平台,则此引用为 null。还可以使用 SubsystemManager.GetInstances<XRInputSubsystem> 获得所有激活的 XRInputSubsystem 对象,每个 XRInputSubsystem 都可以使用 XRInputSubsystem.TryGetInputDevices 获取其设备。

您可以使用输入子系统通过 UnityEngine.XR.XRInputSubsystem 将设备重新居中。“重新居中”可将 HMD 的当前位置设置为所有设备的新原点。对于无法重新居中的设备,或者如果平台不支持重新居中,则返回 false。

要获取跟踪边界,请使用 TryGetBoundaryPoints。这包含一系列顺时针排序的 3D 点,其中 y 值位于地面,它们标记出用户指定的“安全区域”以放置内容和交互。您可以使用 XRInputSubsystem.boundaryChanged 来监听此边界的变化。

XRInputSubsystem 还负责跟踪原点模式,该模式提供了跟踪世界原点的上下文。Unity 支持以下跟踪原点模式:

  • 设备:原点的位置在主显示设备(通常是 HMD 或手机)的第一个已知位置。
  • 地面:原点的位置在地面上的已知位置。
  • 跟踪参考:原点的位置在设置了 TrackingReference 特征的 InputDevice 上。
  • 未知:跟踪原点的类型未知。这可能是由于系统故障或缺少跟踪原点模式支持所致。

您可以使用三种 API 来管理跟踪原点模式:

通过旧版输入系统实现 XR 输入

您仍然可以使用包含 InputXR.InputTracking 的旧版输入系统来获取 XR 输入功能。为此,请使用此页面上的 XR 输入映射表中的相应旧输入索引。在 Player SettingsInput 部分(菜单:__Edit__ > Project Settings > __Input__),创建一个轴映射,从而为平台设备功能添加从输入名称到轴索引的适当映射。要获取按钮或轴的值,请使用 Input.GetAxisInput.GetButton 并传入现在映射的轴或按钮的名称。

有关如何使用按钮和游戏杆轴的更多信息,请参阅 InputManager 文档。

触觉

您可以将触觉事件发送到 InputDevice。触觉呈脉冲形式,具有振幅和持续时间。

并非所有平台都支持所有类型的触觉,但您可以查询设备的触觉功能。以下示例获取右手的输入设备,检查设备是否具有触觉功能,然后在有触觉功能的情况下再现脉冲:

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);
            }
    }
}
移动键盘
2D game development