Skip to content

Instantly share code, notes, and snippets.

@andyman
Created September 17, 2025 08:45
Show Gist options
  • Select an option

  • Save andyman/3c0199e6adf0904c7fbe542a0ef23579 to your computer and use it in GitHub Desktop.

Select an option

Save andyman/3c0199e6adf0904c7fbe542a0ef23579 to your computer and use it in GitHub Desktop.
A simple quick-n-dirty XR microgesture detector made during a game jam for Godot
extends Node
class_name XRMicrogestureDetector
@export var is_right : bool = false
@export var hand : Node3D
@export var thumb_tip_node : Node3D
@export var thumb_distal_node : Node3D
@export var index_mid_node : Node3D
@export var index_tip_node : Node3D
# swipe parameters
@export var close_enough_to_center: float = 0.01
@export var swipe_threshold: float = 0.02 # how far thumb must move relative to start
@export var swipe_time_limit: float = 0.3 # max time allowed for swipe (seconds)
@export var swipe_confirm_frames: int = 3 # require N consecutive frames over threshold
# visuals feedback
@export var left_arrow : MeshInstance3D
@export var right_arrow : MeshInstance3D
@export var center_dot : MeshInstance3D
@export var swiping_scale_multiplier : float = 1.1
@export var swiped_arrow_scale_multiplier : float = 1.3
@export var idle_material : Material
@export var swiping_material : Material
@export var swiped_material : Material
# swipe signals
signal left_swipe(hand: String)
signal right_swipe(hand: String)
# internal
var timer: float = 0.0
var swipe_frames: int = 0
var left_swipe_material_timer = 0.0
var right_swipe_material_timer = 0.0
var left_arrow_base_scale : Vector3
var right_arrow_base_scale : Vector3
var center_dot_base_scale : Vector3
func _ready() -> void:
left_arrow_base_scale = left_arrow.scale
right_arrow_base_scale = right_arrow.scale
center_dot_base_scale = center_dot.scale
func _physics_process(delta: float) -> void:
_process_hand(delta)
func _process_hand(delta: float) -> void:
# --- get joint positions ---
var thumb_tip : Vector3 = thumb_tip_node.global_position
var thumb_distal : Vector3 = thumb_distal_node.global_position
var index_mid : Vector3 = index_mid_node.global_position
var index_tip : Vector3 = index_tip_node.global_position
# convert to hand-local space
var thumb_tip_local : Vector3 = hand.to_local(thumb_tip)
var thumb_distal_local : Vector3 = hand.to_local(thumb_distal)
var thumb_central_local : Vector3 = (thumb_tip_local + thumb_distal_local) * 0.5
var index_mid_local = hand.to_local(index_mid)
var index_tip_local = hand.to_local(index_tip)
var near_center : bool = (thumb_tip_local - index_mid_local).length() < close_enough_to_center or (thumb_distal_local - index_mid_local).length() < close_enough_to_center or (thumb_central_local - index_mid_local).length() < close_enough_to_center
# if we're near the center then keep resetting the timer
if (near_center):
timer = 0.0
swipe_frames = 0
elif (timer < swipe_time_limit):
timer += delta
var swipe_dir : Vector3 = (index_tip_local - index_mid_local).normalized()
if (is_right):
swipe_dir = -swipe_dir
var projection_total : float = (thumb_tip_local - index_mid_local).dot(swipe_dir)
var hand_name : String = "right" if is_right else "left"
if projection_total > swipe_threshold:
swipe_frames += 1
if swipe_frames >= swipe_confirm_frames:
swipe_frames = 0
left_arrow.scale = left_arrow_base_scale * swiped_arrow_scale_multiplier
timer = swipe_time_limit + 1.0
left_swipe_material_timer = 0.25
emit_signal("left_swipe", hand_name)
elif projection_total < -swipe_threshold:
swipe_frames += 1
if swipe_frames >= swipe_confirm_frames:
swipe_frames = 0
right_arrow.scale = right_arrow_base_scale * swiped_arrow_scale_multiplier
timer = swipe_time_limit + 1.0
right_swipe_material_timer = 0.25
emit_signal("right_swipe", hand_name)
if (timer < swipe_time_limit):
left_arrow.scale = left_arrow.scale.lerp(left_arrow_base_scale * swiping_scale_multiplier, delta * 20.0)
right_arrow.scale = right_arrow.scale.lerp(right_arrow_base_scale * swiping_scale_multiplier, delta * 20.0)
center_dot.scale = center_dot.scale.lerp(center_dot_base_scale * swiping_scale_multiplier, delta * 20.0)
left_arrow.material_override = swiping_material
right_arrow.material_override = swiping_material
center_dot.material_override = swiping_material
else:
left_arrow.scale = left_arrow.scale.lerp(left_arrow_base_scale, delta * 3.0)
right_arrow.scale = right_arrow.scale.lerp(right_arrow_base_scale, delta * 3.0)
center_dot.scale = center_dot.scale.lerp(center_dot_base_scale, delta * 3.0)
left_arrow.material_override = swiped_material if (left_swipe_material_timer > 0.0) else idle_material
right_arrow.material_override = swiped_material if (right_swipe_material_timer > 0.0) else idle_material
center_dot.material_override = idle_material
left_swipe_material_timer -= delta
right_swipe_material_timer -= delta
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment