152 lines
4.5 KiB
GDScript
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
|