SDK 架构与 API
了解 HEXR Unity SDK 的组成结构、手套如何连接、交互如何转化为触觉反馈,以及如何在你自己的代码中驱动手套。
本页介绍 com.microtube.hexr 0.9.4 版本,发布于 2026 年 10 月 11 日。早期版本请参阅更新日志。
插件包结构
插件包采用分层结构:你的场景与最上层交互,手套位于最底层。两个后端层只有在安装了对应的 XR SDK 时才会编译,核心层从不依赖它们。
| 程序集 | 命名空间 | 包含内容 | 编译条件 |
|---|---|---|---|
HaptGlove.Runtime | HaptGlove | HaptGloveHandler、蓝牙传输层、Haptics 命令构建器 | 始终 |
HexR.Runtime | HexR | 管理器、手部追踪、Pressure Controller(压力控制器)、触点、场景组件、HexR 菜单 | 始终 |
HexR.Runtime.MetaOVR | HexR.MetaOVR | Meta 抓取与戳按检测、可交互对象事件、透视 | 已安装 Meta Interaction SDK |
HexR.Runtime.OpenXR | HexR.OpenXR | XR Interaction Toolkit 集成(OpenXR 头显,包括 PICO) | 已安装 XR Interaction Toolkit |
HexR.Editor | HexR | HexR 菜单、Auto Setup、验证器、HexR Tools 窗口 | 仅限编辑器 |
后端在加载时会自动向核心层注册。碰撞体可视化工具、HexRUsable 和 HexR 菜单保留在全局命名空间中,以便旧场景仍可正常运行。
HEXR 场景
HexR → Create HexR Rig 会添加前两项,第三项由你的 XR SDK 提供。
| 对象 | 组件 | 作用 |
|---|---|---|
| HexR Main | HexRManager | 管理两只手套、处理连接,并在场景切换后重新关联双手。可通过 HexRManager.Instance 访问 |
| Left / Right Hand Physics | HaptGloveHandler、HexRTrackedHand、FingerUseTracking | 每只手一个:负责蓝牙连接、指定跟随哪只被追踪的手,以及记录每根手指的弯曲程度 |
| HexR Floating Menu | HexRFloatingMenu | 头显中的 HexR 菜单:连接手套、显示触点、查看每根手指的触觉反馈、切换场景和演示。用指尖按压操作 |
| Left / Right Pressure Controller | PressureTrackerMain | 该手的触觉控制器 |
场景启动时,触点会按名称查找它们。没有 Pressure Controller 的手不会产生任何触觉反馈。使用 HexR → Add Pressure Controller 即可添加。
被追踪的手
HexRTrackedHand.handRoot 指向你的 SDK 提供的手。HEXR 按名称查找各个关节:
| XR SDK | 手部根节点 | 关节 |
|---|---|---|
| Meta Interaction SDK | OpenXRLeftHand / OpenXRRightHand | XRHand_IndexTip、XRHand_Palm、… |
| XR Interaction Toolkit + XR Hands | Left / Right Hand Interaction Visual 下的 L_Wrist / R_Wrist | L_IndexTip、L_Palm、… |
每个触点由一个小型触发碰撞体、一个运动学(kinematic)Rigidbody(Unity 需要它来产生触发事件)和一个标明所属手和手指的 HapticFingerTrigger 组成。HEXR 没有自己的手模型:自 0.9 起,在所有后端上,触点都直接位于被追踪的手上。仍带有「幽灵」手的旧项目可以通过 HexR → Migration → Remove Ghost Hand 将其移除。
连接手套
蓝牙连接由你的应用自行建立,无需在系统设置中配对,也无需配套应用。每只手套都有各自的 HaptGloveHandler。
Connect(hand),或点击菜单中的 Connect 按钮- 同一时间只有一只手在扫描;第二只手套会等待第一只完成。
- 在 Android 上,失败的连接尝试会以逐渐增加的间隔重试,最长持续一分钟;掉线的手套也会自动重连。再次点击菜单中的 Connect 会取消重试,而
Connect(hand)永远不会取消重试。 - 断开连接、退出和暂停时,都会先释放手套。请参阅释放手套。
| 应用运行平台 | 蓝牙 |
|---|---|
| Android 头显(Meta Quest、PICO) | 支持;装置(rig)的默认设置 |
| Windows(Unity 编辑器和 64 位构建) | 支持。在构建版本中,将每个 HaptGloveHandler 上的 Build Platform 设置为 Window。在编辑器中,请通过 HexR Haptics Tester 连接,它会为 Play 模式自动完成此设置 |
| macOS、iOS、Linux、HoloLens(UWP) | 插件包不支持。包内虽附带一个 macOS 蓝牙库,但目前尚无任何代码调用它 |
包括头显在内的完整列表,请参阅平台与头显支持。
事件
这些是 HaptGloveHandler 上的公共 UnityAction 字段。使用 += 订阅,使用 -= 取消订阅。切勿使用 = 赋值:这会移除 HexRManager 所依赖的处理函数。它们不是 UnityEvent,因此不会显示在 Inspector 中。
| 事件 | 触发时机 |
|---|---|
onBluetoothConnected(HandType) | 手套已连接 |
onBluetoothConnectionFailed(HandType) | 连接尝试失败 |
onBluetoothDisconnected(HandType) | 连接已关闭:手套掉线、你主动断开连接,或应用正在关闭 |
onPumpAction(HandType, bool) | 供气已启动或停止 |
onBatteryStateChange(BatteryState) | Full、Medium 或 Low |
onDeviceFound(id, name) | 扫描发现了设备 |
HexRManager.ConnectAttemptEnded | 静态事件。连接尝试结束但未能建立连接时触发:权限被拒绝,或某次尝试失败(包括随后会重试的尝试)。连接成功时不会触发;成功连接请使用 onBluetoothConnected |
触觉如何触发
PressureTrackerMainHaptGloveHandler 构建并发送命令| 触发 | 用途 | 示例 |
|---|---|---|
| XR 交互事件 | 在抓取或悬停时感受物体 | HexRInteractableHaptics |
| 物理接触 | 感受表面、液体、心跳 | SpecialHaptics |
| 你的代码 | 游戏事件、序列、测试 | PressureTrackerMain |
延迟
从应用中发生事件到用户产生触感,所需时间是三段延迟之和。之所以分开列出,是因为只有最后一段由手套决定,另外两段取决于你的设置。
| 阶段 | 过程 | 耗时 |
|---|---|---|
| 1 · 从应用到蓝牙射频 | 在 Quest 和 PICO 构建中,包内的两个预置装置都开启了 Quest BLE Buffering:命令会先被收集起来,每 100 ms 最多合并为一次蓝牙写入发送,并在 Unity 的固定更新中进行检查。在 Windows 上以及在 Python 中,每条命令在你发送时都会立即写入 | 在开启缓冲的 Quest 和 PICO 上最多约 100 ms |
| 2 · 蓝牙 | 手套请求 7.5 ms 的连接间隔;实际使用的间隔由手机、头显或 PC 决定。Android 和 Python 使用带响应的写入方式,需等待手套响应 | 由主机决定;未经测量 |
| 3 · 手套 | 压力命令到达后,气泵会在一个 20 ms 控制周期内开始驱动,然后逐渐升至目标压力。在最快的 speed(1)下,每升高 10 kPa 约需 60 ms:达到最轻强度约需 90 ms,达到最强约需 300 ms;speed 为 0.1 时,耗时为其十倍。5 Hz 及以上的振动会在控制循环的下一轮开始。释放命令会立即停止气泵 | 依据固件源代码得出;非实测数据 |
当前手套的端到端延迟(从你的事件发生到用户感受到触感的时间)尚未经过测量,本文也不提供相关数值。如果你的应用依赖这一指标,请在你自己的环境中进行测量。
开启缓冲后,蓝牙写入次数更少、每次写入的数据更多,代价是最多需等待 100 ms。关闭缓冲(HexRManager 上的 Quest BLE Buffering 设置)后,每条命令都会立即写入,但在 Android 上连续发送的命令可能会丢失。该设置仅在 Quest 上测试过;如果 PICO 构建能够连接,但触感迟滞或没有反应,请分别尝试开启和关闭。
手部靠近检测
Pressure Controller(压力控制器)只有在 IsHandNear() 为 true 时才会发送大部分触觉反馈。满足以下任一条件即为 true:
| 信号 | 设置来源 |
|---|---|
| 正在抓取,或悬停准备抓取 | 你的 SDK 的抓取交互器:Meta 的 HandGrabInteractor,或 XRI 的 XRDirectInteractor 或 NearFarInteractor(自 0.9.4 起)。由 Auto Setup 以及每次场景加载时自动连接。对于 Near-Far Interactor,只有近距离抓取才算数,其悬停也仅在关闭远距离投射(far casting)时才算数:沿射线从房间另一头抓取的物体不会让手套施压 |
| 正在戳按,或即将戳按 | 你的 SDK 的戳按交互器:Meta 的 PokeInteractor 或 XRI 的 XRPokeInteractor,连接方式相同 |
指尖位于 ProximityCheck 区域内 | 你添加到交互器未覆盖的对象上的 ProximityCheck。指尖离开后,该状态会保持 0.5 s(即 clearDelay 的默认值) |
这三项结果是每个 PressureTrackerMain 上的公共字段:HandGrabbing、PokeHovering 和 CollisionNearHand。检测方法本身 IsHandNear() 是私有的。该检测按手进行:任意位置出现一个信号,就会为该手上所有受检测限制的调用放行。
| 受检测 | 不受检测 |
|---|---|
Single…Haptic、TriggerAll…、TriggerCustom…、TriggerPinchPressure | HapticFingerTrigger.TriggerFixPressure、所有 SpecialHaptics 效果、将 ByPassHandCheck 设为 true 的 CustomSingleHaptics / CustomSingleVibrations、RemoveAllHaptics、RemoveAllVibrations 以及各个 Remove…Haptics 调用 |
通过受检测调用执行的释放操作同样会受到检测:如果手已经离开,RemovePinchPressure() 以及最后一个参数为 false 的 CustomSingleHaptics 都会被丢弃。请使用 RemoveAllHaptics()、Remove…Haptics() 调用,或将最后一个参数设为 true 来释放。
如果你的调用没有任何效果,请选中 Pressure Controller(压力控制器),将 Inspector 切换到 Debug 模式并观察它的三个标志;或者在测试时为 ByPassHandCheck 传入 true。
通道与参数
共有六个通道:Haptics.Finger.Thumb、Index、Middle、Ring、Pinky、Palm。每个通道同一时间只播放一种模式。超出范围的数值会被限制在范围内。
| 模式 | 手感 | 参数 |
|---|---|---|
| 压力 | 持续稳定的按压 | intensity 0.1–1(约 15–50 kPa);speed 0.1–1,表示压力上升的速度 |
| 振动 | 通断交替的压力脉冲 | frequency 0.1–40 Hz;intensity 0.1–1。低于 5 Hz 时,强度决定脉冲压力;从 5 Hz 起,强度决定脉冲时长 |
| 慢脉冲 | 波形可调的缓慢节奏 | frequency 0.1–2 Hz;intensity;peakRatio 0.2–0.8;speed;endIntensity(低于 0.1 时释放) |
| 计数脉冲 | 指定次数的脉冲 | frequency;intensity;pulseCount 1–1000 |
场景组件以 10–60 的刻度表示强度(10 几乎感觉不到,60 则很有力),并将其换算为上文 0.1–1 的强度值。在你自己的代码中,可使用 PressureTrackerMain.StrengthToIntensity(strength) 进行同样的换算。
释放手套
手套会一直保持最后一条命令,直到收到新的指令;手套本身不会自动释放。插件包对此的处理方式如下:
| 触发时机 | 过程 |
|---|---|
| 组件被禁用或销毁 | HexRInteractableHaptics、HexRPhysicalButton、HexRGrabbable 和 SpecialHaptics 会释放它们施压的通道。后两者自 0.9.4 起支持;SpecialHaptics 区域只释放自己的通道,不会释放其他组件的通道 |
| 应用退出,包括在编辑器中停止 Play 模式 | 先释放两只手套上的所有通道,再关闭连接。自 0.9.4 起 |
| 应用暂停:摘下头显,或应用切换到后台 | 在 HaptGloveHandler.releaseWhenPaused 开启时(默认开启),释放所有通道。恢复运行后效果不会自动恢复,下一次发生变化时才会重新施加。自 0.9.4 起 |
你主动断开连接:调用 Disconnect(hand),或使用菜单中的开关 | 先释放手套,再关闭连接。自 0.9.4 起 |
| 应用崩溃或被强制关闭 | 无法发送任何命令,手套会持续施压 |
| 蓝牙连接中断 | 无法发送任何命令。手套会持续施压,并且在 Android 上处理器重连时不会释放。请参阅重连时释放 |
请关闭手臂模块电源。断连时自动释放只能由手套固件实现,而目前的固件并不具备这一功能。在你自己的代码中,施加了什么就要释放什么(在 OnDisable 中也要释放),或者在手套处理器上调用 ReleaseAllChannels()。
场景组件
大多数项目根本不需要调用 API,这些组件会自行触发触觉反馈。
| 组件 | 用途 | 关键字段 |
|---|---|---|
HexRInteractableHaptics — 从这里开始 | 在抓取或悬停时感受你的 SDK 自带的可交互对象(XRI 或 Meta)。后端会自动选择。禁用时释放其通道 | fireOn Select / Hover / SelectAndHover;各手指开关;strength 10–60;followGrip,根据手指的闭合程度在 minStrength 和 strength 之间缩放强度(两者均在 0.9.4 中修复);backend |
SpecialHaptics | 被触碰时播放预设效果的触发碰撞体。不受手部靠近检测限制 | RPressureTracker 和 LPressureTracker(必填:可由 Auto Find Hand Physics 自动填充)、TypeOfHaptics(见下表)、HapticPressure、VibrationsFrequencyValue、InTimer / OutTimer |
ProximityCheck | 告知 Pressure Controller(压力控制器)手正靠近一个不属于 SDK 可交互对象的物体 | Auto Set Up 按钮会自动连接两个 Pressure Controller |
HexRPhysicalButton | 由真实指尖按压的按钮,无需任何 SDK。HexR 菜单就是用它构建的 | onPressed;可触发的手指(默认为食指) |
HexRUsable | 扳机、把手、握把:所选手指弯曲时触发事件。自 0.9.4 起支持两种后端,之前仅支持 OpenXR | UseThreshold;WhenUseTriggerEvents / WhenStopUsingTriggerEvents |
HapticFingerTrigger | 即触点本身。在你自己的 OnTriggerEnter 中读取它,即可得知是哪只手的哪根手指发生了触碰 | handType、fingertype;TriggerFixPressure、TriggerVibrationPressure、RemoveHaptics |
HexRGrabbable | HEXR 自带的抓取功能,无需交互 SDK,手套的手即可拾取。直接发送到手套,不经过手部靠近检测。在 0.9.4 之前,没有父对象时它会在启动时抛出异常,其触觉反馈也会在抓取时抛出异常 | PinchGrab / PalmGrab;HapticStrength 10–60;需要 Rigidbody 和触发碰撞体 |
SpecialHaptics 效果 | 手感 |
|---|---|
CustomHaptics | 触碰期间持续施加压力 |
CustomVibrations | 以设定的频率和强度振动 |
FountainEffect | 流水 |
RainDropEffect | 雨滴落在手掌上 |
HeartBeatEffect | 心跳,规律或不规律 |
HandSqueezeEffect | 当手在该区域内握拢时触发 OnSqueezeEventTrigger。需要在装置(rig)的手上添加 FingerUseTracking |
我需要哪个组件?
| 你想要…… | 应用 | 补充说明 |
|---|---|---|
| 在 XR SDK 抓取、戳按或悬停于物体时感受该物体 | 在 SDK 的可交互对象旁添加 HexRInteractableHaptics | 物体本身或其子对象上必须有 XRI 或 Meta 可交互对象,否则不起作用。将 Backend 保持为 Auto |
| 让某个表面或区域带来特定触感,例如水、雨、心跳、嗡嗡振动 | 在触发碰撞体上添加 SpecialHaptics | 仅凭接触即可触发,不受手部靠近检测限制,因此无需 ProximityCheck |
| 无需 SDK,用指尖按下的按钮 | HexRPhysicalButton | 需要触发碰撞体 |
| 手指弯曲时执行某个动作 | HexRUsable | 自 0.9.4 起支持 Meta 和 OpenXR。更早的版本通过 L_ / R_ 关节名区分左右手,而 Meta 的骨骼并不使用这些名称 |
| 在装置的抓取和戳按交互器覆盖不到的物体上,使用受手部靠近检测约束的触觉 | 添加 ProximityCheck | 点击它的 Auto Set Up。它本身不会触发任何触觉 |
| 在你自己的代码中精确控制 | PressureTrackerMain 方法 | 施加的触觉记得释放——参见触发反馈 |
组合使用组件
| 组合 | 结果 |
|---|---|
HexRInteractableHaptics + ProximityCheck | 可行。仅当装置的交互器不向手部靠近检测提供输入时才需要,例如在开启远距投射(far casting)的 Near-Far 装置上由悬停触发的触觉,或 0.9.4 之前的任何 OpenXR 装置;其他情况下属于多余 |
可抓取物体上的 SpecialHaptics 区域 | 可行。PICO 教程中的心跳就是这样实现的 |
SpecialHaptics + ProximityCheck | 可行但没有必要。该区域本身不受检测限制;而当手位于区域内时,ProximityCheck 还会为该手的其他所有受检测约束的调用放行 |
HexRInteractableHaptics + 同一可交互对象上调用 Pressure Controller(压力控制器)的 UnityEvent | 避免。两者驱动相同的通道,以最后运行的为准;Inspector 会对此发出警告 |
HexRInteractableHaptics + HexRGrabbable | 避免。两套独立的抓取系统都会移动物体,并且都会发送触觉 |
手部靠近检测按手计算,而不是按物体计算:任何位置上的一次抓取、悬停或 ProximityCheck,都会为该手上所有受此检测约束的调用放行。
C# API
如果你想用自己的逻辑驱动触觉,请调用这些方法。
PressureTrackerMain — 位于每个 Pressure Controller(压力控制器)上
| 方式 | 作用 |
|---|---|
CustomSingleHaptics(finger, States, TargetPressure, Speed, ByPassHandCheck) | 对单个通道施加压力。States 为 true 时施加,为 false 时释放;TargetPressure 和 Speed 取值 0.1–1。使用命名参数时,必须与这些名称完全一致 |
CustomSingleVibrations(finger, States, TargetPressure, frequency, ByPassHandCheck) | 单个通道振动;frequency 取值 0.1–40 Hz |
SingleThumbHaptic(float) … SinglePalmHaptic(float) | 对单个通道施加 0.1–1 的压力。只有一个 float 参数,因此可以连接到 UnityEvent |
RemoveThumbHaptics() … RemovePalmHaptics() | 释放单个通道 |
TriggerAllHapticsIncrease(float) / TriggerAllVibrations(frequency) | 同时作用于所有通道 |
TriggerCustomHapticsIncrease(bool[6], pressure, speed) / TriggerCustomlVibrations(bool[6], intensity, frequency) | 选定的一组通道,按 Thumb…Palm 的顺序排列 |
TriggerPinchPressure(float) / RemovePinchPressure() | 拇指与食指捏合。释放操作同样受检测约束,因此如果手可能已经离开,请配合 RemoveAllHaptics() 使用 |
RemoveAllHaptics() / RemoveAllVibrations() | 全部释放。永远不会被手部靠近检测拦截;两者都发送,以确保所有通道均已关闭 |
HandGrabbing, PokeHovering, CollisionNearHand | 手部靠近检测的三个输入。你只需读取它们;它们由 Auto Setup 和场景组件负责设置 |
StrengthToIntensity(strength) | 静态方法。将组件使用的 10–60 强度刻度转换为这些方法所接受的 0.1–1 强度。自 0.9.4 起 |
TrackedHandRoot | 装置这一侧所跟随的被追踪的手;找到之前为 null。自 0.9.4 起 |
HexRManager — HexRManager.Instance
| 成员 | 作用 |
|---|---|
Connect(hand) | 连接该手套,包含权限请求,并遵循一次只连接一只手的规则。如果已连接或正在连接,则不执行任何操作;不需要 HexR 菜单。hand 为 HaptGloveHandler.HandType.Left 或 Right。自 0.9.4 起 |
Disconnect(hand) | 先释放该手套,再断开连接。未连接时不执行任何操作。自 0.9.4 起 |
IsConnected(hand), IsConnecting(hand) | 当前是否已连接;是否正在连接(包括等待权限或重试)。自 0.9.4 起 |
ConnectLeftBT() / ConnectRightBT() | 菜单按钮所调用的方法。作用为切换:对已连接的手套会断开连接,在重试期间则会取消。在代码中请优先使用 Connect(hand)。0.9.4 之前,如果场景中没有 HexR 菜单,调用这些方法会抛出异常 |
leftHand / rightHand | 每只手套的 HaptGloveHandler |
XRFramework | OpenXR 或 MetaOVR |
RebindHandsToCurrentScene() | 将触点重新挂接到当前场景的手上。每次加载场景时都会自动运行 |
| 碰撞体设置 | Auto Setup 所添加触点的尺寸和位置,按骨骼分别设置 |
ConnectAttemptEnded | 静态事件:一次连接尝试已结束,但未能建立连接。参见事件 |
HaptGloveHandler — 每只手套一个,命名空间为 HaptGlove
| 成员 | 作用 |
|---|---|
whichHand | Left 或 Right |
BTConnection() | 连接;如果已连接则断开连接。建议优先使用管理器的 Connect(hand),它会额外处理权限请求,并遵循一次只连接一只的规则 |
IsConnecting | 正在连接中,包括两次重试之间的等待。自 0.9.4 起 |
ReleaseAllChannels(), ReleaseChannels(params Haptics.Finger[]) | 一次写入即可关闭通道,振动和压力都会关闭。手套未连接时不会发送任何内容。自 0.9.4 起 |
releaseWhenPaused | 应用暂停时释放所有通道。默认开启。自 0.9.4 起 |
bleConnected | 当前是否已连接? |
UseCustomDeviceName, OptionalCustomDeviceName | 按你指定的名称查找手套 |
RememberDevice(id), ForgetDevice(), ConnectToDevice(id) | 直接连接到哪只手套 |
haptics | 构建命令:HEXRPressure、HEXRVibration、HEXRPulse。GetChannelState(finger) 返回上一条命令对某个通道的设置,类型为 ChannelState(HapticMode、强度、频率) |
BTSend(byte[]) | 直接向手套发送命令。跳过手部靠近检测 |
GetBatteryLevel(), GetAirPressure() | 最新的电量和压力读数 |
HexRTrackedHand 和 FingerUseTracking — 位于每只手上
| 成员 | 作用 |
|---|---|
HexRTrackedHand.handType, handRoot | 哪只手,以及它所跟随的被追踪的手 |
ResolveRawFingerJoint(finger), ResolveRawPalmJoint() | 指尖或手掌关节,两种骨骼均适用 |
FingerUseTracking.ThumbUse … LittleUse | 0 = 完全弯曲,1 = 完全伸直。自动校准:手张开并握拢一次后即趋于稳定 |
FingerUseTracking.isHandOpen() | 是否所有手指都已伸直? |
场景切换与编辑器工具
SDK 的手会随所在场景一起销毁,而装置会一直保留。因此每次加载场景后,管理器都会重新挂接:
HapticFingerTrigger随后日志会显示 [HexR] Rebound Left hand … 6 haptic trigger(s):即五个指尖和手掌。
Auto Setup 会在编辑器中完成同样的工作,结果随场景一起保存。它只添加缺少的部分,绝不会移动你调整过的碰撞体。你可以通过 Create HexR Rig、HexR → Troubleshoot → Re-run Auto Setup 或管理器上的 Auto Set Up HexR 按钮运行它。之后,Validate Scene Setup 会报告仍未连接好的部分。
| 菜单 | 作用 |
|---|---|
| HexR → HexR Tools | 工具窗口,包含三个标签页:HexR Haptics Tester(在播放模式下连接手套、触发任意通道、查看实时压力,以及 All OFF)、HexR Setup(以检查清单形式呈现的验证器,带有 Fix all)、Project Setup(选择 OpenXR、Meta OVR 或 PICO,并安装对应的包) |
| HexR → Create Demo Scene | 装置、Pressure Controller(压力控制器)、一个可抓取物体和一个触觉区域 |
| HexR → Create HexR Rig | 为你的后端创建装置和 Pressure Controller(压力控制器),然后运行 Auto Setup |
| HexR → Add Pressure Controller | 一对 Pressure Controller(压力控制器) |
| HexR → Add HexR Menu | 在你自己的场景中添加 HexR 菜单 |
| HexR → Troubleshoot | Re-run Auto Setup、Validate Scene Setup |
| HexR → Migration | 一次性升级操作,例如 Remove Ghost Hand |
| HexR 菜单 → Show Colliders | 在被追踪的手上绘制所有触点 |
| HexR 菜单 → HAPTICS | 每只手套各手指和手掌最近一次收到的数值(已限制在有效范围内):开启时高亮,结束后变暗。可通过 showHapticsReadout 关闭 |
类型索引
com.microtube.hexr 中的所有公共类型,便于按名称查找。如果上文已有章节介绍,链接会指向该章节。
组件
| 类型 | 说明 |
|---|---|
HexRManager | 装置(rig):两只手套、连接、重新挂接手部。成员 |
HaptGloveHandler | 单只手套的蓝牙连接与命令发送器。成员 |
PressureTrackerMain | Pressure Controller(压力控制器):一只手的所有触觉都经由它发送。方法 |
HexRTrackedHand, FingerUseTracking | 手套跟随哪只被追踪的手,以及每根手指的弯曲程度。成员 |
HapticFingerTrigger | 每个指尖和手掌上的触点。场景组件 |
HexRInteractableHaptics | 为你所用 SDK 自带的可交互对象添加触觉。场景组件 |
HexRGrabbable | HEXR 自带的抓取功能,无需交互 SDK,直接由戴手套的手完成。场景组件 |
SpecialHaptics, ProximityCheck, HexRPhysicalButton, HexRUsable | 预设效果、手部靠近检测区域、指尖按钮、弯曲手指即可使用。场景组件 |
HexRFloatingMenu | 头显内的面板。HEXR 场景 |
HaptGloveCollidersVisualizer | 绘制触点;菜单中的 Show Colliders 使用的就是它。SetVisible(bool)、IsShowing |
HaptGloveUI | 位于 HexR Main 上:将 Connect 按钮的点击转发给管理器的 ConnectLeftBT() / ConnectRightBT()。为兼容旧版面板而保留;它本身不更新任何文本 |
SceneLoader | 应用的场景切换。与 HexRManager 位于同一个常驻对象上 |
HexRGripByHand | OpenXR:在不替换原生 XRGrabInteractable 的前提下,为它提供每只手各自的握持点,以及捏合或握拳两种抓握方式。GripMode 用于选择抓握方式 |
OpenXRHandNearSource, MetaOVRHandNearSource | 根据抓取和戳按交互器为手部靠近检测提供输入。由 Auto Setup 自动添加 |
OpenXRBackendSetup, MetaOVRBackendSetup | 在编辑器中和运行时,将这些输入源连接到 Pressure Controller(压力控制器)上。无需你自己添加任何内容 |
MetaOVRPassthrough, IHexRPassthrough | Meta 上的透视功能,以及菜单在场景加载后用于刷新任意后端透视的接口 |
枚举与取值
| 类型 | 取值及使用位置 |
|---|---|
Haptics | 命令构建器,位于 HaptGloveHandler.haptics 上。通道与参数 |
Haptics.Finger | Thumb, Index, Middle, Ring, Pinky, Palm |
Haptics.HapticMode, Haptics.ChannelState | 上一条命令对某个通道的设置:Off、Pressure、Vibration 或 Pulse,以及强度和频率。通过 GetChannelState(finger) 获取 |
HandType | Left / Right,用于 HaptGloveHandler 和 HapticFingerTrigger |
FingerType | HapticFingerTrigger 对应哪根手指,见 fingertype |
BatteryState | Full、Medium、Low。事件 |
TargetPlatform | 手套处理器的 Build Platform:Android 或 Window(拼写末尾没有 s)。连接手套 |
FireOn | HexRInteractableHaptics.fireOn:Select、Hover、SelectAndHover |
HexRBackendChoice | 由哪个交互 SDK 驱动组件:Auto 或指定名称的后端 |
HexRHandSide | 某次交互来自哪只手(以后端报告的为准) |
HeartBeat | SpecialHaptics 心跳:Regular 或 Irregular |
Options, Option | HexRGrabbable.Options PinchGrab / PalmGrab;HexRGrabbable.Option 重力 On / Off;HexRManager.Options 即 XRFramework |
FingertipCenters | 管理器碰撞体设置中各手指触点的偏移量 |
后端底层、编辑器与内部实现
| 类型 | 说明 |
|---|---|
IHexRInteractionBackend, IHexRInteractionBinding, IHexRInteractionSink, HexRInteractionBackends, HexRHandSideResolver | XR SDK 接入 HEXR 的方式。仅在添加你自己的后端时才需要 |
HexRCompat | 为在 2022.3 与 Unity 6 之间改名的 Unity API 提供的替代实现。参见Unity 版本支持 |
HexRMenu, HexRAutoSetup, HexRSetupValidator, SetupCheck, HexRToolsWindow | 编辑器:HexR 菜单、Auto Setup、Validate Scene Setup 和 HexR Tools 窗口。编辑器工具 |
HexRSettingEditorGUI, HexRInteractableHapticsEditor, HapticEffectControllerEditor, ProximityCheckEditor, FingerUseTrackingEditorSetting | 编辑器:HexRManager、HexRInteractableHaptics、SpecialHaptics、ProximityCheck 和 FingerUseTracking 的 Inspector |
BLE, BLEScan, BLEData, Characteristic, Service, DeviceUpdate, ErrorMessage, ScanStatus, Encode, Decode, FunIndex, AndroidUiThread | 内部:HaptGloveHandler 底层的蓝牙层。不应直接使用;请通过手套处理器或 Pressure Controller(压力控制器)调用 |
Python
在 Unity 之外,HaptGlovePython 可以构建同样的命令,再由你用 bleak 写入手套。它需要 Python 3.8 至 3.13。安装设置和经过测试的示例请见Python 指南。
| 方式 | 作用 |
|---|---|
hexr_pressure(finger, state, intensity, speed) | 对单个通道施加压力。state 为 True 时施加;为 False 且强度为 0 时释放 |
hexr_pressure_multiple(fingers, states, intensities, speeds) | 一次写入即可对多个通道施加压力;intensities 和 speeds 取值 0.1–1。所有状态均为 False 且强度均为 0 时释放这些通道 |
hexr_vibrations(finger, state, frequency, intensity, peakRatio, speed, endIntensity) | 慢速脉冲,0.1–2 Hz。hexr_vibrations_multiple 接受列表参数 |
AddressDiscovery.py, CharacteristicDiscovery.py, ExampleHaptics.py | 查找你的手套,列出它公开的特征,运行现成的示例 |
请使用 Haptics.Finger.Index,而不是 "Index"。传入字符串会引发 AttributeError。
复制即用的示例
针对最常见需求的完整脚本。每个脚本都可以基于 com.microtube.hexr 0.9.4 编译通过。如果只是想在抓取物体时感受到它,完全不需要写代码:给物体添加 HexRInteractableHaptics 即可。
通过代码连接两只手套
无需 HexR 菜单。将它放在场景中的任意对象上即可。在编辑器中,请改用 HexR Haptics Tester 进行连接——参见连接手套。
using UnityEngine;
using HaptGlove;
using HexR;
// Connects both gloves when the scene starts -- no HexR menu needed --
// and logs each one as it comes up or drops.
public class ConnectGloves : MonoBehaviour
{
void Start()
{
HexRManager rig = HexRManager.Instance;
rig.leftHand.onBluetoothConnected += OnConnected;
rig.rightHand.onBluetoothConnected += OnConnected;
rig.leftHand.onBluetoothDisconnected += OnDisconnected;
rig.rightHand.onBluetoothDisconnected += OnDisconnected;
// Does nothing for a glove that is already connected or connecting.
rig.Connect(HaptGloveHandler.HandType.Left);
rig.Connect(HaptGloveHandler.HandType.Right);
}
void OnDestroy()
{
HexRManager rig = HexRManager.Instance;
if (rig == null) return;
rig.leftHand.onBluetoothConnected -= OnConnected;
rig.rightHand.onBluetoothConnected -= OnConnected;
rig.leftHand.onBluetoothDisconnected -= OnDisconnected;
rig.rightHand.onBluetoothDisconnected -= OnDisconnected;
}
void OnConnected(HaptGloveHandler.HandType hand) { Debug.Log(hand + " glove connected"); }
void OnDisconnected(HaptGloveHandler.HandType hand) { Debug.Log(hand + " glove disconnected"); }
}在你自己的事件中按压手指
将一个 Pressure Controller(压力控制器)拖入 Hand,然后将 Press 和 Release 连接到任意 UnityEvent。
using UnityEngine;
using HaptGlove;
using HexR;
// Wire Press() and Release() to any UnityEvent: a button, a trigger, an
// animation event. Strength is on the same 10-60 scale as the components'.
public class PressOnEvent : MonoBehaviour
{
public PressureTrackerMain hand; // a Pressure Controller
public Haptics.Finger finger = Haptics.Finger.Index;
[Range(10f, 60f)] public float strength = 30f;
bool pressing;
public void Press()
{
float intensity = PressureTrackerMain.StrengthToIntensity(strength);
// true skips the hand-near check: this press answers your event, not a touch.
hand.CustomSingleHaptics(finger, true, intensity, 1f, true);
pressing = true;
}
public void Release()
{
if (!pressing) return;
hand.CustomSingleHaptics(finger, false, 0f, 1f, true);
pressing = false;
}
// Disabled, or the scene changes, mid-press: let go as well.
void OnDisable() { Release(); }
}按压触碰到物体的手指
适用于任何带有触发碰撞体的物体。触点上的 HapticFingerTrigger 会告知是哪只手的哪根手指发生了触碰,因此该脚本无需单独配置 Pressure Controller(压力控制器)。
using System.Collections.Generic;
using UnityEngine;
using HexR;
// On a trigger collider: presses exactly the finger or palm that touches it,
// on the hand it belongs to, and releases it when it leaves.
[RequireComponent(typeof(Collider))]
public class PressWhereTouched : MonoBehaviour
{
[Range(0.1f, 1f)] public float intensity = 0.5f;
readonly HashSet<HapticFingerTrigger> touching = new HashSet<HapticFingerTrigger>();
void OnTriggerEnter(Collider other)
{
if (!other.TryGetComponent(out HapticFingerTrigger touch)) return;
touch.TriggerFixPressure(intensity); // 0.1-1, skips the hand-near check
touching.Add(touch);
}
void OnTriggerExit(Collider other)
{
if (!other.TryGetComponent(out HapticFingerTrigger touch)) return;
touch.RemoveHaptics();
touching.Remove(touch);
}
// Disabled while a finger is inside: release what is still pressed.
void OnDisable()
{
foreach (HapticFingerTrigger touch in touching)
{
if (touch != null) touch.RemoveHaptics();
}
touching.Clear();
}
}重新连接时释放
连接中断的手套仍会保持最后一条命令所设定的按压。此脚本会在手套重新连接后立即将其释放。
using UnityEngine;
using HaptGlove;
using HexR;
// A glove whose Bluetooth link dropped keeps its last pressure. When it
// reconnects, release everything before anything else is sent.
public class ReleaseOnReconnect : MonoBehaviour
{
void Start()
{
HexRManager.Instance.leftHand.onBluetoothConnected += ReleaseAll;
HexRManager.Instance.rightHand.onBluetoothConnected += ReleaseAll;
}
void OnDestroy()
{
if (HexRManager.Instance == null) return;
HexRManager.Instance.leftHand.onBluetoothConnected -= ReleaseAll;
HexRManager.Instance.rightHand.onBluetoothConnected -= ReleaseAll;
}
void ReleaseAll(HaptGloveHandler.HandType hand)
{
HaptGloveHandler glove = hand == HaptGloveHandler.HandType.Left
? HexRManager.Instance.leftHand
: HexRManager.Instance.rightHand;
glove.ReleaseAllChannels();
}
}下一步
Connect(hand) 连接一只手套HexRInteractableHaptics要集成到现有应用中?请先从 HexRInteractableHaptics 入手,再考虑更底层的 PressureTrackerMain 或 HaptGloveHandler。