XR Hand Device
The XRHandSubsystem exposes the XRHandDevice input device within the Unity Input System, which is a type of TrackedDevice. This is automatically done when the subsystem is running, such as when enabling the Hand Tracking Subsystem OpenXR feature.
Configuration
The runtime value of XRHandSubsystem.handSubsystemConfiguration drastically alters the meaning of the <XRHandDevice> input device controls, essentially switching it between two entirely different input devices. You can set the configuration on the XRHandSubsystem to choose between the XRHandSubsystemConfiguration.xrHandDevicePoseSource options. Refer to Access hand data: Get the XRHandSubsystem instance to learn how to get the XRHandSubsystem to change the configuration.
A simple way to change from the default Legacy mode to Common Gestures mode is to add the following component to a GameObject:
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.XR.Hands;
using UnityEngine.XR.Hands.Configuration;
/// <summary>
/// A component that automatically sets the hand subsystem configuration
/// to use the Common Gestures mode instead of Legacy.
/// </summary>
class UpdateHandsConfigurationSample : MonoBehaviour
{
List<XRHandSubsystem> m_HandSubsystems;
void Start()
{
if (!TryGetHandSubsystem(out var handSubsystem))
{
Debug.LogWarning("Hand Tracking Subsystem not found or not running," +
" can't update its config.", this);
return;
}
var config = handSubsystem.handSubsystemConfiguration;
config.xrHandDevicePoseSource = XRHandDevicePoseSource.CommonGestures;
handSubsystem.UpdateHandsConfiguration(config);
}
// Gets the first hand subsystem. If there are multiple,
// returns the first running subsystem.
bool TryGetHandSubsystem(out XRHandSubsystem handSubsystem)
{
m_HandSubsystems ??= new List<XRHandSubsystem>();
SubsystemManager.GetSubsystems(m_HandSubsystems);
if (m_HandSubsystems.Count == 0)
{
handSubsystem = default;
return false;
}
if (m_HandSubsystems.Count > 1)
{
for (var i = 0; i < m_HandSubsystems.Count; ++i)
{
handSubsystem = m_HandSubsystems[i];
if (handSubsystem.running)
return true;
}
}
handSubsystem = m_HandSubsystems[0];
return true;
}
}
The Common Gestures mode is the recommended path to use for your OpenXR project. The Legacy mode is default to avoid breaking existing projects which already made use of the <XRHandDevice> and the locations of each of the poses, and for maximum compatibility in projects which are not targeting OpenXR which may not support common gestures.
Note
When using Common Gestures mode on OpenXR, you must also enable the Hand Interaction Profile. Refer to Common Gestures for more details.
XRHandSubsystemProvider implementers
For advanced users or for platform providers, the XRHandSubsystemDescriptor contains properties which indicate whether the provider can supply each of the Poses or Gesture values. The XRHandSubsystemProvider.canSurfaceCommonPoseData must also be implemented to indicate that the provider can supply data for populating XRCommonHandGestures.
Available controls
The following tables outline the mapping between the data sources and Unity's control paths on the input device, along with any applicable OpenXR path for each value.
To specify a particular hand, you can add {LeftHand} or {RightHand} after the <XRHandDevice> in the binding, such as <XRHandDevice>{LeftHand}/devicePosition.
Some controls defined in the <XRHandDevice> are not supplied a value depending on the configuration mode, as outlined in Configuration.
Note
The set of "device" pose bindings differs between configuration modes. Those four binding paths (devicePosition, deviceRotation, isTracked, and trackingState) will either map to the Wrist pose or the Grip pose.
Legacy (Default)
The following tables apply when the configuration mode is set to XRHandDevicePoseSource.LegacyJointRecognition:
Poses
These poses represent the joint poses from a hand, specifically XRHandJointID.Wrist, XRHandJointID.Palm, XRHandJointID.IndexTip, and XRHandJointID.ThumbTip. Refer to Joint nomenclature for a visual representation of each joint.
You can bind to these poses with input actions to use in your components, such as Tracked Pose Driver, instead of querying them directly from an XRHand. Refer to Access hand data: Get joint data to learn how to get hand joint poses through scripting API instead.
| Data | Binding Path | Type |
|---|---|---|
| Wrist Position | <XRHandDevice>/wristPosition<XRHandDevice>/devicePosition(or <TrackedDevice>/devicePosition) |
Vector3 |
| Wrist Rotation | <XRHandDevice>/wristRotation<XRHandDevice>/deviceRotation(or <TrackedDevice>/deviceRotation) |
Quaternion |
| Wrist Is Tracked | <XRHandDevice>/wristIsTracked<XRHandDevice>/isTracked(or <TrackedDevice>/isTracked) |
Boolean |
| Wrist Tracking State | <XRHandDevice>/wristTrackingState<XRHandDevice>/trackingState(or <TrackedDevice>/trackingState) |
Integer (flags enum) |
| Palm Position | <XRHandDevice>/gripPosition |
Vector3 |
| Palm Rotation | <XRHandDevice>/gripRotation |
Quaternion |
| Palm Is Tracked | <XRHandDevice>/gripIsTracked |
Boolean |
| Palm Tracking State | <XRHandDevice>/gripTrackingState |
Integer (flags enum) |
| Index Tip Position | <XRHandDevice>/pokePosition |
Vector3 |
| Index Tip Rotation | <XRHandDevice>/pokeRotation |
Quaternion |
| Index Tip Is Tracked | <XRHandDevice>/pokeIsTracked |
Boolean |
| Index Tip Tracking State | <XRHandDevice>/pokeTrackingState |
Integer (flags enum) |
| Thumb Tip Position | <XRHandDevice>/pinchPosition |
Vector3 |
| Thumb Tip Rotation | <XRHandDevice>/pinchRotation |
Quaternion |
| Thumb Tip Is Tracked | <XRHandDevice>/pinchIsTracked |
Boolean |
| Thumb Tip Tracking State | <XRHandDevice>/pinchTrackingState |
Integer (flags enum) |
Note
Legacy mode does not supply an Aim Pose. Those four binding paths (aimPosition, aimRotation, aimIsTracked, and aimTrackingState) will always remain default values.
Note
Legacy mode does not supply any gesture values. Those nine binding paths (graspValue, graspFirm, etc.) will always remain default values.
Common Gestures
For this input device to work on OpenXR, you must also enable the Hand Tracking Subsystem OpenXR feature and enable the Hand Interaction Profile within the Edit > Project Settings > XR Plug-in Management > OpenXR window. The OpenXR Plugin package must be at version 1.8.1 or newer. You must also enable this mode in the XRHandSubsystem configuration, as outlined in Configuration.
The following tables apply when the configuration mode is set to XRHandDevicePoseSource.CommonGestures:
Poses
These poses represent the poses from a hand interaction profile, specifically Grip pose, Poke pose, Pinch pose, and Aim pose, and the wrist joint pose.
You can bind to these poses with input actions to use in your components like Tracked Pose Driver instead of the HandInteraction poses to support hand playback overriding the values or for supporting simulation.
| Data | Binding Path(s) | Type | OpenXR Path |
|---|---|---|---|
| Wrist Position | <XRHandDevice>/wristPosition |
Vector3 | XR_HAND_JOINT_WRIST_EXT |
| Wrist Rotation | <XRHandDevice>/wristRotation |
Quaternion | XR_HAND_JOINT_WRIST_EXT |
| Wrist Is Tracked | <XRHandDevice>/wristIsTracked |
Boolean | XR_HAND_JOINT_WRIST_EXT |
| Wrist Tracking State | <XRHandDevice>/wristTrackingState |
Integer (flags enum) | XR_HAND_JOINT_WRIST_EXT |
| Grip Position | <XRHandDevice>/gripPosition<XRHandDevice>/devicePosition(or <TrackedDevice>/devicePosition) |
Vector3 | /input/grip/pose |
| Grip Rotation | <XRHandDevice>/gripRotation<XRHandDevice>/deviceRotation(or <TrackedDevice>/deviceRotation) |
Quaternion | /input/grip/pose |
| Grip Is Tracked | <XRHandDevice>/gripIsTracked<XRHandDevice>/isTracked(or <TrackedDevice>/isTracked) |
Boolean | /input/grip/pose |
| Grip Tracking State | <XRHandDevice>/gripTrackingState<XRHandDevice>/trackingState(or <TrackedDevice>/trackingState) |
Integer (flags enum) | /input/grip/pose |
| Poke Position | <XRHandDevice>/pokePosition |
Vector3 | /input/poke_ext/pose |
| Poke Rotation | <XRHandDevice>/pokeRotation |
Quaternion | /input/poke_ext/pose |
| Poke Is Tracked | <XRHandDevice>/pokeIsTracked |
Boolean | /input/poke_ext/pose |
| Poke Tracking State | <XRHandDevice>/pokeTrackingState |
Integer (flags enum) | /input/poke_ext/pose |
| Pinch Position | <XRHandDevice>/pinchPosition |
Vector3 | /input/pinch_ext/pose |
| Pinch Rotation | <XRHandDevice>/pinchRotation |
Quaternion | /input/pinch_ext/pose |
| Pinch Is Tracked | <XRHandDevice>/pinchIsTracked |
Boolean | /input/pinch_ext/pose |
| Pinch Tracking State | <XRHandDevice>/pinchTrackingState |
Integer (flags enum) | /input/pinch_ext/pose |
| Aim Position | <XRHandDevice>/aimPosition |
Vector3 | /input/aim/pose |
| Aim Rotation | <XRHandDevice>/aimRotation |
Quaternion | /input/aim/pose |
| Aim Is Tracked | <XRHandDevice>/aimIsTracked |
Boolean | /input/aim/pose |
| Aim Tracking State | <XRHandDevice>/aimTrackingState |
Integer (flags enum) | /input/aim/pose |
Gesture values
These values represent three groups of action inputs from a hand interaction profile, specifically Grasp action, Pinch action, and Aim activate action.
| Data | Binding Path | Type | OpenXR Path |
|---|---|---|---|
| The extent to which a user is making a fist | <XRHandDevice>/graspValue |
Float 0 to 1 |
/input/grasp_ext/value |
| Is the user making a fist | <XRHandDevice>/graspFirm |
Boolean | /input/grasp_ext/value |
| Precondition to making a fist | <XRHandDevice>/graspReady |
Boolean | /input/grasp_ext/ready_ext |
| The extent to which a user is pinching | <XRHandDevice>/pinchValue |
Float 0 to 1 |
/input/pinch_ext/value |
| Is the user pinching | <XRHandDevice>/pinchTouched |
Boolean | /input/pinch_ext/value |
| Precondition to making a pinch | <XRHandDevice>/pinchReady |
Boolean | /input/pinch_ext/ready_ext |
| The extent to which a user is aim pinching | <XRHandDevice>/aimActivateValue |
Float 0 to 1 |
/input/aim_activate_ext/value |
| Is the user aim pinching | <XRHandDevice>/aimActivated |
Boolean | /input/aim_activate_ext/value |
| Precondition to making an aim pinch | <XRHandDevice>/aimActivateReady |
Boolean | /input/aim_activate_ext/ready_ext |
Note
These values are not available when using Legacy mode. Those nine binding paths (graspValue, graspFirm, etc.) will always remain default values.