xrjamfeb2025/addons/godot-xr-tools/rumble/rumble_manager.gd

161 lines
4.5 KiB
GDScript

extends Node
## XR Tools Rumble (Controllers) Manager Script
##
## This script uses the controller's existing rumble intensity variable,
## and allows you to rumble the controller for a certain amount
## of time 'beats'.
##
## Example: something hits you while you're mowing the lawn,
## i.e. a short intense rumble happens during long low rumble.
## Name in the OpenXR Action Map for haptics
const HAPTIC_ACTION := &"haptic" # TODO: Migrate
# Shorthand for all trackers, in use to be substituted with _queues.keys()
const ALL_TRACKERS := [&"all"]
# A Queue Per Haptic device (Dictionary<StringName, XRToolsRumbleManagerQueue>)
var _queues: Dictionary = {}
## Add support for is_xr_class
func is_xr_class(p_name: String) -> bool:
return p_name == "XRToolsRumbleManager"
## Get the default Haptics Scale value
func get_default_haptics_scale() -> float:
var default = 1.0
# Check if the project has overridden the addon's default
if ProjectSettings.has_setting("godot_xr_tools/input/haptics_scale"):
default = ProjectSettings.get_setting("godot_xr_tools/input/haptics_scale")
if default < 0.0 or default > 1.0:
# out of bounds? reset to default
default = 1.0
return default
## Used to convert gamepad magnitudes to equivalent XR haptic magnitude
func combine_magnitudes(weak: float, strong: float) -> float:
if strong >= 0.01:
return 0.5 + clamp(strong / 2, 0.0, 0.5)
return clamp(weak / 2, 0.0, 0.5)
# On Ready
func _ready():
if Engine.is_editor_hint():
return
# Some rumble events are active during pause
process_mode = PROCESS_MODE_ALWAYS
# Create a queues for standard controllers
_queues[&"left_hand"] = XRToolsRumbleManagerQueue.new()
_queues[&"right_hand"] = XRToolsRumbleManagerQueue.new()
# Determine how much to - and perform the - rumbles each tick
func _process(delta: float) -> void:
if Engine.is_editor_hint():
return
# We'll be subtracting this from the event remaining ms
var delta_ms = int(delta * 1000)
for tracker_name in _queues:
var haptic_queue : XRToolsRumbleManagerQueue = _queues[tracker_name]
# default to noXRToolsRumbleManagerQueuensure it's a float, or it rounds to all or nothing!)
var magnitude: float = 0.0
# Iterate over the events
for key in haptic_queue.events.keys():
var event : XRToolsRumbleEvent = haptic_queue.events[key]
# if we're paused and it's not supposed to be active, skip
if get_tree().paused and not event.active_during_pause:
continue
# If we've passed the threshold from positive to negative, the event is done
if !event.indefinite and haptic_queue.time_remaining[key] < 0:
clear(key, [tracker_name])
continue
# Reduce the time remaining
haptic_queue.time_remaining[key] -= delta_ms
# If it's of greater magnitude, update left magnitude to be set
if event.magnitude > magnitude:
magnitude = event.magnitude
# scale the final magnitude
magnitude *= XRToolsUserSettings.haptics_scale
# Make that tracker rumble
if magnitude > 0:
XRServer.primary_interface.trigger_haptic_pulse(
HAPTIC_ACTION,
tracker_name, # if the tracker name isn't valid, it will error but continue
0,
magnitude,
0.1,
0)
# Add an event
func add(event_key: Variant, event: XRToolsRumbleEvent,
trackers: Array = ALL_TRACKERS) -> void:
if not event_key:
push_error("Event key is invalid!")
return
if not event:
clear(event_key, trackers)
return
# Substitube the shorthand for all trackers with the real thing
if trackers == ALL_TRACKERS:
trackers = _queues.keys()
for tracker in trackers:
if tracker is XRNode3D:
tracker = tracker.tracker
# Create queue first time a target is suggested
if not _queues.has(tracker):
_queues[tracker] = XRToolsRumbleManagerQueue.new()
# Add the event and it's remaining time to the respective queues
_queues[tracker].events[event_key] = event
_queues[tracker].time_remaining[event_key] = event.duration_ms
# Remove an event
func clear(event_key: Variant, trackers: Array = ALL_TRACKERS) -> void:
if not event_key:
push_error("Event key is invalid!")
return
# Substitube the shorthand for all trackers with the real thing
if trackers == ALL_TRACKERS:
trackers = _queues.keys()
for tracker in trackers:
if tracker is XRNode3D:
tracker = tracker.tracker
# Ignore if the queue doesn't exist
if not _queues.has(tracker):
continue
# Remove the event and it's remaining time from the respective queues
_queues[tracker].events.erase(event_key)
_queues[tracker].time_remaining.erase(event_key)