scpcb-godot/src/touch_screen_joystick.gd

364 lines
10 KiB
GDScript

@tool
extends Control
class_name TouchScreenJoystick
@export var use_textures : bool:
set(new_bool):
use_textures = new_bool
notify_property_list_changed()
@export var knob_color := Color.WHITE
@export var base_color := Color.WHITE
@export var background_color := Color(Color.BLACK, 0.25)
@export var base_radius := 130.0
@export var knob_radius := 65.0
@export var thickness := 1.8
@export var anti_aliased : bool
@export_group("Textures")
@export var use_custom_max_length : bool:
set(new_bool):
use_custom_max_length = new_bool
notify_property_list_changed()
@export var max_length := 120.0
@export var base_texture : Texture2D
@export var knob_texture : Texture2D
@export var background_texture : Texture2D
@export_group("Joystick Params")
@export_enum("FIXED", "DYNAMIC") var mode := 0
@export var deadzone := 10.0:
set(new_deadzone):
deadzone = clamp(new_deadzone, 10, base_radius)
@export var smooth_reset : bool:
set(new_bool):
smooth_reset = new_bool
notify_property_list_changed()
@export var smooth_speed := 5.0
@export var change_opacity_when_touched : bool:
set(new_bool):
change_opacity_when_touched = new_bool
notify_property_list_changed()
@export_range(0, 100, 0.01, "suffix:%") var from_opacity := 50.0
@export_range(0, 100, 0.01, "suffix:%") var to_opacity := 100.0
@export var use_input_actions : bool:
set(new_bool):
use_input_actions = new_bool
notify_property_list_changed()
@export_subgroup("Input Actions")
@export var action_left := "ui_left"
@export var action_right := "ui_right"
@export var action_up := "ui_up"
@export var action_down := "ui_down"
@export_group("Debug")
@export var draw_debugs : bool:
set(new_bool):
draw_debugs = new_bool
notify_property_list_changed()
@export var deadzone_color := Color(Color.RED, 0.5)
@export var current_max_length_color := Color(Color.BLUE, 0.5)
var is_pressing : bool
var knob_position : Vector2
var finger_index : int
var default_pos : Vector2
var enabled: bool = true
func _ready() -> void:
default_pos = position
change_opacity()
func _process(delta: float) -> void:
if Engine.is_editor_hint():
return
# checks if currently pressing
if is_pressing:
move_knob_pos()
else:
reset_knob_pos()
# update necessities
update_input_actions()
pivot_offset = size / 2
queue_redraw()
#moves the knob position when pressing
func move_knob_pos() -> void:
if get_distance() <= get_current_max_length():
knob_position = get_local_touch_pos()
else:
# calculates the angle position of the knob if it's position --
# -- exceeds from the current max length
var angle := get_center_pos().angle_to_point(get_global_mouse_position())
knob_position.x = (get_center_pos().x + cos(angle) * get_current_max_length()) - get_center_pos().x
knob_position.y = (get_center_pos().y + sin(angle) * get_current_max_length()) - get_center_pos().y
# triggers an specific input action based on the --
# -- current direction
func trigger_input_actions() -> void:
if enabled:
var dir := get_deadzoned_vector().normalized()
if dir.x > 0:
Input.action_release(action_left)
Input.action_press(action_right, dir.x)
else:
Input.action_release(action_right)
Input.action_press(action_left, -dir.x)
if dir.y < 0:
Input.action_release(action_down)
Input.action_press(action_up, -dir.y)
else:
Input.action_release(action_up)
Input.action_press(action_down, dir.y)
# releases all input actions
func release_input_actions() -> void:
if enabled:
Input.action_release(action_right)
Input.action_release(action_left)
Input.action_release(action_up)
Input.action_release(action_down)
# resets knob position if not pressing
func reset_knob_pos() -> void:
if smooth_reset:
knob_position = lerp(knob_position, Vector2.ZERO, smooth_speed * get_process_delta_time())
else:
knob_position = Vector2.ZERO
func _validate_property(property: Dictionary) -> void:
validitate_default_drawing_properties(property)
validitate_texture_drawing_properties(property)
validitate_input_action_properties(property)
if property.name == "smooth_speed" and not smooth_reset:
property.usage = PROPERTY_USAGE_READ_ONLY
if property.name == "from_opacity" and not change_opacity_when_touched:
property.usage = PROPERTY_USAGE_READ_ONLY
if property.name == "to_opacity" and not change_opacity_when_touched:
property.usage = PROPERTY_USAGE_READ_ONLY
if property.name == "deadzone_color" and not draw_debugs:
property.usage = PROPERTY_USAGE_READ_ONLY
if property.name == "current_max_length_color" and not draw_debugs:
property.usage = PROPERTY_USAGE_READ_ONLY
func validitate_input_action_properties(property : Dictionary) -> void:
if property.name == "action_left" and not use_input_actions:
property.usage = PROPERTY_USAGE_READ_ONLY
if property.name == "action_right" and not use_input_actions:
property.usage = PROPERTY_USAGE_READ_ONLY
if property.name == "action_up" and not use_input_actions:
property.usage = PROPERTY_USAGE_READ_ONLY
if property.name == "action_down" and not use_input_actions:
property.usage = PROPERTY_USAGE_READ_ONLY
func validitate_default_drawing_properties(property : Dictionary) -> void:
if property.name == "base_color" and use_textures:
property.usage = PROPERTY_USAGE_READ_ONLY
if property.name == "knob_color" and use_textures:
property.usage = PROPERTY_USAGE_READ_ONLY
if property.name == "background_color" and use_textures:
property.usage = PROPERTY_USAGE_READ_ONLY
if property.name == "base_radius" and use_textures:
property.usage = PROPERTY_USAGE_READ_ONLY
if property.name == "knob_radius" and use_textures:
property.usage = PROPERTY_USAGE_READ_ONLY
if property.name == "thickness" and use_textures:
property.usage = PROPERTY_USAGE_READ_ONLY
if property.name == "anti_aliased" and use_textures:
property.usage = PROPERTY_USAGE_READ_ONLY
func validitate_texture_drawing_properties(property : Dictionary) -> void:
if property.name == "background_texture" and not use_textures:
property.usage = PROPERTY_USAGE_READ_ONLY
if property.name == "use_custom_max_length" and not use_textures:
property.usage = PROPERTY_USAGE_READ_ONLY
if property.name == "max_length" and not use_textures:
property.usage = PROPERTY_USAGE_READ_ONLY
if property.name == "max_length" and not use_custom_max_length:
property.usage = PROPERTY_USAGE_READ_ONLY
if property.name == "base_texture" and not use_textures:
property.usage = PROPERTY_USAGE_READ_ONLY
if property.name == "knob_texture" and not use_textures:
property.usage = PROPERTY_USAGE_READ_ONLY
func _draw() -> void:
if not use_textures:
draw_default_joystick()
else:
draw_textured_joystick()
if draw_debugs:
draw_debug()
func draw_default_joystick() -> void:
draw_set_transform(size / 2)
# background
draw_circle(Vector2.ZERO, base_radius, background_color, true, -1.0, anti_aliased)
# base
draw_circle(Vector2.ZERO, base_radius, base_color, false, thickness, anti_aliased)
var pos := knob_position
# knob
draw_circle(pos, knob_radius, knob_color, true, -1.0, anti_aliased)
func draw_textured_joystick() -> void:
if background_texture:
var centered_base_pos := size / 2 - (base_texture.get_size() / 2)
draw_set_transform(centered_base_pos)
draw_texture_rect(background_texture, Rect2(Vector2.ZERO, base_texture.get_size()), false)
# draw textured base
if base_texture:
var centered_base_pos := size / 2 - (base_texture.get_size() / 2)
size.x = clamp(size.x, base_texture.get_size().x, INF)
size.y = clamp(size.y, base_texture.get_size().y, INF)
draw_set_transform(centered_base_pos)
draw_texture_rect(base_texture, Rect2(Vector2.ZERO, base_texture.get_size()), false)
# draw texture knob
if knob_texture:
var centered_knob_pos := (Vector2.ZERO - knob_texture.get_size() / 2) + size / 2
draw_set_transform(centered_knob_pos)
draw_texture_rect(knob_texture, Rect2(knob_position, knob_texture.get_size()), false)
func draw_debug() -> void:
draw_set_transform(size / 2)
# draw deadzone
draw_circle(Vector2.ZERO, deadzone, deadzone_color, false, 1.0, true)
# draw current max length
draw_circle(Vector2.ZERO, get_current_max_length(), current_max_length_color, false, 1.0, true)
draw_circle(knob_position, 10, Color.RED, true, -1.0, true)
func _input(event: InputEvent) -> void:
if event is InputEventScreenTouch:
var is_touching := event.pressed and get_global_rect().has_point(event.position) as bool
if is_touching:
on_touched(event)
else:
on_touch_released(event)
func on_touched(event: InputEventScreenTouch) -> void:
is_pressing = true
finger_index = event.index
change_opacity()
var mouse_pos := get_global_mouse_position() - size / 2
if mode == 1 and event.index == finger_index and get_global_rect().has_point(mouse_pos):
position = mouse_pos
#update_input_actions()
func on_touch_released(event: InputEventScreenTouch) -> void:
if event.index == finger_index:
is_pressing = false
if mode == 1:
position = default_pos
change_opacity()
#update_input_actions()
func update_input_actions() -> void:
if use_input_actions and is_pressing:
trigger_input_actions()
else:
release_input_actions()
func get_vector() -> Vector2:
return get_center_pos().direction_to(knob_position + get_center_pos())
func get_deadzoned_vector() -> Vector2:
var vector : Vector2
if is_pressing and not is_in_deadzone():
vector = get_center_pos().direction_to(knob_position + get_center_pos())
else:
vector = Vector2.ZERO
return vector
func get_center_pos() -> Vector2:
return position + size / 2
func get_local_touch_pos() -> Vector2:
return (get_global_mouse_position() - get_center_pos()) / scale.x
func get_distance() -> float:
return get_global_mouse_position().distance_to(get_center_pos()) / scale.x
# get the current max length of the knob's position. --
# -- if you use textures, the current max length will --
# -- automatically set to the half base texture's width
func get_current_max_length() -> float:
var curr_max_length : float
if not use_textures:
curr_max_length = base_radius
else:
if use_custom_max_length:
curr_max_length = max_length
elif not use_custom_max_length and base_texture:
curr_max_length = base_texture.get_size().x / 2
return curr_max_length
# changes the opacity when touched
func change_opacity() -> void:
if change_opacity_when_touched and not Engine.is_editor_hint():
if is_pressing:
modulate.a = to_opacity / 100.0
else:
modulate.a = from_opacity / 100.0
else:
modulate.a = 1.0
func is_in_deadzone() -> bool:
return get_distance() <= deadzone