Skip to content

Commit

Permalink
Add Rumble Manager and Demo
Browse files Browse the repository at this point in the history
added manager code
  • Loading branch information
lunarcloud committed Oct 31, 2023
1 parent e23e6d4 commit 5c9444e
Show file tree
Hide file tree
Showing 26 changed files with 596 additions and 131 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@
logs
Thumbs.db
android/
.venv/
35 changes: 0 additions & 35 deletions addons/godot-xr-tools/misc/rumble_event.gd

This file was deleted.

41 changes: 41 additions & 0 deletions addons/godot-xr-tools/rumble/rumble_event.gd
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
@icon("res://addons/godot-xr-tools/editor/icons/rumble.svg")
class_name XRToolsRumbleEvent
extends Resource

##
## XR Tools Rumble Event Resource
##

@export_range(0, 1, 0.10) var magnitude: float = 0.5

@export_enum("Both", "Left", "Right") var hand = 0

@export_category("Timing")

@export var indefinite: bool = false

@export_range(100, 4000, 100) var duration_ms: int = 300

@export_category("Hints")

@export var active_during_pause: bool = false

var remaining_ms: int


# Make sure that every parameter has a default value.
# Otherwise, there will be problems with creating and editing
# your resource via the inspector.
func _init(p_magnitude = 0.1, p_hand := 0, p_duration_ms := 0,
p_active_during_pause := false, p_indefinite := false):
magnitude = p_magnitude
hand = p_hand
duration_ms = p_duration_ms
active_during_pause = p_active_during_pause
indefinite = p_indefinite
reset_remaining()


# re-arms event for re-use
func reset_remaining() -> void:
remaining_ms = duration_ms
151 changes: 151 additions & 0 deletions addons/godot-xr-tools/rumble/rumble_manager.gd
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
@tool
@icon("res://addons/godot-xr-tools/editor/icons/rumble.svg")
class_name XRToolsRumbleManager
extends Node

##
## XR Tools Rumble (Controllers) Manager Script
##
## @desc:
## 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.
##

enum Hand { BOTH = 0, LEFT = 1, RIGHT = 2 }

# Name in the OpenXR Action Map for haptics
const HAPTIC_ACTION = "haptic"

@export_range(0.0, 1.0, 0.05) var wakeup_rumble := 0.1

## Used to control rumble like volume
@export_range(0.0, 1.0, 0.05) var magnitude_scale := 1.0

@export var default_rumble_event: XRToolsRumbleEvent = create_event()

# Keep track of singular instance
# GDLint Issue: https://github.com/Scony/godot-gdscript-toolkit/issues/242
static var instance: XRToolsRumbleManager

var time_start: int = 0

# Dictionary Variant to XRToolsRumbleEvent
var _events: Dictionary = {}

@onready var _controller_left_node: XRController3D = XRHelpers.get_left_controller(self)

@onready var _controller_right_node: XRController3D = XRHelpers.get_right_controller(self)


# Add support for is_xr_class
func is_xr_class(name: String) -> bool:
return name == "XRToolsRumbleManager"


static 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)


static func normal_clamp(value: float) -> float:
return clamp(value, 0.0, 1.0)


static func sec_to_ms(secs: float) -> int:
return int(secs * 1000)


static func create_event(
magnitude := 0.1, hand := 0, duration_ms := 100,
active_during_pause := false, indefinite := false
) -> XRToolsRumbleEvent:
var event = XRToolsRumbleEvent.new()
event.magnitude = magnitude
event.hand = hand
event.duration_ms = duration_ms
event.active_during_pause = active_during_pause
event.indefinite = indefinite
return event


func _enter_tree():
if not is_instance_valid(instance):
instance = self
else:
self.queue_free()


func _exit_tree():
if is_instance_valid(instance) and instance == self:
instance = null


func _ready():
set_event_details("wakeup", wakeup_rumble, 0, 150, false)


func _process(delta: float) -> void:
# default to no rumble (ensure it's a float, or it rounds to all or nothing!)
var left_magnitude: float = 0.0
var right_magnitude: float = 0.0

# We'll be subtracting this from the event remaining ms
var delta_ms = sec_to_ms(delta)

# Iterate over the events
for key in _events.keys():
var event : XRToolsRumbleEvent = _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

# Reduce the time remaining
event.remaining_ms -= delta_ms

# If we've passed the threshold from positive to negative, the event is done
if !event.indefinite and event.remaining_ms < 0:
_events.erase(key)
continue

# If it's a left or both hand - and of greater magnitude - update left magnitude to be set
if event.hand != Hand.RIGHT and event.magnitude > left_magnitude:
left_magnitude = event.magnitude

# If it's a right or both hand - and of greater magnitude - update right magnitude to be set
if event.hand != Hand.LEFT and event.magnitude > right_magnitude:
right_magnitude = event.magnitude

# now that we're done looping through the events, set the rumbles, scaled
if is_instance_valid(_controller_left_node):
_controller_left_node.trigger_haptic_pulse(
HAPTIC_ACTION, 0, left_magnitude * magnitude_scale, 0.1, 0)

if is_instance_valid(_controller_right_node):
_controller_right_node.trigger_haptic_pulse(
HAPTIC_ACTION, 0, right_magnitude * magnitude_scale, 0.1, 0)


func set_event_details(
event_key: Variant, magnitude: float, hand := 0, duration_ms := -1,
active_during_pause := false, indefinite := false
) -> void:
set_event(event_key, create_event(magnitude, hand, duration_ms, active_during_pause, indefinite))


func set_event(event_key: Variant, event: XRToolsRumbleEvent) -> void:
event.reset_remaining() # re-arms event for re-use
if event_key != null:
_events[event_key] = event
else:
push_error("Event key is invalid! ", event_key)


func remove(event_key: Variant) -> void:
if event_key != null:
_events.erase(event_key)
29 changes: 29 additions & 0 deletions addons/godot-xr-tools/rumble/rumbler.gd
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
@icon("res://addons/godot-xr-tools/editor/icons/rumble.svg")
class_name XRToolsRumbler
extends Node

##
## XR Tools Rumbler
##
## @desc:
## A node you attach to handle (contain and make easy to activate/cancel)
## a particular rumble event.
##


@export var event: XRToolsRumbleEvent


func rumble():
if is_instance_valid(event) and is_instance_valid(XRToolsRumbleManager.instance):
XRToolsRumbleManager.instance.set_event(self, event)


func cancel():
if is_instance_valid(XRToolsRumbleManager.instance):
XRToolsRumbleManager.instance.remove(self)


func rumble_if_player_body(body):
if XRTools.is_xr_class(body, "XRToolsPlayerBody"):
rumble()
Loading

0 comments on commit 5c9444e

Please sign in to comment.