XR SDK 输入子系统是用于报告按钮、轴和跟踪设备信息的接口。这是将用户控制的数据输入到 Unity 引擎的各种输入端点中的核心子系统。Unity 会将输入信息报告给 InputDevices 和输入系统,具体取决于可用信息的类型。
要创建可正常工作的基本 XR 输入提供程序,请执行以下步骤:
本指南用到了以下术语:
大多数输入子系统 API 都依赖于设备。设备是通过您选择的唯一 ID 引用的输入功能容器。这可以是具体的内容(例如游戏手柄或头盔),也可以表示抽象对象(例如检测到的手部骨架)。设备具有固定数量的功能,您在设备连接期间无法更改这些功能。
输入功能是您可以从中获取传感器或用户修改数据的任何内容。这可以是按钮、位置跟踪元素或电池寿命。它们分组为各种类型的数据,通过 UnityXRInputFeatureType 进行标识。下面是可以在输入设备上采用的当前受支持数据类型:
UnityXRInputFeatureType | Data Type |
---|---|
kUnityXRInputFeatureTypeCustom | char[](最多 1024 个元素) |
kUnityXRInputFeatureTypeBinary | bool |
kUnityXRInputFeatureTypeDiscreteStates | unsigned int |
kUnityXRInputFeatureTypeAxis1D | 浮点精度 |
kUnityXRInputFeatureTypeAxis2D | UnityXRVector2 |
kUnityXRInputFeatureTypeAxis3D | UnityXRVector3 |
kUnityXRInputFeatureTypeRotation | UnityXRVector4 |
kUnityXRInputFeatureTypeHand | UnityXRHand |
kUnityXRInputFeatureTypeBone | UnityXRBone |
kUnityXRInputFeatureTypeEyes | UnityXREyes |
用途为功能提供上下文。它标识开发者应如何使用功能。例如,功能可以是一个 2D 轴,但用途会告知开发者它是一个触摸板。用途还可以告知开发者,某个一维轴功能在报告电池寿命。您可以创建自己的用途,不过需要尽可能多地使用 Unity 开发的用途,因为它们为开发者启用了跨平台实用程序。有关所有人员都可用的常见用途列表,请参阅下面的功能用途部分。
UnityXRInternalInputDeviceId 可标识所有设备。可将这些标识符视为 Unity 和提供程序进行共享以引用特定设备的唯一 ID。您可以定义哪些 Id 映射到哪些设备,唯一的约束是不能对同时连接的两个设备使用相同 Id。将特定 Id 报告为正在连接时,Unity 会请求有关设备的功能以及使用该 Id 的该设备当前状态的信息,并使用该 Id 发送特定于设备的事件。
IUnityXRInputInterface 中的两个 API 可处理设备连接和断开连接:
这会报告一个新设备。提供程序提供的 UnityXRInternalInputDeviceId
可以是任何值,只要它表示内部唯一的设备并且没有两个设备与来自相同提供程序的相同 Id 连接。设备只能在输入提供程序生命周期的开始与停止事件之间连接。在调用 IUnityXRInputProvider.Start 时任何已连接的设备在该回调期间应进行报告。
一旦设备报告为已连接,Unity 便会在下一个输入更新循环中使用提供的 UnityXRInternalInputDeviceId 调用 IUnityXRInputProvider.FillDeviceDefinition,以便获取有关该设备的特定信息。
这会报告输入设备不再可用。仅在已将输入设备报告为已连接后,才能将它报告为已断开连接。收到 IUnityXRInputProvider.Stop 时,必须将当前连接的所有输入设备都报告为断开连接。
以上两个调用都是线程安全的,可以随时调用。
设备定义描述设备可以向 Unity 报告的功能。功能包括设备标识信息,例如设备名称、角色、制造商和序列号。设备定义还包含所有可用输入功能的索引列表。
当设备报告为已连接时,Unity 会通过 IUnityXRInputProvider.FillDeviceDefinition 调用提供程序。UnityXRInputDeviceDefinition 参数充当句柄,可以传递到 IUnityXRInputInterface 上任何以 DeviceDefinition 为前缀的方法中。这些方法如下所示:
开发者使用设备上的一些数据来标识特定设备或新连接设备的常规功能。
这使提供程序可以设置设备名称。名称必须清晰、简洁,并且可被大众市场消费者所识别。这不应包括制造商的名称。此名称可通过 UnityEngine.XR.InputDevice.name 供开发者使用,在输入系统中用作 InputDevice.product。请勿将此留空。
这使提供程序可以指定连接的设备类型。UnityXRInputDeviceCharacteristics 是一系列有助于定义设备功能的标志。这些可更改 InputDevice 的输入系统用途。
这使提供程序可以设置设备的制造商。制造商必须清晰、简洁,并且可被大众市场消费者所识别。此字符串可通过 UnityEngine.XR.InputDevice.manufacturer 供开发者使用,在输入系统中用作 InputDevice.manufacturer。请勿将此留空。
这使提供程序可以设置设备的序列号。此字符串可通过 UnityEngine.XR.InputDevice.serialNumber 供开发者使用,在输入系统中用作 InputDevice.serialNumber。这必须是此特定设备的唯一标识符,否则您应该将它留空。
您可以通过以下 API 调用将输入功能添加到设备定义中。
这会添加所设置类型的功能(kUnityXRInputFeatureTypeCustom 除外),并返回该新功能的 UnityXRInputFeatureIndex。
这会添加 kUnityXRInputFeatureTypeCustom 功能。这些是可变缓冲区,最多 1024 字节。您可以使用这些缓冲区创建 Unity 未知的自定义类型,需要显式大小。此方法返回该新功能的 UnityXRInputFeatureIndex。
这会添加一个功能,而且还包括一个功能用途。此方法是 helper 方法,将 DeviceDefinition_AddFeature 和 DeviceDefinition_AddUsageAtIndex 相结合,并返回该新功能的 UnityXRInputFeatureIndex。
这会向现有功能添加功能用途。它从添加功能的方法之一获取 UnityXRInputFeatureIndex。您可以向单一功能添加所需数量的用途。
注意:返回的 UnityXRInputFeatureIndex 值全部采用添加它们的先后顺序。
设备状态是包含设备当前状态的数据结构。UnityXRInputDeviceState 的结构通过 UnityXRInputDeviceDefinition 进行描述。
注意:包含在设备状态中的功能使用 UnityXRInputFeatureIndex(在设备定义中声明该功能时报告)进行访问。
定义声明后,Unity 会通过 IUnityXRInputProvider.UpdateDeviceState 在每一帧请求设备状态两次。UnityXRInputUpdateType 参数指定 Unity 所需的更新类型:
UnityXRInputDeviceState 参数充当句柄,可以传递到 IUnityXRInputInterface 上任何以 DeviceState 为前缀的方法中。
这会在提供的 UnityXRInputFeatureIndex 处设置类型为 kUnityXRInputFeatureTypeCustom 的功能。设置自定义值功能时,提供程序必须始终使用所声明功能的完整大小设置值;没有部分值设置。此外,您必须在提交期间向 Unity 声明所有自定义功能,并详细说明它们包含的数据类型以及使用其他各个功能类型无法存在的原因。
这会设置类型为 kUnityXRInputFeatureTypeBinary 的布尔值(开/关)功能。此功能的默认、静止或未使用状态必须为 false。
这会设置 32 位无符号整数值。这还可用于表示枚举。默认、未使用值必须是 0,如果用于枚举,则 0 必须表示 null、无、未设置或无效值。
这会设置 32 位浮点值。默认、未使用值必须是 0.0。
这会设置类型为 UnityXRVector2 的值。UnityXRVector2 结构是一对 (X, Y) 32 位浮点数。默认、未使用值必须是 (0.0, 0.0)。
这会设置类型为 UnityXRVector3 的值。UnityXRVector2 结构是一对 (X, Y, Z) 32 位浮点数。默认、未使用值必须是 (0.0, 0.0, 0.0)。
这会设置类型为 UnityXRVector4 的值(格式设置为四元数)。默认、未使用值必须是 (0, 0, 0, 1)。请参阅有关四元数的文档以了解更多信息。
这会设置类型为 UnityXRBone 的值。
这会设置类型为 UnityXRHand 的值。
这会设置类型为 UnityXREyes 的值。
注意:传入的 UnityXRInputFeatureUsageIndex 是在填写 UnityXRInputFeatureDefinition 期间,向设备定义添加该单个功能时返回的相同值。
以下高级类型是用于包含来自多个数据源的数据的特殊功能类型。
这些表示空间中分层姿势的一个骨骼或一个元素。position 成员表示以米为单位的三维位置(使用 Y 向上的左手坐标)。rotation 成员表示该骨骼在空间中的方向,通过以弧度为单位的归一化四元数进行表示。parentBoneIndex 是 UnityXRInputFeatureIndex,必须指向在层级视图中向上的 UnityXRBone 或 kUnityInvalidXRInputFeatureIndex(如果它是此骨架的根)。
这些表示骨骼的手状结构。它们将骨骼层级视图组织成手指和根,以便于遍历。rootBoneIndex 必须始终指向类型为 kUnityXRInputFeatureTypeBone 的有效索引(表示手的手掌或中心)。fingerBoneIndices 必须堆叠,以便第一个维度或数组映射到各个手指(跟在 UnityXRHandFinger 枚举值后),数组的第二个维度是从根到尖端的各个手指骨骼。
这些表示一双眼睛、其凝视点和当前眨眼数据。The leftEyePose 和 rightEyePose 属于类型 UnityXRPose,其中 position 成员表示以米为单位的三维位置(使用 Y 向上的左手坐标),rotation 成员表示以弧度为单位的归一化四元数。fixationPoint 表示左右眼会聚的位置,也是以米为单位的三维位置(使用 Y 向上的左手坐标)。leftOpenAmount 和 rightOpenAmount 表示眼睛的睁开程度,其中 0 为闭合,1 为完全睁开。它们不能超过 [0,1] 范围。
在设备状态和定义更新之外,Unity 期望您对各种事件进行反应和响应。
这适用于私有事件,特定于此提供程序。eventType 参数是用于标识有效负载的自定义代码。如果提供程序不理解该事件,则它必须返回 kUnitySubsystemErrorCodeFailure。
跟踪原点是指真实世界空间中与被跟踪设备相关的点。它实际上是真实世界空间中设备报告位置 (0, 0, 0) 的点。Unity 支持多种跟踪原点模式,提供程序可以选择加入它们支持的模式。追踪原点模式在 UnityXRInputTrackingOriginModeFlags 下列出。
kUnityXRInputTrackingOriginModeDevice 将原点放置在主设备在启动时的位置和偏航处,或是最后一个 UnityXRInputProvider.HandleRecenter 事件的位置处。
kUnityXRInputTrackingOriginModeFloor 将原点放置在地板上的某处。地板上的位置取决于提供程序,只要让开发者可以了解地板与各种设备之间的高度差即可。
kUnityXRInputTrackingOriginModeTrackingReference 将原点放置在具有 kUnityXRInputDeviceCharacteristicsTrackingReference 特征的特定 InputDevice 位置处。
最后,kUnityXRInputTrackingOriginModeUnknown 是一个错误案例,不应由提供程序返回。
当跟踪原点模式设置为 kUnityXRInputTrackingOriginModeDevice 时,对重新居中的调用应将主设备的当前位置设置为新原点。
这是来自 Unity 的请求,用于获取提供程序所使用的当前跟踪原点模式。提供程序应设置 trackingOriginMode 参数并返回 kUnitySubsystemErrorCodeSuccess。返回的参数只能是单一标志值。
这是针对支持的跟踪原点模式的请求。提供程序应设置 supportedTrackingOriginModes 参数并返回 kUnitySubsystemErrorCodeSuccess。返回的参数应该是 UnityXRInputProvider.HandleSetTrackingOriginMode 能够支持的所有 UnityXRInputTrackingOriginModeFlags 的累积列表。
这是来自 Unity 的请求,用于更改当前跟踪原点模式。trackingOriginMode 参数是所需的跟踪原点模式。如果原点能够更改,则提供程序应返回 kUnitySubsystemErrorCodeSuccess,否则返回 kUnitySubsystemErrorCodeSuccess。如果跟踪原点模式已是所需模式,则提供程序不应执行任何操作,并返回 kUnitySubsystemErrorCodeSuccess。
这是提供程序可以发送的事件,用于向 Unity 通知跟踪原点的位置已更改。在 UnityXRInputProvider.HandleSetTrackingOriginMode 成功并移动原点时,必须调用此事件。如果提供程序由于整体跟踪信息更改而不得不更改原点,也可以调用此事件。
这是提供程序可以发送的事件,用于向 Unity 通知有可用的跟踪边界,或跟踪边界已更改。如果存在边界,并且跟踪原点已更改,从而移动了边界的相对位置,则必须调用此事件。这可以使用 null 和 0 个点进行调用,以便移除现有跟踪边界。
这是对提供程序填写的给定设备触觉功能的请求。将 supportsImpulse 设置为 true 可为 UnityXRInputProvider.HandleHapticImpulse 启用事件。将 supportsBuffer 设置为 true 可为 UnityXRInputProvider.HandleHapticBuffer 启用事件。
注意:功能结构使提供程序可以设置通道和请求的数量以启动和停止包含通道索引的触觉。这使提供程序可以在单一设备内具有多个可以独立运行的电机。第一个通道应是要使用的最常用电机,但后续顺序取决于提供程序。
这是要求设备在设置的持续时间内以设置的幅度运行的请求。Unity 会为此请求填写数据。缓冲区参数属于类型 UnityXRHapticImpulse。
这是要求设备在提供设置的缓冲区的情况下,运行某个模式的请求。Unity 会为此请求填写数据。缓冲区参数属于类型 UnityXRHapticUpdate。
bufferSize 从不会多于从 UnityXRInputProvider.QueryHapticCapabilities 事件返回的 UnityXRHapticCapabilities.bufferMaxSize。
这是来自 Unity,要求停止任何触觉效果的请求。这应该对提供的 UnityXRInternalInputDeviceId 停止脉冲或缓冲触觉效果。
功能用途是简单的字符串标签,可提供有关功能的上下文并帮助 Unity 开发者以通用方式访问设备。您可以将设备声明为具有触发器、设备位置、菜单按钮或输入功能的其他共享概念。开发者可以访问这些内容并与设备进行交互,而无需确切知道它是什么。这些也用于将输入数据路由到 UnityEngine.Input 和 UnityEngine.XR.InputTracking 并决定索引。单一输入功能可以具有多种用途,但您声明的每个输入功能必须至少分配有一个用途。如果您选择不使用 Unity 默认提供的用途,则必须在提交进行认证时让 Unity 了解您使用了什么,并准备好根据 Unity 的反馈更新用途字符串。
所有世界空间用途都以米、m/s、m/s2 或弧度(适当时)为单位。空间方向应为左手,z 向前,y 向上。空间原点应是设备在连接时的位置。此空间是您自己的,不会直接映射到 Unity 世界空间。
下面是 Unity 中提供的常见用途:
以下功能适用于在真实世界中提供跟踪的设备。它们对于识别当前跟踪能力十分有用:
kUnityXRInputFeatureUsageIsTracked 是一个布尔值,指定设备当前是否在正确跟踪。True 表示完全跟踪,false 表示部分跟踪或未跟踪。
kUnityXRInputFeatureUsageTrackingState 是离散状态功能,由 UnityXRInputTrackingStateFlags 枚举提供支持,用于标识当前可用和更新的实际跟踪功能。此值绝不能高于 kUnityXRInputTrackingStateAll。
其余跟踪功能会中继有关特定“节点”(例如设备、左眼或彩色摄像机)的个别数据。根据数据类型,它们分为六个一组。这些内容必须与 kUnityXRInputFeatureUsageTrackingState 中的当前值结合起来更新。也就是说,如果跟踪状态显示位置可用,则所有位置用途都必须正确更新。
用途前缀如下所示:
前缀 | 描述 |
---|---|
kUnityXRInputFeatureUsageDevice | 输入设备的通用化位置 |
kUnityXRInputFeatureUsageCenterEye | 所有眼睛渲染位置的集中式平均值 |
kUnityXRInputFeatureUsageLeftEye | 左眼的渲染位置 |
kUnityXRInputFeatureUsageRightEye | 右眼的渲染位置 |
kUnityXRInputFeatureUsageColorCamera | 任何传入摄像机视频源的位置 |
这些前缀各自都具有一系列可用后缀,表示各种跟踪属性,如下所示: * Position * Rotation * Velocity * AngularVelocity * Acceleration * AngularAcceleration
定义中不包含这些前缀意味着它们从不可用。包含它们,但通过 kUnityXRInputFeatureUsageTrackingState 功能将它们标记为不可用表示功能当前不可用,但以后可能可用。
这些包含通用化设备信息,而不是用户启动的控件。它们是用户无法直接控制的设备功能。
kUnityXRInputFeatureUsageBatteryLevel 是表示设备当前电池电量的 1D 轴功能,其中 0 表示没有电池电量,1 表示充满电。它必须始终在 [0–1] 范围内。
kUnityXRInputFeatureUsageUserPresence 是布尔值,在用户当前佩戴头盔时返回 true。
这些是二维模拟浮点值,例如触控板和游戏杆。这些控件通常使用拇指进行移动。它们提供 X 和 Y,应始终在 ([–1,1],[–1,1]) 范围内。
kUnityXRInputFeatureUsagePrimary2DAxis 是表示触控板或游戏杆的 2D 轴功能。0,0 是空闲位置,Y 正值表示远离控制器用户。
kUnityXRInputFeatureUsageSecondary2DAxis 是表示第二个游戏杆或触控板的 2D 轴(与 kUnityXRInputFeatureUsagePrimary2DAxis 一起使用)。0,0 是空闲位置,Y 正值表示远离控制器用户。
这些全部是一维模拟浮点值。此处标识了可以“半按”的按钮、触发器和其他控件。
kUnityXRInputFeatureUsageTrigger 是映射到索引启动的触发器的 1D 轴。这必须始终在 [0,1] 范围内,其中 0 是打开,1 是完全挤压。如果实现了此功能,则设备还必须实现 kUnityXRInputFeatureUsageTriggerButton。
kUnityXRInputFeatureUsageGrip 是映射到用手挤压激活握把的 1D 轴。这必须始终在 [0,1] 范围内,其中 0 是打开,1 是完全挤压。如果实现了此功能,则设备还必须实现 kUnityXRInputFeatureUsageGripButton。
这些是一维数字值。它们可以启动或不启动,但没有进一步的粒度。
kUnityXRInputFeatureUsagePrimaryButton 是表示控制器上主按钮的二进制功能。这通常用作菜单中的接受或高级按钮。如果启动了此功能,则 kUnityXRInputFeatureUsagePrimaryTouch 也必须启动(如果存在)。
kUnityXRInputFeatureUsagePrimaryTouch 是表示控制器上主按钮的触摸状态的二进制功能。如果实现了此功能,则设备还必须实现 kUnityXRInputFeatureUsagePrimaryButton。
kUnityXRInputFeatureUsageSecondaryButton 是表示控制器上辅助按钮的二进制功能。这通常用作后退或备选按钮。如果启动了此功能,则 kUnityXRInputFeatureUsageSecondaryTouch 也必须启动(如果存在)。
kUnityXRInputFeatureUsageSecondaryTouch 是表示控制器上辅助按钮的触摸状态的二进制功能。如果实现了此功能,则设备还必须实现 kUnityXRInputFeatureUsageSecondaryButton。
kUnityXRInputFeatureUsageGripButton 是表示是否触发手动挤压的二进制功能。如果实现了此功能,则设备还必须实现 kUnityXRInputFeatureUsageGrip。
kUnityXRInputFeatureUsageTriggerButton 是表示是否触发手动挤压的布尔值功能。如果实现了此功能,则设备还必须实现 kUnityXRInputFeatureUsageTrigger。
kUnityXRInputFeatureUsageMenuButton 是表示非游戏暂停或菜单按钮的二进制功能。这对于用户而言通常不容易实现。
kUnityXRInputFeatureUsagePrimary2DAxisClick 是表示按下或单击 kUnityXRInputFeatureUsagePrimary2DAxis 2D 轴的二进制功能。如果实现了此功能,则设备还必须实现 kUnityXRInputFeatureUsagePrimary2DAxis。如果启动了此功能,则 kUnityXRInputFeatureUsagePrimary2DAxisTouch 也必须启动(如果存在)。
kUnityXRInputFeatureUsagePrimary2DAxisTouch 是表示轻触 kUnityXRInputFeatureUsagePrimary2DAxis 2D 轴的二进制功能。如果实现了此功能,则设备还必须实现 kUnityXRInputFeatureUsagePrimary2DAxis。
kUnityXRInputFeatureUsageSecondary2DAxisClick 是表示按下或单击 kUnityXRInputFeatureUsageSecondary2DAxis 2D 轴的二进制功能。如果实现了此功能,则设备还必须实现 kUnityXRInputFeatureUsageSecondary2DAxis。如果启动了此功能,则 kUnityXRInputFeatureUsageSecondary2DAxisTouch 也必须启动(如果存在)。
kUnityXRInputFeatureUsageSecondary2DAxisTouch 是表示轻触 kUnityXRInputFeatureUsageSecondary2DAxis 2D 轴的二进制功能。如果实现了此功能,则设备还必须实现 kUnityXRInputFeatureUsageSecondary2DAxis。
这些表示各种传感器类型。它们用作搜索手部和眼睛类型数据的简便方法。
kUnityXRInputFeatureUsageHandData 是表示手部跟踪数据的 UnityXRHands 功能。 kUnityXRInputFeatureUsageEyesData 是表示眼睛跟踪数据的 UnityXREyes 功能。
要让用户可以在使用新输入系统时绑定到并使用代码访问您的设备属性,您需要提供设备布局描述作为输入提供程序的一部分。
您应该为您在输入提供程序中定义的每种设备类型提供设备布局。 如果您没有为注册的设备提供显式设备布局,则用户将无法使用新输入系统 UI 绑定到设备上的功能。输入系统仍会接收设备数据,用户能够手动创建到新设备功能的绑定。
有关输入系统设备布局的更多信息,请参阅输入系统文档。下面提供了一个便于实现的示例。
此示例为新的 Example VR Controller 提供布局。
Example VR Controller 的 XRSDK 布局定义描述为具有:
- 一个附加 kUnityXRInputFeatureTypeBinary
,名为 exampleButton
- 一个 kUnityXRInputFeatureTypeAxis3D
值,名为 examplePosition
- 一个 kUnityXRInputFeatureTypeRotation
值,名为 exampleRotation
要使用户可以使用新输入系统绑定和控制设备,您必须为新的 Example VR Controller 提供设备布局。
首先,您需要提供 InputControlLayout
属性,还需要提供用于新输入系统 UI 的显式名称。使用 [Preseve]
属性确保这些元素不会从编译步骤中剥离。
[Preserve]
[InputControlLayout(displayName = "Example VR Controller")]
public class ExampleVRController : XRController
{
接下来,为 XRSDK 布局中定义的各种元素提供 InputControl
映射。
再次使用 [Preserve]
确保这些元素不会从构建中剥离。aliases
关键字的使用使新输入系统可以根据提供的别名执行常见匹配。
[Preserve]
[InputControl(aliases = new[] { "PrimaryButton" })]
public ButtonControl exampleButton { get; private set; }
[Preserve]
[InputControl]
public Vector3Control examplePosition { get; private set; }
[Preserve]
[InputControl]
public QuaternionControl exampleRotation { get; private set; }
最后,提供 FinishSetup
方法的实现,该方法将控件映射绑定到控件的实例。确保还调用了基类的 FinishSetup
,否则基控件不会绑定。
protected override void FinishSetup()
{
base.FinishSetup();
exampleButton = GetChildControl<ButtonControl>("exampleButton");
examplePosition = GetChildControl<Vector3Control>("examplePosition");
exampleRotation = GetChildControl<QuaternionControl>("exampleRotation");
}
需要执行的最后一步是在为这些设备启动 XRSDK 加载程序时向新输入系统注册新设备布局。以下代码是此实现的示例。
您必须使用与产品正确匹配的字符串或 XRSDK 输入提供程序在连接设备时提供的其他字符串填写 REGEX THAT MATCHES YOUR DEVICE
部分。
public override bool Initialize()
{
# if UNITY_INPUT_SYSTEM
InputLayoutLoader.RegisterInputLayouts();
# endif
# if UNITY_INPUT_SYSTEM
# if UNITY_EDITOR
[InitializeOnLoad]
# endif
static class InputLayoutLoader
{
static InputLayoutLoader()
{
RegisterInputLayouts();
}
public static void RegisterInputLayouts()
{
UnityEngine.InputSystem.InputSystem.RegisterLayout<ExampleVRController>(
matches: new InputDeviceMatcher()
.WithInterface(XRUtilities.InterfaceMatchAnyVersion)
.WithProduct("<REGEX THAT MATCHES YOUR DEVICE>")
);