SDK Architecture & API
How the HEXR Unity SDK is put together, how the glove connects, how interactions become haptics, and how to drive the glove from your own code.
This page describes com.microtube.hexr version 0.9.3.
Package structure
The package is a stack. Your scene talks to the top; the glove sits at the bottom. The two backend layers only compile when their XR SDK is installed, and the core never depends on them.
| Assembly | Namespace | Holds | Compiles when |
|---|---|---|---|
HaptGlove.Runtime | HaptGlove | HaptGloveHandler, Bluetooth transports, Haptics command builders | Always |
HexR.Runtime | HexR | Manager, hand tracking, Pressure Controllers, touch points, scene components, floating menu | Always |
HexR.Runtime.MetaOVR | HexR.MetaOVR | Meta grab and poke detection, interactable events, passthrough | Meta Interaction SDK installed |
HexR.Runtime.OpenXR | HexR.OpenXR | XR Interaction Toolkit integration (OpenXR headsets, including PICO) | XR Interaction Toolkit installed |
HexR.Editor | HexR | HexR menu, Auto Setup, validator, HexR Tools window | Editor only |
Backends register themselves with the core when they load. The collider visualizer, HexRUsable and the floating menu stay in the global namespace so older scenes keep working.
The HEXR scene
HexR → Create HexR Rig adds the first two. Your XR SDK provides the third.
| Object | Components | Role |
|---|---|---|
| HexR Main | HexRManager | Owns both gloves, runs connections, re-attaches the hands after a scene change. Reach it as HexRManager.Instance |
| Left / Right Hand Physics | HaptGloveHandler, HexRTrackedHand, FingerUseTracking | One per hand: the Bluetooth link, which tracked hand it follows, how bent each finger is |
| HexR Floating Menu | HexRFloatingMenu | In-headset panel: connect gloves, show touch points, watch each finger’s haptics, switch scenes and demos. Pressed with a fingertip |
| Left / Right Pressure Controller | PressureTrackerMain | The haptics controller for that hand |
The touch points find them by name when the scene starts. A hand without one produces no haptics. HexR → Add Pressure Controller adds them.
The tracked hand
HexRTrackedHand.handRoot points at your SDK’s hand. HEXR finds the joints by name:
| XR SDK | Hand root | Joints |
|---|---|---|
| Meta Interaction SDK | OpenXRLeftHand / OpenXRRightHand | XRHand_IndexTip, XRHand_Palm, … |
| XR Interaction Toolkit + XR Hands | L_Wrist / R_Wrist under Left / Right Hand Interaction Visual | L_IndexTip, L_Palm, … |
Each touch point is a small trigger collider, a kinematic Rigidbody (Unity needs one for trigger events) and a HapticFingerTrigger that says which hand and finger it is. HEXR has no hand of its own: since 0.9 the touch points sit on the tracked hand on every backend. An older project with a “ghost” hand can remove it with HexR → Migration → Remove Ghost Hand.
Connecting the glove
Your app opens the Bluetooth connection itself. No pairing in system settings, no companion app. Each glove has its own HaptGloveHandler.
ConnectLeftBT() / ConnectRightBT(), or the menu’s Connect button- Only one hand scans at a time; the second glove waits for the first.
- On Android a failed attempt is retried with a growing delay for up to a minute, and a glove that drops out is reconnected. Press Connect again to cancel a retry.
| Where your app runs | Bluetooth |
|---|---|
| Android headsets — Meta Quest, PICO | Supported; the rig’s default |
| Windows — Unity Editor and 64-bit builds | Supported. In a build, set Build Platform on each HaptGloveHandler to Window. The Haptics Tester does this in the Editor |
Events
C# events on HaptGloveHandler; subscribe with +=. They are not UnityEvents, so they do not show in the Inspector.
| Event | When |
|---|---|
onBluetoothConnected(HandType) | The glove connected |
onBluetoothConnectionFailed(HandType) | An attempt failed |
onBluetoothDisconnected(HandType) | A connected glove dropped out |
onPumpAction(HandType, bool) | The air supply started or stopped |
onBatteryStateChange(BatteryState) | Full, Medium or Low |
onDeviceFound(id, name) | A scan found a device |
HexRManager.ConnectAttemptEnded | Static. A connect press finished, either way |
How haptics fire
PressureTrackerMain for that handHaptGloveHandler builds and sends the command| Trigger | Use it for | Example |
|---|---|---|
| XR interaction events | Feeling an object while it is grabbed or hovered | HexRInteractableHaptics |
| Physics contact | Feeling a surface, a liquid, a heartbeat | SpecialHaptics |
| Your code | Game events, sequences, tests | PressureTrackerMain |
On Android, commands sent close together are grouped into one Bluetooth write about every 100 ms. Response time from command to sensation is under 20 ms.
The hand-near check
A Pressure Controller sends most haptics only while IsHandNear() is true. Any one of these makes it true:
| Signal | Set by |
|---|---|
| Grabbing, or hovering to grab | Your SDK’s grab interactor — wired for you by Auto Setup and on every scene load |
| Poking, or about to | Your SDK’s poke interactor — wired the same way |
A fingertip inside a ProximityCheck volume | A ProximityCheck you add to an object that is not an SDK interactable |
| Checked | Not checked |
|---|---|
Single…Haptic, TriggerAll…, TriggerCustom…, TriggerPinchPressure | HapticFingerTrigger.TriggerFixPressure, every SpecialHaptics effect, CustomSingleHaptics / CustomSingleVibrations with byPassHandCheck: true, RemoveAllHaptics and the Remove…Haptics calls |
If your call does nothing, check IsHandNear() first, or pass byPassHandCheck: true while testing.
Channels and parameters
Six channels: Haptics.Finger.Thumb, Index, Middle, Ring, Pinky, Palm. Each plays one mode at a time. Values outside a range are clamped.
| Mode | Feels like | Parameters |
|---|---|---|
| Pressure | A steady press | intensity 0.1–1 (about 15–50 kPa); speed 0.1–1, how fast it ramps |
| Vibration | Pressure pulsing on and off | frequency 0.1–40 Hz; intensity 0.1–1. Below 5 Hz it sets pulse pressure, from 5 Hz pulse length |
| Slow pulse | A shaped, slow rhythm | frequency 0.1–2 Hz; intensity; peakRatio 0.2–0.8; speed; endIntensity (below 0.1 releases) |
| Counted pulses | A set number of pulses | frequency; intensity; pulseCount 1–1000 |
Scene components
Most projects never call the API. These components fire haptics on their own.
| Component | Use it for | Key fields |
|---|---|---|
HexRInteractableHaptics — start here | Feel your SDK’s own interactable (XRI or Meta) while grabbed or hovered. The backend is picked automatically | fireOn Select / Hover / SelectAndHover; finger toggles; strength; followGrip + minStrength; backend |
SpecialHaptics | A trigger collider that plays a preset effect when touched | TypeOfHaptics (table below), HapticPressure, VibrationsFrequencyValue, InTimer / OutTimer |
ProximityCheck | Tell the Pressure Controller a hand is near an object that is not an SDK interactable | Auto Set Up button wires both Pressure Controllers |
HexRPhysicalButton | A button pressed by a real fingertip, no SDK needed. The floating menu is built from it | onPressed; accepted fingers (index by default) |
HexRUsable | Triggers, handles, grips: fires when chosen fingers curl | UseThreshold; WhenUseTriggerEvents / WhenStopUsingTriggerEvents |
HapticFingerTrigger | The touch point itself. Read it in your own OnTriggerEnter to learn which hand and finger touched | handType, fingertype; TriggerFixPressure, TriggerVibrationPressure, RemoveHaptics |
HexRGrabbable | HEXR's own grab, picked up by the glove hands with no interaction SDK. For an object that should also grab without gloves, use HexRInteractableHaptics | PinchGrab / PalmGrab; needs a trigger Collider and a Rigidbody |
SpecialHaptics effect | Feels like |
|---|---|
CustomHaptics | Constant pressure while touched |
CustomVibrations | Vibration at a set frequency and strength |
FountainEffect | Running water |
RainDropEffect | Raindrops on the palm |
HeartBeatEffect | A beating heart, regular or irregular |
HandSqueezeEffect | Fires OnSqueezeEventTrigger when the hand closes on the zone. Needs FingerUseTracking on the rig’s hands |
C# API
Call these when you want haptics driven by your own logic.
PressureTrackerMain — on each Pressure Controller
| Method | Does |
|---|---|
CustomSingleHaptics(finger, state, targetPressure, speed, byPassHandCheck) | Pressure on one channel. state true applies, false releases; targetPressure and speed 0.1–1 |
CustomSingleVibrations(finger, state, targetPressure, frequency, byPassHandCheck) | Vibration on one channel; frequency 0.1–40 Hz |
SingleThumbHaptic(float) … SinglePalmHaptic(float) | Pressure 0.1–1 on one channel. One float, so they wire to a UnityEvent |
RemoveThumbHaptics() … RemovePalmHaptics() | Release one channel |
TriggerAllHapticsIncrease(float) / TriggerAllVibrations(frequency) | Every channel at once |
TriggerCustomHapticsIncrease(bool[6], pressure, speed) / TriggerCustomlVibrations(bool[6], intensity, frequency) | A chosen set of channels, in Thumb…Palm order |
TriggerPinchPressure(float) / RemovePinchPressure() | Thumb-and-index pinch |
RemoveAllHaptics() / RemoveAllVibrations() | Release everything |
IsHandNear() | Whether the hand-near check passes |
HexRManager — HexRManager.Instance
| Member | Does |
|---|---|
ConnectLeftBT() / ConnectRightBT() | Connect that glove. Safe to wire to a button |
leftHand / rightHand | Each glove’s HaptGloveHandler |
XRFramework | OpenXR or MetaOVR |
RebindHandsToCurrentScene() | Re-attach the touch points to this scene’s hands. Runs by itself on every scene load |
| Collider settings | Size and placement of the touch points Auto Setup adds, per skeleton |
ConnectAttemptEnded | Static event: a connect press finished |
HaptGloveHandler — one per glove, namespace HaptGlove
| Member | Does |
|---|---|
whichHand | Left or Right |
BTConnection() | Connect, or disconnect if connected. Prefer the manager’s Connect…BT(), which adds permissions and the one-at-a-time rule |
bleConnected | Connected right now? |
UseCustomDeviceName, OptionalCustomDeviceName | Look for a glove by a name you choose |
RememberDevice(id), ForgetDevice(), ConnectToDevice(id) | Which glove it goes straight to |
haptics | Builds commands: HEXRPressure, HEXRVibration, HEXRPulse. GetChannelState(finger) returns what the last command set on a channel, as a ChannelState (HapticMode, intensity, frequency) |
BTSend(byte[]) | Send a command straight to the glove. Skips the hand-near check |
GetBatteryLevel(), GetAirPressure() | Latest battery level and pressure readings |
HexRTrackedHand and FingerUseTracking — on each hand
| Member | Does |
|---|---|
HexRTrackedHand.handType, handRoot | Which hand, and the tracked hand it follows |
ResolveRawFingerJoint(finger), ResolveRawPalmJoint() | The tip or palm joint, on either skeleton |
FingerUseTracking.ThumbUse … LittleUse | 0 = fully curled, 1 = fully extended. Self-calibrating: settles once the hand has opened and closed |
FingerUseTracking.isHandOpen() | All fingers extended? |
Scene changes and editor tools
Your SDK’s hands die with their scene; the rig lives on. So after every scene load the manager re-attaches:
HapticFingerTrigger on each fingertip and the palmThen the log says [HexR] Rebound Left hand … 6 haptic trigger(s): five fingertips and the palm.
Auto Setup does the same in the Editor so it is saved with the scene. It only adds what is missing and never moves a collider you tuned. Run it from Create HexR Rig, HexR → Troubleshoot → Re-run Auto Setup, or the manager’s Auto Set Up HexR button. Validate Scene Setup then reports anything still unwired.
| Menu | Does |
|---|---|
| HexR → HexR Tools | The tools window: Haptics Tester (connect a glove in Play mode, fire any channel, watch live pressure), HexR Setup (the validator as a checklist), Project Setup (detects your backend, installs its packages) |
| HexR → Create Demo Scene | Rig, Pressure Controllers, a grabbable object and a haptic zone |
| HexR → Create HexR Rig | The rig and Pressure Controllers for your backend, then Auto Setup |
| HexR → Add Pressure Controller | The pair of Pressure Controllers |
| HexR → Add HexR Menu | The floating menu in your own scene |
| HexR → Troubleshoot | Re-run Auto Setup, Validate Scene Setup |
| HexR → Migration | One-off upgrades, such as Remove Ghost Hand |
| Floating menu → Show Colliders | Draws every touch point on the tracked hands |
| Floating menu → HAPTICS | The value last sent to each finger and the palm of each glove, as your code passed it: lit while on, dimmed after. Turn it off with showHapticsReadout |
Type index
Every public type in com.microtube.hexr, for looking a name up. Where a section above covers it, the link goes there.
Components
| Type | What it is |
|---|---|
HexRManager | The rig: both gloves, connections, re-attaching the hands. Members |
HaptGloveHandler | One glove’s Bluetooth link and command sender. Members |
PressureTrackerMain | A Pressure Controller: every haptic for one hand goes through it. Methods |
HexRTrackedHand, FingerUseTracking | Which tracked hand a glove follows, and how bent each finger is. Members |
HapticFingerTrigger | The touch point on each fingertip and the palm. Scene components |
HexRInteractableHaptics | Haptics on your SDK’s own interactable. Scene components |
HexRGrabbable | HEXR’s own grab, by the glove hands with no interaction SDK. Scene components |
SpecialHaptics, ProximityCheck, HexRPhysicalButton, HexRUsable | Preset effects, the hand-near volume, fingertip buttons, curl-to-use. Scene components |
HexRFloatingMenu | The in-headset panel. The HEXR scene |
HaptGloveCollidersVisualizer | Draws the touch points; what the menu’s Show Colliders uses. SetVisible(bool), IsShowing |
HaptGloveUI | On HexR Main: keeps the manager’s glove status text up to date |
SceneLoader | Scene transitions for the app. Sits on the same persistent object as HexRManager |
HexRGripByHand | OpenXR: gives a stock XRGrabInteractable a grip per hand, and a pinch or fist hold, without replacing it. GripMode picks the hold |
OpenXRHandNearSource, MetaOVRHandNearSource | Feed the hand-near check from the grab and poke interactors. Auto Setup adds them |
OpenXRBackendSetup, MetaOVRBackendSetup | Wire those sources onto the Pressure Controllers, in the Editor and at run time. Nothing to add yourself |
MetaOVRPassthrough, IHexRPassthrough | Passthrough on Meta, and the interface the menu uses to refresh any backend’s passthrough after a scene load |
Enums and values
| Type | Values and where it is used |
|---|---|
Haptics | Command builders, on HaptGloveHandler.haptics. Channels and parameters |
Haptics.Finger | Thumb, Index, Middle, Ring, Pinky, Palm |
Haptics.HapticMode, Haptics.ChannelState | What the last command set on a channel: Off, Pressure, Vibration or Pulse, with intensity and frequency. From GetChannelState(finger) |
HandType | Left / Right, on HaptGloveHandler and on HapticFingerTrigger |
FingerType | Which finger a HapticFingerTrigger is, in fingertype |
BatteryState | Full, Medium, Low. Events |
TargetPlatform | A glove handler’s Build Platform: Android or Windows. Connecting the glove |
FireOn | HexRInteractableHaptics.fireOn: Select, Hover, SelectAndHover |
HexRBackendChoice | Which interaction SDK drives a component: Auto or a named backend |
HexRHandSide | Which hand an interaction came from, as a backend reports it |
HeartBeat | SpecialHaptics heartbeat: Regular or Irregular |
Options, Option | HexRGrabbable.Options PinchGrab / PalmGrab; HexRGrabbable.Option gravity On / Off; HexRManager.Options is XRFramework |
FingertipCenters | Per-finger touch point offsets in the manager’s collider settings |
Backend plumbing, editor and internals
| Type | What it is |
|---|---|
IHexRInteractionBackend, IHexRInteractionBinding, IHexRInteractionSink, HexRInteractionBackends, HexRHandSideResolver | How an XR SDK plugs into HEXR. Only needed to add a backend of your own |
HexRCompat | Stand-ins for Unity APIs renamed between 2022.3 and Unity 6. See Unity version support |
HexRMenu, HexRAutoSetup, HexRSetupValidator, SetupCheck, HexRToolsWindow | Editor: the HexR menu, Auto Setup, Validate Scene Setup and the HexR Tools window. Editor tools |
HexRSettingEditorGUI, HexRInteractableHapticsEditor, HapticEffectControllerEditor, ProximityCheckEditor, FingerUseTrackingEditorSetting | Editor: the Inspectors for HexRManager, HexRInteractableHaptics, SpecialHaptics, ProximityCheck and FingerUseTracking |
BLE, BLEScan, BLEData, Characteristic, Service, DeviceUpdate, ErrorMessage, ScanStatus, Encode, Decode, FunIndex, AndroidUiThread | Internal: the Bluetooth layer under HaptGloveHandler. Not for direct use; go through the handler or a Pressure Controller |
Python
Outside Unity, HaptGlovePython builds the same commands; you write them with bleak. Setup and examples are in the Python guide.
| Method | Does |
|---|---|
hexr_pressure_multiple(fingers, states, intensities, speeds) | Pressure on several channels in one write; intensities and speeds 0.1–1 |
hexr_vibrations(finger, state, frequency, intensity, peakRatio, speed, endIntensity) | The slow pulse, 0.1–2 Hz. hexr_vibrations_multiple takes lists |
AddressDiscovery.py, CharacteristicDiscovery.py, ExampleHaptics.py | Find your glove, list what it exposes, run a ready example |
Use Haptics.Finger.Index, not "Index". A string raises AttributeError.
Example
Press the index finger while a fingertip is inside this zone. HapticFingerTrigger says which hand touched, so the command goes to the right Pressure Controller.
using UnityEngine;
using HaptGlove;
using HexR;
public class IndexPress : MonoBehaviour
{
public PressureTrackerMain leftHand; // Left Pressure Controller
public PressureTrackerMain rightHand; // Right Pressure Controller
void OnTriggerEnter(Collider other)
{
PressureTrackerMain hand = HandOf(other);
// finger, apply, intensity 0.1-1, ramp speed 0.1-1, bypass the hand-near check
if (hand != null) hand.CustomSingleHaptics(Haptics.Finger.Index, true, 0.8f, 1f, true);
}
void OnTriggerExit(Collider other)
{
PressureTrackerMain hand = HandOf(other);
if (hand != null) hand.CustomSingleHaptics(Haptics.Finger.Index, false, 0f, 1f, true);
}
PressureTrackerMain HandOf(Collider other)
{
HapticFingerTrigger finger = other.GetComponent();
if (finger == null) return null;
return finger.handType == HapticFingerTrigger.HandType.Left ? leftHand : rightHand;
}
} Where to go next
HexRInteractableHaptics on an objectIntegrating into an existing app? Start with HexRInteractableHaptics before the lower-level PressureTrackerMain or HaptGloveHandler.