This commit is contained in:
sharpoff
2026-01-14 13:01:37 +09:00
commit 49d7e22132
660 changed files with 41243 additions and 0 deletions

View File

@@ -0,0 +1,126 @@
@tool
class_name XRToolsInteractableAreaButton
extends Area3D
## XR Tools Interactable Area Button script
##
## The interactable area button detects objects and areas intering its
## area, and moves an associated button object using a tween to animate
## the movement.
## Button pressed event
signal button_pressed(button)
## Button released event
signal button_released(button)
## Button object
@export var button := NodePath()
## Displacement when pressed
@export var displacement : Vector3 = Vector3(0.0, -0.02, 0.0)
## Displacement duration
@export var duration : float = 0.1
## If true, the button is pressed
var pressed : bool = false
## Dictionary of trigger items pressing the button
var _trigger_items := {}
## Tween for animating button
var _tween: Tween
# Node references
@onready var _button: Node3D = get_node(button)
# Button positions
@onready var _button_up := _button.transform.origin
@onready var _button_down := _button_up + displacement
# Add support for is_xr_class on XRTools classes
func is_xr_class(xr_name: String) -> bool:
return xr_name == "XRToolsInteractableAreaButton"
# Called when the node enters the scene tree for the first time.
func _ready():
# Connect area signals
if area_entered.connect(_on_button_entered):
push_error("Unable to connect button area signal")
if area_exited.connect(_on_button_exited):
push_error("Unable to connect button area signal")
if body_entered.connect(_on_button_entered):
push_error("Unable to connect button area signal")
if body_exited.connect(_on_button_exited):
push_error("Unable to connect button area signal")
# Called when an area or body enters the button area
func _on_button_entered(item: Node3D) -> void:
# Add to the dictionary of trigger items
_trigger_items[item] = item
# Detect transition to pressed
if !pressed:
# Update state to pressed
pressed = true
# Kill the current tween
if _tween:
_tween.kill()
# Construct the button animation tween
_tween = create_tween()
_tween.set_trans(Tween.TRANS_LINEAR)
_tween.set_ease(Tween.EASE_IN_OUT)
_tween.tween_property(_button, "position", _button_down, duration)
# Emit the pressed signal
button_pressed.emit(self)
# Called when an area or body exits the button area
func _on_button_exited(item: Node3D) -> void:
# Remove from the dictionary of triggered items
_trigger_items.erase(item)
# Detect transition to released
if pressed and _trigger_items.is_empty():
# Update state to released
pressed = false
# Kill the current tween
if _tween:
_tween.kill()
# Construct the button animation tween
_tween = create_tween()
_tween.set_trans(Tween.TRANS_LINEAR)
_tween.set_ease(Tween.EASE_IN_OUT)
_tween.tween_property(_button, "position", _button_up, duration)
# Emit the released signal
button_released.emit(self)
# Check button configuration
func _get_configuration_warnings() -> PackedStringArray:
var warnings := PackedStringArray()
# Ensure a button has been specified
if not get_node_or_null(button):
warnings.append("Button node to animate must be specified")
# Ensure a valid duration
if duration <= 0.0:
warnings.append("Duration must be a positive number")
return warnings

View File

@@ -0,0 +1 @@
uid://dq042kibpfryy

View File

@@ -0,0 +1,8 @@
[gd_scene load_steps=2 format=3 uid="uid://cme66uldrjl2i"]
[ext_resource type="Script" uid="uid://dq042kibpfryy" path="res://addons/godot-xr-tools/interactables/interactable_area_button.gd" id="1"]
[node name="InteractableAreaButton" type="Area3D"]
collision_layer = 0
collision_mask = 131072
script = ExtResource("1")

View File

@@ -0,0 +1,88 @@
@tool
class_name XRToolsInteractableHandle
extends XRToolsPickable
## XR Tools Interactable Handle script
##
## The interactable handle is a (usually invisible) object extending from
## [XRToolsPickable] that can be grabbed by the player and is used to
## manipulate interactable objects.
##
## The interactible handle has an origin position of its parent. In order
## to position interactible handles on the interactible object, the handle
## should be placed under a parent handle-origin node, and the origin nodes
## position set as desired.
##
## When the handle is released, it snaps back to its parent origin. If the
## handle is pulled further than its snap distance, then the handle is
## automatically released.
## Distance from the handle origin to auto-snap the grab
@export var snap_distance : float = 0.3
# Handle origin spatial node
@onready var handle_origin: Node3D = get_parent()
# Add support for is_xr_class on XRTools classes
func is_xr_class(xr_name: String) -> bool:
return xr_name == "XRToolsInteractableHandle" or super(xr_name)
# Called when this handle is added to the scene
func _ready() -> void:
# In Godot 4 we must now manually call our super class ready function
super()
# Ensure we start at our origin
transform = Transform3D.IDENTITY
# Turn off processing - it will be turned on only when held
set_process(false)
# Called on every frame when the handle is held to check for snapping
func _process(_delta: float) -> void:
# Skip if not picked up
if not is_picked_up():
return
# If too far from the origin then drop the handle
var origin_pos = handle_origin.global_transform.origin
var handle_pos = global_transform.origin
if handle_pos.distance_to(origin_pos) > snap_distance:
drop()
# Called when the handle is picked up
func pick_up(by) -> void:
# Call the base-class to perform the pickup
super(by)
# Enable the process function while held
set_process(true)
# Called when the handle is dropped
func let_go(by: Node3D, _p_linear_velocity: Vector3, _p_angular_velocity: Vector3) -> void:
# Call the base-class to perform the drop, but with no velocity
super(by, Vector3.ZERO, Vector3.ZERO)
# Disable the process function as no-longer held
set_process(false)
# Snap the handle back to the origin
transform = Transform3D.IDENTITY
# Check handle configurationv
func _get_configuration_warnings() -> PackedStringArray:
var warnings := PackedStringArray()
if !transform.is_equal_approx(Transform3D.IDENTITY):
warnings.append("Interactable handle must have no transform from its parent handle origin")
return warnings

View File

@@ -0,0 +1 @@
uid://dlojnwxo25bky

View File

@@ -0,0 +1,10 @@
[gd_scene load_steps=2 format=3 uid="uid://bddj5m7ull6g0"]
[ext_resource type="Script" uid="uid://dlojnwxo25bky" path="res://addons/godot-xr-tools/interactables/interactable_handle.gd" id="1"]
[node name="InteractableHandle" type="RigidBody3D"]
collision_layer = 262144
collision_mask = 0
gravity_scale = 0.0
script = ExtResource("1")
picked_up_layer = 0

View File

@@ -0,0 +1,81 @@
@tool
class_name XRToolsInteractableHandleDriven
extends Node3D
## XR Tools Interactable Handle Driven script
##
## This is the base class for interactables driven by handles. It subscribes
## to all child handle picked_up and dropped signals, and maintains a list
## of all grabbed handles.
##
## When one or more handles are grabbed, the _process function is enabled
## to process the handle-driven movement.
## Signal called when this interactable is grabbed
signal grabbed(interactable)
## Signal called when this interactable is released
signal released(interactable)
# Array of handles currently grabbed
var grabbed_handles := Array()
# Add support for is_xr_class on XRTools classes
func is_xr_class(xr_name: String) -> bool:
return xr_name == "XRToolsInteractableHandleDriven"
# Called when the node enters the scene tree for the first time.
func _ready():
# Hook picked_up and dropped signals from all child handles
_hook_child_handles(self)
# Turn off processing until a handle is grabbed
set_process(false)
# Called when a handle is picked up
func _on_handle_picked_up(handle: XRToolsInteractableHandle) -> void:
# Append to the list of grabbed handles
grabbed_handles.append(handle)
# Enable processing
if grabbed_handles.size() == 1:
# Report grabbed
emit_signal("grabbed", self)
# Enable physics processing
set_process(true)
# Called when a handle is dropped
func _on_handle_dropped(handle: XRToolsInteractableHandle) -> void:
# Remove from the list of grabbed handles
grabbed_handles.erase(handle)
# Disable processing when we drop the last handle
if grabbed_handles.is_empty():
# Disable physics processing
set_process(false)
# Report released
emit_signal("released", self)
# Recursive function to hook picked_up and dropped signals in all child handles
func _hook_child_handles(node: Node) -> void:
# If this node is a handle then hook its handle signals
var handle := node as XRToolsInteractableHandle
if handle:
if handle.picked_up.connect(_on_handle_picked_up):
push_error("Unable to connect handle signal")
if handle.dropped.connect(_on_handle_dropped):
push_error("Unable to connect handle signal")
# Recurse into all children
for child in node.get_children():
_hook_child_handles(child)

View File

@@ -0,0 +1 @@
uid://jeeqtkcsy7ou

View File

@@ -0,0 +1,155 @@
@tool
class_name XRToolsInteractableHinge
extends XRToolsInteractableHandleDriven
## XR Tools Interactable Hinge script
##
## The interactable hinge is a hinge transform node controlled by the
## player through one or more [XRToolsInteractableHandle] instances.
##
## The hinge rotates itelf around its local X axis, and so should be
## placed as a child of a node to translate and rotate as appropriate.
##
## The interactable hinge is not a [RigidBody3D], and as such will not react
## to any collisions.
## Signal for hinge moved
signal hinge_moved(angle)
## Hinge minimum limit
@export var hinge_limit_min : float = -45.0: set = _set_hinge_limit_min
## Hinge maximum limit
@export var hinge_limit_max : float = 45.0: set = _set_hinge_limit_max
## Hinge step size (zero for no steps)
@export var hinge_steps : float = 0.0: set = _set_hinge_steps
## Hinge position
@export var hinge_position : float = 0.0: set = _set_hinge_position
## Default position
@export var default_position : float = 0.0: set = _set_default_position
## If true, the hinge moves to the default position when releases
@export var default_on_release : bool = false
# Hinge values in radians
@onready var _hinge_limit_min_rad : float = deg_to_rad(hinge_limit_min)
@onready var _hinge_limit_max_rad : float = deg_to_rad(hinge_limit_max)
@onready var _hinge_steps_rad : float = deg_to_rad(hinge_steps)
@onready var _hinge_position_rad : float = deg_to_rad(hinge_position)
@onready var _default_position_rad : float = deg_to_rad(default_position)
# Add support for is_xr_class on XRTools classes
func is_xr_class(xr_name: String) -> bool:
return xr_name == "XRToolsInteractableHinge" or super(xr_name)
# Called when the node enters the scene tree for the first time.
func _ready():
# In Godot 4 we must now manually call our super class ready function
super()
# Set the initial position to match the initial hinge position value
transform = Transform3D(
Basis.from_euler(Vector3(_hinge_position_rad, 0, 0)),
Vector3.ZERO
)
# Connect signals
if released.connect(_on_hinge_released):
push_error("Cannot connect hinge released signal")
# Called every frame when one or more handles are held by the player
func _process(_delta: float) -> void:
# Get the total handle angular offsets
var offset_sum := 0.0
for item in grabbed_handles:
var handle := item as XRToolsInteractableHandle
var to_handle: Vector3 = handle.global_transform.origin * global_transform
var to_handle_origin: Vector3 = handle.handle_origin.global_transform.origin * global_transform
to_handle.x = 0.0
to_handle_origin.x = 0.0
offset_sum += to_handle_origin.signed_angle_to(to_handle, Vector3.RIGHT)
# Average the angular offsets
var offset := offset_sum / grabbed_handles.size()
# Move the hinge by the requested offset
move_hinge(_hinge_position_rad + offset)
# Move the hinge to the specified position
func move_hinge(pos: float) -> void:
# Do the hinge move
pos = _do_move_hinge(pos)
if pos == _hinge_position_rad:
return
# Update the current positon
_hinge_position_rad = pos
hinge_position = rad_to_deg(pos)
# Emit the moved signal
emit_signal("hinge_moved", hinge_position)
# Handle release of hinge
func _on_hinge_released(_interactable: XRToolsInteractableHinge):
if default_on_release:
move_hinge(_default_position_rad)
# Called when hinge_limit_min is set externally
func _set_hinge_limit_min(value: float) -> void:
hinge_limit_min = value
_hinge_limit_min_rad = deg_to_rad(value)
# Called when hinge_limit_max is set externally
func _set_hinge_limit_max(value: float) -> void:
hinge_limit_max = value
_hinge_limit_max_rad = deg_to_rad(value)
# Called when hinge_steps is set externally
func _set_hinge_steps(value: float) -> void:
hinge_steps = value
_hinge_steps_rad = deg_to_rad(value)
# Called when hinge_position is set externally
func _set_hinge_position(value: float) -> void:
var pos = _do_move_hinge(deg_to_rad(value))
hinge_position = rad_to_deg(pos)
_hinge_position_rad = pos
# Called when default_position is set externally
func _set_default_position(value: float) -> void:
default_position = value
_default_position_rad = deg_to_rad(value)
# Do the hinge move
func _do_move_hinge(pos: float) -> float:
# Apply hinge step-quantization
if _hinge_steps_rad:
pos = round(pos / _hinge_steps_rad) * _hinge_steps_rad
# Apply hinge limits
pos = clamp(pos, _hinge_limit_min_rad, _hinge_limit_max_rad)
# Move if necessary
if pos != _hinge_position_rad:
transform.basis = Basis.from_euler(Vector3(pos, 0.0, 0.0))
# Return the updated position
return pos

View File

@@ -0,0 +1 @@
uid://c8gx2wc7hsxf5

View File

@@ -0,0 +1,6 @@
[gd_scene load_steps=2 format=3 uid="uid://bnvxqb33lltmj"]
[ext_resource type="Script" uid="uid://c8gx2wc7hsxf5" path="res://addons/godot-xr-tools/interactables/interactable_hinge.gd" id="1"]
[node name="InteractableHinge" type="Spatial"]
script = ExtResource("1")

View File

@@ -0,0 +1,236 @@
@tool
class_name XRToolsInteractableJoystick
extends XRToolsInteractableHandleDriven
## XR Tools Interactable Joystick script
##
## The interactable joystick is a joystick transform node controlled by the
## player through [XRToolsInteractableHandle] instances.
##
## The joystick rotates itelf around its local X/Y axes, and so should be
## placed as a child of a node to translate and rotate as appropriate.
##
## The interactable joystick is not a [RigidBody3D], and as such will not react
## to any collisions.
## Signal for hinge moved
signal joystick_moved(x_angle, y_angle)
## Constant for flattening a vector horizontally (X/Z only)
const VECTOR_XZ := Vector3(1.0, 0.0, 1.0)
## Constant for flattening a vector vertically (Y/Z only)
const VECTOR_YZ := Vector3(0.0, 1.0, 1.0)
## Joystick X minimum limit
@export var joystick_x_limit_min : float = -45.0: set = _set_joystick_x_limit_min
## Joystick X maximum limit
@export var joystick_x_limit_max : float = 45.0: set = _set_joystick_x_limit_max
## Joystick Y minimum limit
@export var joystick_y_limit_min : float = -45.0: set = _set_joystick_y_limit_min
## Joystick Y maximum limit
@export var joystick_y_limit_max : float = 45.0: set = _set_joystick_y_limit_max
## Joystick X step size (zero for no steps)
@export var joystick_x_steps : float = 0.0: set = _set_joystick_x_steps
## Joystick Y step size (zero for no steps)
@export var joystick_y_steps : float = 0.0: set = _set_joystick_y_steps
## Joystick X position
@export var joystick_x_position : float = 0.0: set = _set_joystick_x_position
## Joystick Y position
@export var joystick_y_position : float = 0.0: set = _set_joystick_y_position
## Default X position
@export var default_x_position : float = 0.0: set = _set_default_x_position
## Default Y position
@export var default_y_position : float = 0.0: set = _set_default_y_position
## If true, the joystick moves to the default position when released
@export var default_on_release : bool = false
# Joystick values in radians
@onready var _joystick_x_limit_min_rad : float = deg_to_rad(joystick_x_limit_min)
@onready var _joystick_x_limit_max_rad : float = deg_to_rad(joystick_x_limit_max)
@onready var _joystick_y_limit_min_rad : float = deg_to_rad(joystick_y_limit_min)
@onready var _joystick_y_limit_max_rad : float = deg_to_rad(joystick_y_limit_max)
@onready var _joystick_x_steps_rad : float = deg_to_rad(joystick_x_steps)
@onready var _joystick_y_steps_rad : float = deg_to_rad(joystick_y_steps)
@onready var _joystick_x_position_rad : float = deg_to_rad(joystick_x_position)
@onready var _joystick_y_position_rad : float = deg_to_rad(joystick_y_position)
@onready var _default_x_position_rad : float = deg_to_rad(default_x_position)
@onready var _default_y_position_rad : float = deg_to_rad(default_y_position)
# Add support for is_xr_class on XRTools classes
func is_xr_class(xr_name: String) -> bool:
return xr_name == "XRToolsInteractableJoystick" or super(xr_name)
# Called when the node enters the scene tree for the first time.
func _ready():
# In Godot 4 we must now manually call our super class ready function
super()
# Set the initial position to match the initial joystick position value
transform = Transform3D(
Basis.from_euler(Vector3(_joystick_y_position_rad, _joystick_x_position_rad, 0)),
Vector3.ZERO)
# Connect signals
if released.connect(_on_joystick_released):
push_error("Cannot connect joystick released signal")
# Called every frame when one or more handles are held by the player
func _process(_delta: float) -> void:
# Do not process in the editor
if Engine.is_editor_hint():
return
# Skip if no handles grabbed
if grabbed_handles.is_empty():
return
# Get the total handle angular offsets
var offset_x_sum := 0.0
var offset_y_sum := 0.0
for item in grabbed_handles:
var handle := item as XRToolsInteractableHandle
var to_handle: Vector3 = handle.global_transform.origin * global_transform
var to_handle_origin: Vector3 = handle.handle_origin.global_transform.origin * global_transform
var to_handle_x := to_handle * VECTOR_XZ
var to_handle_origin_x := to_handle_origin * VECTOR_XZ
offset_x_sum += to_handle_origin_x.signed_angle_to(to_handle_x, Vector3.UP)
var to_handle_y := to_handle * VECTOR_YZ
var to_handle_origin_y := to_handle_origin * VECTOR_YZ
offset_y_sum += to_handle_origin_y.signed_angle_to(to_handle_y, Vector3.RIGHT)
# Average the angular offsets
var offset_x := offset_x_sum / grabbed_handles.size()
var offset_y := offset_y_sum / grabbed_handles.size()
# Move the joystick by the requested offset
move_joystick(
_joystick_x_position_rad + offset_x,
_joystick_y_position_rad + offset_y)
# Move the joystick to the specified position
func move_joystick(position_x: float, position_y: float) -> void:
# Do the move
var pos := _do_move_joystick(Vector2(position_x, position_y))
if pos.x == _joystick_x_position_rad and pos.y == _joystick_y_position_rad:
return
# Update the current positon
_joystick_x_position_rad = pos.x
_joystick_y_position_rad = pos.y
joystick_x_position = rad_to_deg(pos.x)
joystick_y_position = rad_to_deg(pos.y)
# Emit the joystick signal
emit_signal("joystick_moved", joystick_x_position, joystick_y_position)
# Handle release of joystick
func _on_joystick_released(_interactable: XRToolsInteractableJoystick):
if default_on_release:
move_joystick(_default_x_position_rad, _default_y_position_rad)
# Called when joystick_x_limit_min is set externally
func _set_joystick_x_limit_min(value: float) -> void:
joystick_x_limit_min = value
_joystick_x_limit_min_rad = deg_to_rad(value)
# Called when joystick_y_limit_min is set externally
func _set_joystick_y_limit_min(value: float) -> void:
joystick_y_limit_min = value
_joystick_y_limit_min_rad = deg_to_rad(value)
# Called when joystick_x_limit_max is set externally
func _set_joystick_x_limit_max(value: float) -> void:
joystick_x_limit_max = value
_joystick_x_limit_max_rad = deg_to_rad(value)
# Called when joystick_y_limit_max is set externally
func _set_joystick_y_limit_max(value: float) -> void:
joystick_y_limit_max = value
_joystick_y_limit_max_rad = deg_to_rad(value)
# Called when joystick_x_steps is set externally
func _set_joystick_x_steps(value: float) -> void:
joystick_x_steps = value
_joystick_x_steps_rad = deg_to_rad(value)
# Called when joystick_y_steps is set externally
func _set_joystick_y_steps(value: float) -> void:
joystick_y_steps = value
_joystick_y_steps_rad = deg_to_rad(value)
# Called when joystick_x_position is set externally
func _set_joystick_x_position(value: float) -> void:
var pos := Vector2(deg_to_rad(value), _joystick_y_position_rad)
pos = _do_move_joystick(pos)
joystick_x_position = rad_to_deg(pos.x)
_joystick_x_position_rad = pos.x
# Called when joystick_y_position is set externally
func _set_joystick_y_position(value: float) -> void:
var pos := Vector2(_joystick_x_position_rad, deg_to_rad(value))
pos = _do_move_joystick(pos)
joystick_y_position = rad_to_deg(pos.y)
_joystick_y_position_rad = pos.y
# Called when default_x_position is set externally
func _set_default_x_position(value: float) -> void:
default_x_position = value
_default_x_position_rad = deg_to_rad(value)
# Called when default_y_position is set externally
func _set_default_y_position(value: float) -> void:
default_y_position = value
_default_y_position_rad = deg_to_rad(value)
# Do the joystick move
func _do_move_joystick(pos: Vector2) -> Vector2:
# Apply joystick step-quantization
if _joystick_x_steps_rad:
pos.x = round(pos.x / _joystick_x_steps_rad) * _joystick_x_steps_rad
if _joystick_y_steps_rad:
pos.y = round(pos.y / _joystick_y_steps_rad) * _joystick_y_steps_rad
# Apply joystick limits
pos.x = clamp(pos.x, _joystick_x_limit_min_rad, _joystick_x_limit_max_rad)
pos.y = clamp(pos.y, _joystick_y_limit_min_rad, _joystick_y_limit_max_rad)
# Move if necessary
if pos.x != _joystick_x_position_rad or pos.y != _joystick_y_position_rad:
transform.basis = Basis.from_euler(Vector3(pos.y, pos.x, 0.0))
# Return the updated position
return pos

View File

@@ -0,0 +1 @@
uid://c0j857ar3agno

View File

@@ -0,0 +1,6 @@
[gd_scene load_steps=2 format=3 uid="uid://du32t1mb2kd7x"]
[ext_resource type="Script" uid="uid://c0j857ar3agno" path="res://addons/godot-xr-tools/interactables/interactable_joystick.gd" id="1"]
[node name="InteractableJoystick" type="Spatial"]
script = ExtResource("1")

View File

@@ -0,0 +1,120 @@
@tool
class_name XRToolsInteractableSlider
extends XRToolsInteractableHandleDriven
## XR Tools Interactable Slider script
##
## The interactable slider is a slider transform node controlled by the
## player through [XRToolsInteractableHandle] instances.
##
## The slider translates itelf along its local X axis, and so should be
## placed as a child of a node to translate and rotate as appropriate.
##
## The interactable slider is not a [RigidBody3D], and as such will not react
## to any collisions.
## Signal for slider moved
signal slider_moved(position)
## Slider minimum limit
@export var slider_limit_min : float = 0.0
## Slider maximum limit
@export var slider_limit_max : float = 1.0
## Slider step size (zero for no steps)
@export var slider_steps : float = 0.0
## Slider position
@export var slider_position : float = 0.0: set = _set_slider_position
## Default position
@export var default_position : float = 0.0
## If true, the slider moves to the default position when released
@export var default_on_release : bool = false
# Add support for is_xr_class on XRTools classes
func is_xr_class(xr_name: String) -> bool:
return xr_name == "XRToolsInteractableSlider" or super(xr_name)
# Called when the node enters the scene tree for the first time.
func _ready() -> void:
# In Godot 4 we must now manually call our super class ready function
super()
# Set the initial position to match the initial slider position value
transform = Transform3D(
Basis.IDENTITY,
Vector3(slider_position, 0.0, 0.0)
)
# Connect signals
if released.connect(_on_slider_released):
push_error("Cannot connect slider released signal")
# Called every frame when one or more handles are held by the player
func _process(_delta: float) -> void:
# Get the total handle offsets
var offset_sum := Vector3.ZERO
for item in grabbed_handles:
var handle := item as XRToolsInteractableHandle
offset_sum += handle.global_transform.origin - handle.handle_origin.global_transform.origin
# Rotate the offset sum vector from global into local coordinate space
offset_sum = offset_sum * global_transform.basis
# Get the average displacement in the X axis
var offset := offset_sum.x / grabbed_handles.size()
# Move the slider by the requested offset
move_slider(slider_position + offset)
# Move the slider to the specified position
func move_slider(pos: float) -> void:
# Do the slider move
pos = _do_move_slider(pos)
if pos == slider_position:
return
# Update the current position
slider_position = pos
# Emit the moved signal
emit_signal("slider_moved", pos)
# Handle release of slider
func _on_slider_released(_interactable: XRToolsInteractableSlider):
if default_on_release:
move_slider(default_position)
# Called when the slider position is set externally
func _set_slider_position(pos: float) -> void:
pos = _do_move_slider(pos)
slider_position = pos
# Do the slider move
func _do_move_slider(pos: float) -> float:
# Apply slider step-quantization
if slider_steps:
pos = round(pos / slider_steps) * slider_steps
# Apply slider limits
pos = clamp(pos, slider_limit_min, slider_limit_max)
# Move if necessary
if pos != slider_position:
transform.origin.x = pos
# Return the updated position
return pos

View File

@@ -0,0 +1 @@
uid://cloxfr8r3b00u

View File

@@ -0,0 +1,6 @@
[gd_scene load_steps=2 format=3 uid="uid://b0r6edl74ddo7"]
[ext_resource type="Script" uid="uid://cloxfr8r3b00u" path="res://addons/godot-xr-tools/interactables/interactable_slider.gd" id="1"]
[node name="InteractableSlider" type="Node3D"]
script = ExtResource("1")