Files
tekton/scripts/ui/virtual_joystick.gd
T

152 lines
4.5 KiB
GDScript

extends Control
# VirtualJoystick - Touch joystick for mobile movement control
# Provides 8-directional movement input
signal direction_changed(direction: Vector2i)
signal joystick_released
@export var dead_zone: float = 0.2
@export var clamp_zone: float = 0.8
@export var joystick_radius: float = 60.0
@export var knob_radius: float = 25.0
@export var repeat_delay: float = 0.3 # Initial delay before repeat
@export var repeat_rate: float = 0.15 # Repeat rate for continuous movement
var base_color: Color = Color(1, 1, 1, 0.4)
var knob_color: Color = Color(1, 1, 1, 0.7)
var pressed_color: Color = Color(0.4, 0.9, 0.4, 0.8)
var is_pressed: bool = false
var touch_index: int = -1
var center_position: Vector2
var current_direction: Vector2 = Vector2.ZERO
var last_grid_direction: Vector2i = Vector2i.ZERO
var _repeat_timer: float = 0.0
var _initial_repeat: bool = true
func _ready():
# Set minimum size for touch target
custom_minimum_size = Vector2(joystick_radius * 2 + 40, joystick_radius * 2 + 40)
center_position = size / 2
# Enable touch input
mouse_filter = Control.MOUSE_FILTER_STOP
set_process(true)
func _draw():
# Draw base circle
var base_circle_color = pressed_color if is_pressed else base_color
draw_circle(center_position, joystick_radius, base_circle_color)
draw_arc(center_position, joystick_radius, 0, TAU, 64, Color.WHITE, 2.0)
# Draw knob
var knob_pos = center_position + current_direction * joystick_radius * clamp_zone
var knob_circle_color = pressed_color if is_pressed else knob_color
draw_circle(knob_pos, knob_radius, knob_circle_color)
draw_arc(knob_pos, knob_radius, 0, TAU, 32, Color.WHITE, 1.5)
# Draw direction indicators (8 directions)
for i in range(8):
var angle = i * TAU / 8
var line_start = center_position + Vector2.from_angle(angle) * (joystick_radius * 0.6)
var line_end = center_position + Vector2.from_angle(angle) * (joystick_radius * 0.9)
draw_line(line_start, line_end, Color(1, 1, 1, 0.3), 2.0)
func _process(delta: float):
# Handle continuous movement while holding joystick
if is_pressed and last_grid_direction != Vector2i.ZERO:
_repeat_timer -= delta
if _repeat_timer <= 0:
emit_signal("direction_changed", last_grid_direction)
_repeat_timer = repeat_rate
_initial_repeat = false
func _gui_input(event: InputEvent):
if event is InputEventScreenTouch:
if event.pressed:
_start_touch(event.index, event.position)
elif event.index == touch_index:
_end_touch()
elif event is InputEventScreenDrag:
if event.index == touch_index:
_update_touch(event.position)
# Mouse support for testing
elif event is InputEventMouseButton:
if event.button_index == MOUSE_BUTTON_LEFT:
if event.pressed:
_start_touch(0, event.position)
else:
_end_touch()
elif event is InputEventMouseMotion:
if is_pressed and touch_index == 0:
_update_touch(event.position)
func _start_touch(index: int, pos: Vector2):
is_pressed = true
touch_index = index
_repeat_timer = repeat_delay # Use longer initial delay
_initial_repeat = true
_update_touch(pos)
queue_redraw()
func _end_touch():
is_pressed = false
touch_index = -1
current_direction = Vector2.ZERO
last_grid_direction = Vector2i.ZERO
_repeat_timer = 0.0
_initial_repeat = true
emit_signal("joystick_released")
queue_redraw()
func _update_touch(pos: Vector2):
var diff = pos - center_position
var distance = diff.length()
if distance > 0:
current_direction = diff.normalized() * clampf(distance / joystick_radius, 0, clamp_zone)
else:
current_direction = Vector2.ZERO
# Convert to 8-directional grid movement
var grid_dir = _get_grid_direction(current_direction)
if grid_dir != last_grid_direction:
last_grid_direction = grid_dir
if grid_dir != Vector2i.ZERO:
emit_signal("direction_changed", grid_dir)
queue_redraw()
func _get_grid_direction(dir: Vector2) -> Vector2i:
if dir.length() < dead_zone:
return Vector2i.ZERO
# Determine 8-directional output
var angle = dir.angle()
# Divide circle into 8 sectors (each 45 degrees)
var sector = int(round(angle / (TAU / 8))) % 8
if sector < 0:
sector += 8
match sector:
0: return Vector2i(1, 0) # East
1: return Vector2i(1, 1) # Southeast
2: return Vector2i(0, 1) # South
3: return Vector2i(-1, 1) # Southwest
4: return Vector2i(-1, 0) # West
5: return Vector2i(-1, -1) # Northwest
6: return Vector2i(0, -1) # North
7: return Vector2i(1, -1) # Northeast
return Vector2i.ZERO
func get_direction() -> Vector2i:
"""Get the current grid direction for polling."""
return last_grid_direction