Unity 用户手册的本节将介绍 Unity 对于虚拟现实、增强现实和 Windows Mixed Reality 应用程序支持的所有输入设备。本页涵盖以下主题:
XR 平台具有多种输入功能,您可以在设计用户交互时加以利用。您的应用程序可以使用某些特定数据,这些数据引用位置、旋转、触摸、按钮、游戏杆和手指传感器。但是,在不同平台之间访问这些输入功能可能有很大差别。例如,Vive 和 Oculus Rift 之间的差异很小,但是支持 VR 的桌面平台和 Daydream 等移动平台之间的差异则很大。
Unity 提供了一个名为 InputFeatureUsage 的 C# 结构,该结构定义了一组标准的物理设备控件(例如按钮和扳机键),以访问任何平台上的用户输入。这些控件可帮助您通过名称识别输入类型。有关每个 InputFeatureUsage
的定义,请参阅 XR.Input.CommonUsages。
每个 InputFeatureUsage
对应一个常见的输入操作或类型。例如,Unity 将名为 trigger
的 InputFeatureUsage
定义为食指控制的单轴输入(无论您使用哪种 XR 平台)。您可以使用 InputFeatureUsage
通过名称来获取 trigger
状态,因此无需为常规 Unity 输入系统设置一个轴(或某些 XR 平台上的按钮)。
下表列出了标准控制器 InputFeatureUsage
的名称以及它们与常见 XR 系统的控制器之间的映射关系:
InputFeatureUsage | 功能类型 | 旧版输入索引__(左控制器/右控制器) | WMR__ | Oculus | GearVR | Daydream | OpenVR (Full) | Vive | 基于 OpenVR 的 Oculus | 基于 OpenVR 的 WMR | Magic Leap | |
---|---|---|---|---|---|---|---|---|---|---|---|
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
是否仍代表激活的控制器。
您可以通过以下方式访问输入设备:
设备特征描述了设备的功能或用途(例如,是否为头戴式)。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 系统中的物理参考点(例如,用户的头部位置、他们的左右手或诸如 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.deviceConnected 和 InputDevices.deviceDisconnected 在设备连接或断开连接时通知您的应用程序。这些还为您提供了有关新连接的输入设备的参考。
由于您可以在多个帧上保留这些引用,因此设备可能会断开连接,或者不再可用。要检查设备的输入是否仍然可用,请使用 InputDevice.isValid。访问输入设备的脚本在尝试使用该设备之前,应在每个帧的开头进行此检查。
您可以从特定的 InputDevice 读取输入功能,例如扳机键按钮的状态。例如,要读取右扳机键的状态,请按照下列步骤操作:
TryGetFeatureValue()
尝试访问功能的当前值,并根据情况返回不同的值:
要获取特定的按钮、触摸输入或游戏杆轴值,请使用 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()));
}
}
}
}
不同的控制器配置提供对不同功能的访问。例如,您可能在一个系统上有多个控制器,在不同系统上有不同的控制器,或者在具有不同 SDK 的同一控制器上有不同的按钮。这种多样性造成了支持各种 XR 系统的输入变得更加复杂。Unity InputFeatureUsage
API 可帮助您获得与平台无关的输入。
以下示例将访问名为 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<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 对象可以获取根骨骼,也可以获取每根手指的骨骼列表。
当 Hand.TryGetRootBone 取得根骨骼时,将获取表示位于腕部上方的骨骼的对象。您还可以获取代表每根手指的骨骼的列表。调用 Hand.TryGetFingerBones 将返回一个列表,其中包含代表该手指的骨骼(从指关节到指尖)。
输入设备支持眼球跟踪设备以及手部跟踪设备。眼球跟踪包括左眼和右眼位置、用户在 3D 空间中正在注视的位置以及每只眼睛眨眼的次数。数据类型为 Eyes。要从设备中获取该数据,请使用 CommonUsages.eyesData。
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 支持以下跟踪原点模式:
您可以使用三种 API 来管理跟踪原点模式:
您仍然可以使用包含 Input 和 XR.InputTracking 的旧版输入系统来获取 XR 输入功能。为此,请使用此页面上的 XR 输入映射表中的相应旧输入索引。在 Player Settings 的 Input 部分(菜单:__Edit__ > Project Settings > __Input__),创建一个轴映射,从而为平台设备功能添加从输入名称到轴索引的适当映射。要获取按钮或轴的值,请使用 Input.GetAxis 或 Input.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);
}
}
}