aboutsummaryrefslogtreecommitdiff
path: root/client/player/onscreen_controls/virtual_joystick.gd
diff options
context:
space:
mode:
Diffstat (limited to 'client/player/onscreen_controls/virtual_joystick.gd')
-rw-r--r--client/player/onscreen_controls/virtual_joystick.gd161
1 files changed, 161 insertions, 0 deletions
diff --git a/client/player/onscreen_controls/virtual_joystick.gd b/client/player/onscreen_controls/virtual_joystick.gd
new file mode 100644
index 00000000..406de29b
--- /dev/null
+++ b/client/player/onscreen_controls/virtual_joystick.gd
@@ -0,0 +1,161 @@
+# Undercooked - a game about cooking
+# Copyright 2024 Marco F
+# 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,
+ SETTING,
+ WHEN_TOUCHED
+}
+
+@export var visibility_mode := Visibility_mode.SETTING
+
+@export var use_input_actions := true
+
+@export var action_left := "left"
+@export var action_right := "right"
+@export var action_up := "forward"
+@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()
+
+ if visibility_mode == Visibility_mode.SETTING and not Global.get_setting("touch_controls"):
+ 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)