immersive-home/app/addons/godot-xr-tools/interactables/interactable_joystick.gd
2024-03-16 01:16:08 +01:00

237 lines
8.1 KiB
GDScript

@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(name : String) -> bool:
return name == "XRToolsInteractableJoystick" or super(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 position := _do_move_joystick(Vector2(position_x, position_y))
if position.x == _joystick_x_position_rad and position.y == _joystick_y_position_rad:
return
# Update the current positon
_joystick_x_position_rad = position.x
_joystick_y_position_rad = position.y
joystick_x_position = rad_to_deg(position.x)
joystick_y_position = rad_to_deg(position.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 position := Vector2(deg_to_rad(value), _joystick_y_position_rad)
position = _do_move_joystick(position)
joystick_x_position = rad_to_deg(position.x)
_joystick_x_position_rad = position.x
# Called when joystick_y_position is set externally
func _set_joystick_y_position(value: float) -> void:
var position := Vector2(_joystick_x_position_rad, deg_to_rad(value))
position = _do_move_joystick(position)
joystick_y_position = rad_to_deg(position.y)
_joystick_y_position_rad = position.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(position: Vector2) -> Vector2:
# Apply joystick step-quantization
if _joystick_x_steps_rad:
position.x = round(position.x / _joystick_x_steps_rad) * _joystick_x_steps_rad
if _joystick_y_steps_rad:
position.y = round(position.y / _joystick_y_steps_rad) * _joystick_y_steps_rad
# Apply joystick limits
position.x = clamp(position.x, _joystick_x_limit_min_rad, _joystick_x_limit_max_rad)
position.y = clamp(position.y, _joystick_y_limit_min_rad, _joystick_y_limit_max_rad)
# Move if necessary
if position.x != _joystick_x_position_rad or position.y != _joystick_y_position_rad:
transform.basis = Basis.from_euler(Vector3(position.y, position.x, 0.0))
# Return the updated position
return position