1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
|
# Hurry Curry! - a game about cooking
# Copyright 2024 tpart
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, version 3 of the License only.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
#
class_name VirtualJoystick
extends Control
@export var pressed_color := Color.GRAY
@export_range(0, 200, 1) var deadzone_size : float = 10
@export_range(0, 500, 1) var clampzone_size : float = 75
enum Joystick_mode {
FIXED,
DYNAMIC,
FOLLOWING
}
@export var joystick_mode := Joystick_mode.FIXED
enum Visibility_mode {
ALWAYS,
TOUCHSCREEN_ONLY,
WHEN_TOUCHED
}
@export var visibility_mode := Visibility_mode.ALWAYS
@export var use_input_actions := true
@export var action_left := "left"
@export var action_right := "right"
@export var action_up := "forwards"
@export var action_down := "backwards"
var is_pressed := false
var output := Vector2.ZERO
var _touch_index : int = -1
@onready var _base := $Base
@onready var _tip := $Base/Tip
@onready var _base_default_position : Vector2 = _base.position
@onready var _tip_default_position : Vector2 = _tip.position
@onready var _default_color : Color = _tip.modulate
func _ready() -> void:
if not DisplayServer.is_touchscreen_available() and visibility_mode == Visibility_mode.TOUCHSCREEN_ONLY :
hide()
if visibility_mode == Visibility_mode.WHEN_TOUCHED:
hide()
func _input(event: InputEvent) -> void:
if event is InputEventScreenTouch:
if event.pressed:
if _is_point_inside_joystick_area(event.position) and _touch_index == -1:
if joystick_mode == Joystick_mode.DYNAMIC or joystick_mode == Joystick_mode.FOLLOWING or (joystick_mode == Joystick_mode.FIXED and _is_point_inside_base(event.position)):
if joystick_mode == Joystick_mode.DYNAMIC or joystick_mode == Joystick_mode.FOLLOWING:
_move_base(event.position)
if visibility_mode == Visibility_mode.WHEN_TOUCHED:
show()
_touch_index = event.index
_tip.modulate = pressed_color
_update_joystick(event.position)
get_viewport().set_input_as_handled()
elif event.index == _touch_index:
_reset()
if visibility_mode == Visibility_mode.WHEN_TOUCHED:
hide()
get_viewport().set_input_as_handled()
elif event is InputEventScreenDrag:
if event.index == _touch_index:
_update_joystick(event.position)
get_viewport().set_input_as_handled()
func _move_base(new_position: Vector2) -> void:
_base.global_position = new_position - _base.pivot_offset * get_global_transform_with_canvas().get_scale()
func _move_tip(new_position: Vector2) -> void:
_tip.global_position = new_position - _tip.pivot_offset * _base.get_global_transform_with_canvas().get_scale()
func _is_point_inside_joystick_area(point: Vector2) -> bool:
var x: bool = point.x >= global_position.x and point.x <= global_position.x + (size.x * get_global_transform_with_canvas().get_scale().x)
var y: bool = point.y >= global_position.y and point.y <= global_position.y + (size.y * get_global_transform_with_canvas().get_scale().y)
return x and y
func _get_base_radius() -> Vector2:
return _base.size * _base.get_global_transform_with_canvas().get_scale() / 2
func _is_point_inside_base(point: Vector2) -> bool:
var _base_radius = _get_base_radius()
var center : Vector2 = _base.global_position + _base_radius
var vector : Vector2 = point - center
if vector.length_squared() <= _base_radius.x * _base_radius.x:
return true
else:
return false
func _update_joystick(touch_position: Vector2) -> void:
var _base_radius = _get_base_radius()
var center : Vector2 = _base.global_position + _base_radius
var vector : Vector2 = touch_position - center
vector = vector.limit_length(clampzone_size)
if joystick_mode == Joystick_mode.FOLLOWING and touch_position.distance_to(center) > clampzone_size:
_move_base(touch_position - vector)
_move_tip(center + vector)
if vector.length_squared() > deadzone_size * deadzone_size:
is_pressed = true
output = (vector - (vector.normalized() * deadzone_size)) / (clampzone_size - deadzone_size)
else:
is_pressed = false
output = Vector2.ZERO
if use_input_actions:
if output.x > 0:
Input.action_release(action_left)
Input.action_press(action_right, output.x)
else:
Input.action_release(action_right)
Input.action_press(action_left, -output.x)
if output.y > 0:
Input.action_release(action_up)
Input.action_press(action_down, output.y)
else:
Input.action_release(action_down)
Input.action_press(action_up, -output.y)
func _reset():
is_pressed = false
output = Vector2.ZERO
_touch_index = -1
_tip.modulate = _default_color
_base.position = _base_default_position
_tip.position = _tip_default_position
if use_input_actions:
for action in [action_left, action_right, action_down, action_up]:
Input.action_release(action)
|