Skip to content

Commit

Permalink
Add Rumble Manager and Demo
Browse files Browse the repository at this point in the history
  • Loading branch information
lunarcloud committed Dec 1, 2023
1 parent c5c7433 commit e2e051e
Show file tree
Hide file tree
Showing 36 changed files with 1,011 additions and 34 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/
1 change: 1 addition & 0 deletions addons/godot-xr-tools/editor/icons/rumble.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
37 changes: 37 additions & 0 deletions addons/godot-xr-tools/editor/icons/rumble.svg.import
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
[remap]

importer="texture"
type="CompressedTexture2D"
uid="uid://dsexrsb8t0vi2"
path="res://.godot/imported/rumble.svg-104961f6551a931675972887ab17d2bc.ctex"
metadata={
"vram_texture": false
}

[deps]

source_file="res://addons/godot-xr-tools/editor/icons/rumble.svg"
dest_files=["res://.godot/imported/rumble.svg-104961f6551a931675972887ab17d2bc.ctex"]

[params]

compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
svg/scale=1.0
editor/scale_with_editor_scale=false
editor/convert_colors_with_editor_theme=false
8 changes: 8 additions & 0 deletions addons/godot-xr-tools/player/poke/poke.gd
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@ extends Node3D
## Signal emitted when this object pokes another object
signal pointing_event(event)

## Signal emitted when this object presses another object
signal pressed(body)

## Signal emitted when this object releases the press on another object
signal released(body)


# Default layer of 18:player-hands
const DEFAULT_LAYER := 0b0000_0000_0000_0010_0000_0000_0000_0000
Expand Down Expand Up @@ -204,6 +210,7 @@ func _on_PokeBody_body_contact_start(body):
# Report body pressed
target = body
last_collided_at = $PokeBody.global_transform.origin
pressed.emit(body)
XRToolsPointerEvent.entered(self, body, last_collided_at)
XRToolsPointerEvent.pressed(self, body, last_collided_at)

Expand All @@ -219,4 +226,5 @@ func _on_PokeBody_body_contact_end(body):
# Report release
XRToolsPointerEvent.released(self, target, last_collided_at)
XRToolsPointerEvent.exited(self, target, last_collided_at)
released.emit(target)
target = null
13 changes: 10 additions & 3 deletions addons/godot-xr-tools/player/poke/poke.tscn
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
[gd_scene load_steps=6 format=3 uid="uid://bjcxf427un2wp"]
[gd_scene load_steps=8 format=3 uid="uid://bjcxf427un2wp"]

[ext_resource type="Script" path="res://addons/godot-xr-tools/player/poke/poke.gd" id="1"]
[ext_resource type="Script" path="res://addons/godot-xr-tools/player/poke/poke_body.gd" id="2"]
[ext_resource type="Script" path="res://addons/godot-xr-tools/rumble/hand_aware_rumbler.gd" id="3_brn6j"]
[ext_resource type="Resource" uid="uid://brci6umrcd157" path="res://addons/godot-xr-tools/rumble/tap_rumble.tres" id="4_mskmk"]

[sub_resource type="SphereShape3D" id="1"]
resource_local_to_scene = true
Expand All @@ -14,7 +16,7 @@ height = 0.01
radial_segments = 32
rings = 16

[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_g1b1h"]
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_ktt27"]
transparency = 1
shading_mode = 0
albedo_color = Color(0.8, 0.8, 1, 0.5)
Expand All @@ -34,7 +36,12 @@ shape = SubResource("1")

[node name="MeshInstance" type="MeshInstance3D" parent="PokeBody"]
mesh = SubResource("2")
surface_material_override/0 = SubResource("StandardMaterial3D_g1b1h")
surface_material_override/0 = SubResource("StandardMaterial3D_ktt27")

[node name="XRToolsHandAwareRumbler" type="Node" parent="."]
script = ExtResource("3_brn6j")
event = ExtResource("4_mskmk")

[connection signal="pressed" from="." to="XRToolsHandAwareRumbler" method="rumble" unbinds=1]
[connection signal="body_contact_end" from="PokeBody" to="." method="_on_PokeBody_body_contact_end"]
[connection signal="body_contact_start" from="PokeBody" to="." method="_on_PokeBody_body_contact_start"]
8 changes: 8 additions & 0 deletions addons/godot-xr-tools/plugin.gd
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,14 @@ func _enter_tree():
"0.2,0.8,0.05",
0.7)

# Add input haptics_scale to the project settings
_define_project_setting(
"godot_xr_tools/input/haptics_scale",
TYPE_FLOAT,
PROPERTY_HINT_RANGE,
"0.0,1.0,0.1",
1.0)

# Add input y_axis_dead_zone to the project settings
_define_project_setting(
"godot_xr_tools/input/y_axis_dead_zone",
Expand Down
40 changes: 40 additions & 0 deletions addons/godot-xr-tools/rumble/hand_aware_rumbler.gd
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
@tool
class_name XRToolsHandAwareRumbler
extends XRToolsRumbler

## Rumbler which determines hand at runtime

func _ready():
if not event or Engine.is_editor_hint():
return
event = event.duplicate()
_assign_hand(XRHelpers.get_xr_controller(self))


func _assign_hand(controller: XRController3D) -> bool:
if not is_instance_valid(controller) or not event:
return false

if controller.tracker == "left_hand":
event.hand = XRToolsRumbleManager.Hand.LEFT
else:
event.hand = XRToolsRumbleManager.Hand.RIGHT
return true


## Rumble on the hand which owns the node
func rumble_hand(hand_child: Node3D) -> void:
if _assign_hand(XRHelpers.get_xr_controller(hand_child)):
rumble()


# This method verifies the hand has a valid configuration.
func _get_configuration_warnings() -> PackedStringArray:
var warnings := super()

# Check hand for mesh instance
if event and event.hand != XRToolsRumbleManager.Hand.BOTH:
warnings.append("Hand-Aware Rumbler's Event must be set to 'Both'")

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

## XR Tools Rumble Event Resource

## Strength of the rumbling
@export_range(0, 1, 0.10) var magnitude: float = 0.5

## Which hand(s)
@export var hand : XRToolsRumbleManager.Hand = XRToolsRumbleManager.Hand.BOTH

@export_category("Timing")

## Whether the rumble continues until cleared
@export var indefinite: bool = false

## Time to rumble (unless indefinite)
@export_range(10, 4000, 10) var duration_ms: int = 300

@export_category("Hints")

## Whether the rumble can be active during a tree pause
@export var active_during_pause: bool = false
184 changes: 184 additions & 0 deletions addons/godot-xr-tools/rumble/rumble_manager.gd
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
@tool
@icon("res://addons/godot-xr-tools/editor/icons/rumble.svg")
class_name XRToolsRumbleManager
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.

## Hand Specifier
enum Hand {
BOTH = 0, ## Rumble Both Hands
LEFT = 1, ## Rumble Left Hand
RIGHT = 2 ## Rumble Right Hand
}


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


## Initial "Wake-Up" Rumble Magnitude
@export var wakeup_rumble : XRToolsRumbleEvent


# All currently-active events (Dictionary<Variant, XRToolsRumbleEvent>)
var _events: Dictionary = {}

# All currently-active events' time remaining (Dictionary<Variant, int>)
var _time_remaining: Dictionary = {}


# Left Controller Reference
@onready var _controller_left_node: XRController3D = XRHelpers.get_left_controller(self)

# Right Controller Reference
@onready var _controller_right_node: XRController3D = XRHelpers.get_right_controller(self)


# Keep track of singular instance
static var _instance: XRToolsRumbleManager


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


## Get the default Haptics Scale value
static 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
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)


# Enforce singleton
func _enter_tree():
if not is_instance_valid(_instance):
_instance = self
else:
self.queue_free()


# Clear instance if this is the singleton
func _exit_tree():
if is_instance_valid(_instance) and _instance == self:
_instance = null


# On Ready
func _ready():
# Some rumble events are active during pause
process_mode = PROCESS_MODE_ALWAYS

if wakeup_rumble:
_add("wakeup", wakeup_rumble)


# Determine how much to - and perform the - rumbles each tick
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 = int(delta * 1000)

# 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

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

# Reduce the time remaining
_time_remaining[key] -= delta_ms

# 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 * XRToolsUserSettings.haptics_scale,
0.1,
0)

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


# actually set an event
func _add(event_key: Variant, event: XRToolsRumbleEvent) -> void:
if not event_key:
push_error("Event key is invalid! ")
return

if not event:
_remove(event_key)
return

_events[event_key] = event
_time_remaining[event_key] = event.duration_ms


## Adds the event to the list of currently-active rumbles
static func add(event_key: Variant, event: XRToolsRumbleEvent):
if is_instance_valid(event) and is_instance_valid(_instance):
#gdlint:ignore = private-method-call
_instance._add(event_key, event)


# Actually remove an event
func _remove(event_key: Variant) -> void:
if event_key != null:
_events.erase(event_key)
_time_remaining.erase(event_key)


## Removes the event from the list of currently-active rumbles
static func clear(event_key: Variant):
if is_instance_valid(_instance):
#gdlint:ignore = private-method-call
_instance._remove(event_key)
Loading

0 comments on commit e2e051e

Please sign in to comment.