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 读取输入功能,例如扳机键按钮的状态。例如,要读取右扳机键的状态,请按照下列步骤操作:
1.使用 InputDeviceRole.RightHanded 或 XRNode.RightHand 获取惯用右手设备的实例。 2.有了正确的设备后,请使用 InputDevice.TryGetFeatureValue 方法访问当前状态。
TryGetFeatureValue()
尝试访问功能的当前值,并根据情况返回不同的值:
To get a particular button, touch input, or joystick axis value, use the CommonUsages class. CommonUsages
includes each InputFeatureUsage
in the XR input mapping table, as well as tracking features like position and rotation. The following code example uses CommonUsages.triggerButton to detect whether the user is currently pressing the trigger button on a particular InputDevice
instance:
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 对象可以获取根骨骼,也可以获取每根手指的骨骼列表。
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.
Input devices support eye-tracking devices, as well as hand-tracking devices. Eye-tracking consists of the left and right eye positions, the location in 3D space where the user is looking, and the amount that each individual eye is blinking. Its data type is Eyes. To retrieve it from a device, use 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 来管理跟踪原点模式:
You can still use the legacy input system, consisting of Input and XR.InputTracking, to retrieve XR input features. To do this, use the appropriate legacy input indices from the XR input mappings table on this page. In the Input section of the Player Settings (menu: Edit > Project Settings > Input), create an axis mapping to add the appropriate mapping from input name to axis index for the platform device’s feature. To retrieve the button or axis value, use Input.GetAxis or Input.GetButton and pass in the now-mapped axis or button name.
For more information about how to use the button and joystick axes, see documentation on Conventional game input.
您可以将触觉事件发送到 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);
}
}
}