Godot haptics setup
Make objects felt with ready-made nodes, then from GDScript.
How haptics work
Each tracked hand carries six HapticFingerTrigger areas on physics layer 20: five fingertips and the palm. HEXR’s nodes are Area3Ds that watch that layer: when a finger trigger enters, they know the hand and the finger, and send through that hand’s PressureTrackerMain to its glove. Six channels, Thumb to Palm, each pressing (about 15–50 kPa), vibrating (0.1–40 Hz) or pulsing.
Without code
| Node | Use it for | Settings |
|---|---|---|
| HexRGrabbable | Child of a RigidBody3D: pick it up with a pinch or the palm; each touching finger presses | type_of_grab pinch / palm, haptic_strength 10–60 |
| HexRPhysicalButton | A button pressed by a fingertip; the finger feels it while it is down | which fingers press it (thumb … palm), press_pressure, cap and travel; signals pressed and released |
| SpecialHaptics | A zone with an effect for whatever is inside it | type_of_haptics: custom vibrations, custom haptics, fountain, rain drop, heart beat, hand squeeze |
| HexRInteractableHaptics | Child of a Godot XR Tools pickable: pressure while it is held, following how tightly the hand closes | strength, follow_grip, the fingers |
| HexRUsable | Signals when the chosen fingers curl and uncurl around it | which fingers (thumb … little_finger), use_threshold |
| ProximityCheck | Opens the hand-near check (below) while a hand is inside | clear_delay; Auto Set Up in the Inspector adds its shape |
The node and property names are the Unity package’s, in Godot’s snake_case. Size each node’s collision shape to the space a finger should feel. Zones skip the hand-near check: being inside is the evidence.
From code
One finger
var hand := HexR.controller(HaptGloveHandler.HandType.RIGHT) # the hand's PressureTrackerMain
# Pressure: finger, on/off, intensity 0.1-1, ramp speed 0.1-1, skip the hand-near check
hand.custom_single_haptics(Haptics.Finger.INDEX, true, 0.5, 1.0, false)
# Vibration: finger, on/off, intensity 0.1-1, frequency 0.1-40 Hz, skip the hand-near check
hand.custom_single_vibrations(Haptics.Finger.PALM, true, 0.7, 12.0, false)
# The components' 10-60 strength scale, as the 0.1-1 intensity above
var intensity := PressureTrackerMain.strength_to_intensity(30.0)Several fingers
# One entry per channel: Thumb, Index, Middle, Ring, Pinky, Palm
var grip := [true, true, true, false, false, false]
hand.trigger_custom_haptics_increase(grip, 0.6, 1.0) # pressure 0.1-1, speed 0.1-1
hand.trigger_custom_vibrations(grip, 0.7, 12.0) # intensity 0.1-1, frequency 0.1-40 Hz
hand.trigger_all_haptics_increase(0.5) # every channel
hand.trigger_pinch_pressure(0.6) # thumb and index togetherThe raw commands
# Build the bytes yourself and send them: byte-for-byte what the Unity package sends.
var glove := HexR.right_hand # the HaptGloveHandler; Unity: HexRManager.Instance.rightHand
glove.bt_send(glove.haptics.hexr_pressure(Haptics.Finger.INDEX, true, 0.8, 1.0))
glove.bt_send(glove.haptics.hexr_vibration(Haptics.Finger.PALM, true, 18.0, 0.7))
glove.bt_send(glove.haptics.hexr_pulse(Haptics.Finger.THUMB, true, 3.0, 0.6, 5))Press the finger that touches
extends Area3D
## Presses exactly the finger or palm that touches this area, and lets go when it leaves.
## Its collision mask must include layer 20 (hexr_touch).
@export_range(0.1, 1.0) var intensity := 0.5
func _ready() -> void:
area_entered.connect(_on_area_entered)
area_exited.connect(_on_area_exited)
func _on_area_entered(area: Area3D) -> void:
if area is HapticFingerTrigger:
area.trigger_fix_pressure(intensity) # 0.1-1, skips the hand-near check
func _on_area_exited(area: Area3D) -> void:
if area is HapticFingerTrigger:
area.remove_haptics()Letting go
hand.remove_index_haptics() # one finger: remove_thumb_haptics() ... remove_palm_haptics()
hand.remove_all_haptics() # every channel's pressure on this hand
hand.remove_all_vibrations() # every channel's vibration on this hand
HexR.right_hand.release_all_channels() # everything on the glove, both paths, in one writeThe PressureTrackerMain’s gated calls (the single_*, trigger_* and pinch calls) only fire while the hand is near something: grabbing, poke-hovering, or inside a ProximityCheck. That stops a stray call pressing an empty hand. Pass bypass_hand_check = true to the custom calls to skip it; the remove_* calls are never gated.
Nothing on the glove lets go by itself. The addon releases on disconnect, pause and quit, but if the app crashes or the link drops the glove keeps pressing: switch the arm module off.