Input detection now has a buffer zone, where selections are only detected at the extreme edges of the joystick range, but are deselected only after moving through a larger range toward the centre. This creates a buffer zone where selections are unlikely to happen by accident due to worn-out or limited joystick ranges around the outside edge, and deselections are unlikely to happen until the user actually releases the joystick. Phantom inputs are now seemingly entirely eliminated.
191 lines
6.7 KiB
GDScript
191 lines
6.7 KiB
GDScript
@tool
|
|
class_name ControllerInputSymbolWheel
|
|
extends Control
|
|
|
|
@export() var wheel_characters : PackedStringArray = [] : set = __set_wheel_characters
|
|
@export_range(0.0, 10.0, 0.01) var unselected_radius : float = 1.5 : set = __set_unselected_radius
|
|
@export_range(0.0, 10.0, 0.01) var selected_radius : float = 1.5 : set = __set_selected_radius
|
|
@export_range(0.0, 1.0, 0.01) var font_scale : float = 0.45 : set = __set_font_scale
|
|
|
|
@export var unselected_scale : Vector2 = Vector2(0.5, 0.5) : set = __set_unselected_scale
|
|
@export var selected_scale : Vector2 = Vector2(1.0, 1.0) : set = __set_selected_scale
|
|
|
|
@export_range(0.0, 1.0, 0.01) var tween_speed : float = 0.15
|
|
|
|
@export_group("Inputs")
|
|
@export_subgroup("Joypad", "joypad_")
|
|
@export var joypad_symbol_up : InputEventJoypadMotion
|
|
@export var joypad_symbol_down : InputEventJoypadMotion
|
|
@export var joypad_symbol_left : InputEventJoypadMotion
|
|
@export var joypad_symbol_right : InputEventJoypadMotion
|
|
|
|
signal new_character(char:String)
|
|
|
|
var __symbol_labels : Array = []
|
|
var __selected_label : int = -1
|
|
var __selected : bool = false
|
|
|
|
|
|
func _init() -> void:
|
|
if not resized.is_connected(__spin_labels_around_centre):
|
|
resized.connect(__spin_labels_around_centre)
|
|
|
|
func _enter_tree() -> void:
|
|
__regenerate_labels()
|
|
|
|
func _exit_tree() -> void:
|
|
if resized.is_connected(__spin_labels_around_centre):
|
|
resized.disconnect(__spin_labels_around_centre)
|
|
|
|
func _input(event:InputEvent) -> void:
|
|
if not __selected: return
|
|
|
|
var symbols_count : int = __symbol_labels.size()
|
|
var index : int = __selected_label
|
|
var right_stick_vector : Vector2 = Input.get_vector(&"select_symbol_left", &"select_symbol_right", &"select_symbol_up", &"select_symbol_down")
|
|
if right_stick_vector.length() >= 0.95:
|
|
var stick_angle : float = ((Vector2.UP).rotated(right_stick_vector.angle() + (TAU / (symbols_count + symbols_count))).angle() + PI) / TAU
|
|
index = clamp(int(floor(stick_angle * symbols_count)), 0, symbols_count - 1)
|
|
elif right_stick_vector.length() < 0.8:
|
|
index = -1
|
|
|
|
__new_symbol_selection(index)
|
|
|
|
|
|
func select() -> void:
|
|
if __selected: return
|
|
__selected = true
|
|
__spin_labels_around_centre()
|
|
__initialise_input()
|
|
|
|
func deselect() -> void:
|
|
if not __selected: return
|
|
__selected = false
|
|
|
|
if __selected_label >= 0:
|
|
__deselect_symbol(__selected_label)
|
|
__selected_label = -1
|
|
__uninitialise_input()
|
|
|
|
__spin_labels_around_centre()
|
|
|
|
|
|
func __new_symbol_selection(new_selection:int) -> void:
|
|
if __selected_label == new_selection: return
|
|
|
|
if new_selection < 0:
|
|
var send_char : String = wheel_characters[__selected_label]
|
|
if not send_char.is_empty():
|
|
if send_char == "␣":
|
|
new_character.emit(" ")
|
|
else:
|
|
new_character.emit(send_char)
|
|
|
|
if __selected_label >= 0:
|
|
__deselect_symbol(__selected_label)
|
|
|
|
__selected_label = new_selection
|
|
|
|
if __selected_label >= 0:
|
|
__select_symbol(__selected_label)
|
|
|
|
func __select_symbol(selection:int) -> void:
|
|
var label : Label = __symbol_labels[selection]
|
|
label.z_index = 1
|
|
|
|
var label_tween_activate : Tween = get_tree().create_tween()
|
|
label_tween_activate.bind_node(label)
|
|
label_tween_activate.tween_property(label, "scale", selected_scale, tween_speed)
|
|
|
|
var position : Vector2 = __calculate_label_position(label, selection, selected_radius)
|
|
var label_tween_radius : Tween = get_tree().create_tween()
|
|
label_tween_radius.bind_node(label)
|
|
label_tween_radius.tween_property(label, "position", position, tween_speed)
|
|
|
|
func __deselect_symbol(selection:int) -> void:
|
|
var label : Label = __symbol_labels[selection]
|
|
label.z_index = 0
|
|
|
|
var label_tween_deactivate : Tween = get_tree().create_tween()
|
|
label_tween_deactivate.bind_node(label)
|
|
label_tween_deactivate.tween_property(label, "scale", unselected_scale, tween_speed)
|
|
|
|
var position : Vector2 = __calculate_label_position(label, selection, unselected_radius)
|
|
var label_tween_radius : Tween = get_tree().create_tween()
|
|
label_tween_radius.bind_node(label)
|
|
label_tween_radius.tween_property(label, "position", position, tween_speed)
|
|
|
|
func __calculate_label_position(label:Label, label_index:int, radius:float) -> Vector2:
|
|
var angle_slice : float = PI / float(__symbol_labels.size()) * 2.0
|
|
var min_size : int = min(size.x, size.y)
|
|
var max_label_size : int = max(label.size.x, label.size.y)
|
|
label.pivot_offset = label.size / 2.0
|
|
var position : Vector2 = (size / 2.0) - label.size / 2.0
|
|
position += (-Vector2.UP * radius).rotated(angle_slice * label_index) * ((min_size - max_label_size) / 3.0)
|
|
return position
|
|
|
|
|
|
func __regenerate_labels() -> void:
|
|
for i:Label in __symbol_labels:
|
|
i.queue_free()
|
|
__symbol_labels = []
|
|
for char:String in wheel_characters:
|
|
var label : Label = Label.new()
|
|
add_child(label)
|
|
label.text = char
|
|
label.horizontal_alignment = HorizontalAlignment.HORIZONTAL_ALIGNMENT_CENTER
|
|
label.vertical_alignment = VerticalAlignment.VERTICAL_ALIGNMENT_CENTER
|
|
label.size = Vector2(20.0, 20.0)
|
|
label.pivot_offset = label.size / 2.0
|
|
__symbol_labels.append(label)
|
|
__spin_labels_around_centre()
|
|
|
|
func __spin_labels_around_centre() -> void:
|
|
for i:int in range(__symbol_labels.size()):
|
|
var label : Label = __symbol_labels[i]
|
|
var max_label_size : int = max(label.size.x, label.size.y)
|
|
var radius : float = selected_radius if i == __selected_label else unselected_radius
|
|
label.scale = selected_scale if i == __selected_label else unselected_scale
|
|
label.position = __calculate_label_position(label, i, radius)
|
|
|
|
|
|
func __initialise_input() -> void:
|
|
InputMap.add_action(&"select_symbol_up", 0.0)
|
|
InputMap.action_add_event(&"select_symbol_up", joypad_symbol_up)
|
|
InputMap.add_action(&"select_symbol_down", 0.0)
|
|
InputMap.action_add_event(&"select_symbol_down", joypad_symbol_down)
|
|
InputMap.add_action(&"select_symbol_left", 0.0)
|
|
InputMap.action_add_event(&"select_symbol_left", joypad_symbol_left)
|
|
InputMap.add_action(&"select_symbol_right", 0.0)
|
|
InputMap.action_add_event(&"select_symbol_right", joypad_symbol_right)
|
|
|
|
func __uninitialise_input() -> void:
|
|
InputMap.action_erase_event(&"select_symbol_right", joypad_symbol_right)
|
|
InputMap.erase_action(&"select_symbol_right")
|
|
InputMap.action_erase_event(&"select_symbol_left", joypad_symbol_left)
|
|
InputMap.erase_action(&"select_symbol_left")
|
|
InputMap.action_erase_event(&"select_symbol_down", joypad_symbol_down)
|
|
InputMap.erase_action(&"select_symbol_down")
|
|
InputMap.action_erase_event(&"select_symbol_up", joypad_symbol_up)
|
|
InputMap.erase_action(&"select_symbol_up")
|
|
|
|
|
|
func __set_wheel_characters(characters:PackedStringArray) -> void:
|
|
wheel_characters = characters
|
|
__regenerate_labels()
|
|
|
|
func __set_selected_radius(r:float) -> void:
|
|
selected_radius = r
|
|
|
|
func __set_unselected_radius(r:float) -> void:
|
|
unselected_radius = r
|
|
|
|
func __set_font_scale(s:float) -> void:
|
|
font_scale = s
|
|
|
|
func __set_unselected_scale(s:Vector2) -> void:
|
|
unselected_scale = s
|
|
|
|
func __set_selected_scale(s:Vector2) -> void:
|
|
selected_scale = s
|