diff --git a/addons/virtual_joystick/plugin.cfg b/addons/virtual_joystick/plugin.cfg
new file mode 100644
index 0000000..9ae5585
--- /dev/null
+++ b/addons/virtual_joystick/plugin.cfg
@@ -0,0 +1,7 @@
+[plugin]
+
+name="Virtual Joystick"
+description="A simple virtual joystick for touchscreens, with useful options."
+author="Marco Fazio"
+version="1.0"
+script="virtual_joystick_plugin.gd"
diff --git a/addons/virtual_joystick/previews/CoverPreview.svg b/addons/virtual_joystick/previews/CoverPreview.svg
new file mode 100644
index 0000000..a6676a6
--- /dev/null
+++ b/addons/virtual_joystick/previews/CoverPreview.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/addons/virtual_joystick/previews/CoverPreview.svg.import b/addons/virtual_joystick/previews/CoverPreview.svg.import
new file mode 100644
index 0000000..e7c8f04
--- /dev/null
+++ b/addons/virtual_joystick/previews/CoverPreview.svg.import
@@ -0,0 +1,43 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://dyiqtaavx0hc6"
+path="res://.godot/imported/CoverPreview.svg-7c17dc7ed6007b5f94a5e30c30227a42.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/virtual_joystick/previews/CoverPreview.svg"
+dest_files=["res://.godot/imported/CoverPreview.svg-7c17dc7ed6007b5f94a5e30c30227a42.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=1.0
+editor/scale_with_editor_scale=false
+editor/convert_colors_with_editor_theme=false
diff --git a/addons/virtual_joystick/previews/Logo.svg b/addons/virtual_joystick/previews/Logo.svg
new file mode 100644
index 0000000..5953fdc
--- /dev/null
+++ b/addons/virtual_joystick/previews/Logo.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/addons/virtual_joystick/previews/Logo.svg.import b/addons/virtual_joystick/previews/Logo.svg.import
new file mode 100644
index 0000000..c12c272
--- /dev/null
+++ b/addons/virtual_joystick/previews/Logo.svg.import
@@ -0,0 +1,43 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://dauwds7y6kul0"
+path="res://.godot/imported/Logo.svg-ff6e2b43579b0b50d493f1bf1feaff55.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/virtual_joystick/previews/Logo.svg"
+dest_files=["res://.godot/imported/Logo.svg-ff6e2b43579b0b50d493f1bf1feaff55.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
+svg/scale=1.0
+editor/scale_with_editor_scale=false
+editor/convert_colors_with_editor_theme=false
diff --git a/addons/virtual_joystick/previews/ShowcasePreview.png b/addons/virtual_joystick/previews/ShowcasePreview.png
new file mode 100644
index 0000000..c332ceb
Binary files /dev/null and b/addons/virtual_joystick/previews/ShowcasePreview.png differ
diff --git a/addons/virtual_joystick/previews/ShowcasePreview.png.import b/addons/virtual_joystick/previews/ShowcasePreview.png.import
new file mode 100644
index 0000000..176cdf5
--- /dev/null
+++ b/addons/virtual_joystick/previews/ShowcasePreview.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://bjorj4u4mywl2"
+path="res://.godot/imported/ShowcasePreview.png-70f7a50f53e5fc814a0e306c3f1e556a.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/virtual_joystick/previews/ShowcasePreview.png"
+dest_files=["res://.godot/imported/ShowcasePreview.png-70f7a50f53e5fc814a0e306c3f1e556a.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/addons/virtual_joystick/previews/joystick_icon.png b/addons/virtual_joystick/previews/joystick_icon.png
new file mode 100644
index 0000000..9f32dd2
Binary files /dev/null and b/addons/virtual_joystick/previews/joystick_icon.png differ
diff --git a/addons/virtual_joystick/previews/joystick_icon.png.import b/addons/virtual_joystick/previews/joystick_icon.png.import
new file mode 100644
index 0000000..46340ad
--- /dev/null
+++ b/addons/virtual_joystick/previews/joystick_icon.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://bawnhue5kyj0a"
+path="res://.godot/imported/joystick_icon.png-6e8744e631946e765d6836c0ecb27227.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/virtual_joystick/previews/joystick_icon.png"
+dest_files=["res://.godot/imported/joystick_icon.png-6e8744e631946e765d6836c0ecb27227.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/addons/virtual_joystick/test/player.gd b/addons/virtual_joystick/test/player.gd
new file mode 100644
index 0000000..d9b3be1
--- /dev/null
+++ b/addons/virtual_joystick/test/player.gd
@@ -0,0 +1,23 @@
+extends Sprite2D
+
+@export var speed : float = 100
+
+@export var joystick_left : VirtualJoystick
+
+@export var joystick_right : VirtualJoystick
+
+var move_vector := Vector2.ZERO
+
+func _process(delta: float) -> void:
+ ## Movement using the joystick output:
+# if joystick_left and joystick_left.is_pressed:
+# position += joystick_left.output * speed * delta
+
+ ## Movement using Input functions:
+ move_vector = Vector2.ZERO
+ move_vector = Input.get_vector("ui_left","ui_right","ui_up","ui_down")
+ position += move_vector * speed * delta
+
+ # Rotation:
+ if joystick_right and joystick_right.is_pressed:
+ rotation = joystick_right.output.angle()
diff --git a/addons/virtual_joystick/test/player.gd.uid b/addons/virtual_joystick/test/player.gd.uid
new file mode 100644
index 0000000..3a0a2db
--- /dev/null
+++ b/addons/virtual_joystick/test/player.gd.uid
@@ -0,0 +1 @@
+uid://dbehmxybmfqcy
diff --git a/addons/virtual_joystick/test/test.tscn b/addons/virtual_joystick/test/test.tscn
new file mode 100644
index 0000000..fcccfdc
--- /dev/null
+++ b/addons/virtual_joystick/test/test.tscn
@@ -0,0 +1,34 @@
+[gd_scene format=3 uid="uid://bq2sqb1u1l5ve"]
+
+[ext_resource type="PackedScene" uid="uid://dmr0fcamx7t56" path="res://addons/virtual_joystick/virtual_joystick_scene.tscn" id="1_4k4lh"]
+[ext_resource type="Texture2D" uid="uid://dy743vehs0t1t" path="res://icon.svg" id="2_44wa8"]
+[ext_resource type="Script" uid="uid://dbehmxybmfqcy" path="res://addons/virtual_joystick/test/player.gd" id="3_dsmxw"]
+
+[node name="Test" type="Node2D" unique_id=349797032]
+
+[node name="UI" type="CanvasLayer" parent="." unique_id=1633235312]
+
+[node name="Virtual joystick left" parent="UI" unique_id=1988201088 instance=ExtResource("1_4k4lh")]
+offset_left = 159.0
+offset_top = -81.0
+offset_right = 459.0
+offset_bottom = 219.0
+
+[node name="Virtual joystick right" parent="UI" unique_id=1109766981 instance=ExtResource("1_4k4lh")]
+anchors_preset = 3
+anchor_left = 1.0
+anchor_right = 1.0
+offset_left = -232.0
+offset_top = -81.0
+offset_right = 68.0
+offset_bottom = 219.0
+grow_horizontal = 0
+joystick_mode = 1
+use_input_actions = false
+
+[node name="Player" type="Sprite2D" parent="." unique_id=1232070811 node_paths=PackedStringArray("joystick_left", "joystick_right")]
+position = Vector2(108, 134)
+texture = ExtResource("2_44wa8")
+script = ExtResource("3_dsmxw")
+joystick_left = NodePath("../UI/Virtual joystick left")
+joystick_right = NodePath("../UI/Virtual joystick right")
diff --git a/addons/virtual_joystick/textures/joystick_base_outline.png b/addons/virtual_joystick/textures/joystick_base_outline.png
new file mode 100644
index 0000000..7cae462
Binary files /dev/null and b/addons/virtual_joystick/textures/joystick_base_outline.png differ
diff --git a/addons/virtual_joystick/textures/joystick_base_outline.png.import b/addons/virtual_joystick/textures/joystick_base_outline.png.import
new file mode 100644
index 0000000..3be3a04
--- /dev/null
+++ b/addons/virtual_joystick/textures/joystick_base_outline.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://bm30au8mjfc2f"
+path="res://.godot/imported/joystick_base_outline.png-1529fbc0a23b5af9e961e1a3d047aa0b.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/virtual_joystick/textures/joystick_base_outline.png"
+dest_files=["res://.godot/imported/joystick_base_outline.png-1529fbc0a23b5af9e961e1a3d047aa0b.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/addons/virtual_joystick/textures/joystick_tip.png b/addons/virtual_joystick/textures/joystick_tip.png
new file mode 100644
index 0000000..28c57ac
Binary files /dev/null and b/addons/virtual_joystick/textures/joystick_tip.png differ
diff --git a/addons/virtual_joystick/textures/joystick_tip.png.import b/addons/virtual_joystick/textures/joystick_tip.png.import
new file mode 100644
index 0000000..251e89c
--- /dev/null
+++ b/addons/virtual_joystick/textures/joystick_tip.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://bkvmydfn5saxg"
+path="res://.godot/imported/joystick_tip.png-95989d4495f4fb8387b93ac5f53ed669.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/virtual_joystick/textures/joystick_tip.png"
+dest_files=["res://.godot/imported/joystick_tip.png-95989d4495f4fb8387b93ac5f53ed669.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/addons/virtual_joystick/textures/joystick_tip_arrows.png b/addons/virtual_joystick/textures/joystick_tip_arrows.png
new file mode 100644
index 0000000..b4caa99
Binary files /dev/null and b/addons/virtual_joystick/textures/joystick_tip_arrows.png differ
diff --git a/addons/virtual_joystick/textures/joystick_tip_arrows.png.import b/addons/virtual_joystick/textures/joystick_tip_arrows.png.import
new file mode 100644
index 0000000..4f584f4
--- /dev/null
+++ b/addons/virtual_joystick/textures/joystick_tip_arrows.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://dt13r06u87fib"
+path="res://.godot/imported/joystick_tip_arrows.png-cd5fb367005f4e3e79a192d69a489560.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/virtual_joystick/textures/joystick_tip_arrows.png"
+dest_files=["res://.godot/imported/joystick_tip_arrows.png-cd5fb367005f4e3e79a192d69a489560.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/addons/virtual_joystick/virtual_joystick.gd b/addons/virtual_joystick/virtual_joystick.gd
new file mode 100644
index 0000000..bb3efcf
--- /dev/null
+++ b/addons/virtual_joystick/virtual_joystick.gd
@@ -0,0 +1,174 @@
+class_name VirtualJoystick
+
+extends Control
+
+## A simple virtual joystick for touchscreens, with useful options.
+## Github: https://github.com/MarcoFazioRandom/Virtual-Joystick-Godot
+
+# EXPORTED VARIABLE
+
+## The color of the button when the joystick is pressed.
+@export var pressed_color := Color.GRAY
+
+## If the input is inside this range, the output is zero.
+@export_range(0, 200, 1) var deadzone_size : float = 10
+
+## The max distance the tip can reach.
+@export_range(0, 500, 1) var clampzone_size : float = 75
+
+enum Joystick_mode {
+ FIXED, ## The joystick doesn't move.
+ DYNAMIC, ## Every time the joystick area is pressed, the joystick position is set on the touched position.
+ FOLLOWING ## When the finger moves outside the joystick area, the joystick will follow it.
+}
+
+## If the joystick stays in the same position or appears on the touched position when touch is started
+@export var joystick_mode := Joystick_mode.FIXED
+
+enum Visibility_mode {
+ ALWAYS, ## Always visible
+ TOUCHSCREEN_ONLY, ## Visible on touch screens only
+ WHEN_TOUCHED ## Visible only when touched
+}
+
+## If the joystick is always visible, or is shown only if there is a touchscreen
+@export var visibility_mode := Visibility_mode.ALWAYS
+
+## If true, the joystick uses Input Actions (Project -> Project Settings -> Input Map)
+@export var use_input_actions := true
+
+@export var action_left := "ui_left"
+@export var action_right := "ui_right"
+@export var action_up := "ui_up"
+@export var action_down := "ui_down"
+
+# PUBLIC VARIABLES
+
+## If the joystick is receiving inputs.
+var is_pressed := false
+
+# The joystick output.
+var output := Vector2.ZERO
+
+# PRIVATE VARIABLES
+
+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
+
+# FUNCTIONS
+
+func _ready() -> void:
+ if ProjectSettings.get_setting("input_devices/pointing/emulate_mouse_from_touch"):
+ printerr("The Project Setting 'emulate_mouse_from_touch' should be set to False")
+ if not ProjectSettings.get_setting("input_devices/pointing/emulate_touch_from_mouse"):
+ printerr("The Project Setting 'emulate_touch_from_mouse' should be set to True")
+
+ 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:
+ # Release actions
+ if output.x >= 0 and Input.is_action_pressed(action_left):
+ Input.action_release(action_left)
+ if output.x <= 0 and Input.is_action_pressed(action_right):
+ Input.action_release(action_right)
+ if output.y >= 0 and Input.is_action_pressed(action_up):
+ Input.action_release(action_up)
+ if output.y <= 0 and Input.is_action_pressed(action_down):
+ Input.action_release(action_down)
+ # Press actions
+ if output.x < 0:
+ Input.action_press(action_left, -output.x)
+ if output.x > 0:
+ Input.action_press(action_right, output.x)
+ if output.y < 0:
+ Input.action_press(action_up, -output.y)
+ if output.y > 0:
+ Input.action_press(action_down, 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
+ # Release actions
+ if use_input_actions:
+ for action in [action_left, action_right, action_down, action_up]:
+ if Input.is_action_pressed(action):
+ Input.action_release(action)
diff --git a/addons/virtual_joystick/virtual_joystick.gd.uid b/addons/virtual_joystick/virtual_joystick.gd.uid
new file mode 100644
index 0000000..b434d19
--- /dev/null
+++ b/addons/virtual_joystick/virtual_joystick.gd.uid
@@ -0,0 +1 @@
+uid://chxcpeg4d1phj
diff --git a/addons/virtual_joystick/virtual_joystick_icon.png b/addons/virtual_joystick/virtual_joystick_icon.png
new file mode 100644
index 0000000..9f32dd2
Binary files /dev/null and b/addons/virtual_joystick/virtual_joystick_icon.png differ
diff --git a/addons/virtual_joystick/virtual_joystick_icon.png.import b/addons/virtual_joystick/virtual_joystick_icon.png.import
new file mode 100644
index 0000000..fe9cc9f
--- /dev/null
+++ b/addons/virtual_joystick/virtual_joystick_icon.png.import
@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://dyngdl4xiinc"
+path="res://.godot/imported/virtual_joystick_icon.png-de46b24999f9aaea71ffaa7771da41e6.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://addons/virtual_joystick/virtual_joystick_icon.png"
+dest_files=["res://.godot/imported/virtual_joystick_icon.png-de46b24999f9aaea71ffaa7771da41e6.ctex"]
+
+[params]
+
+compress/mode=0
+compress/high_quality=false
+compress/lossy_quality=0.7
+compress/uastc_level=0
+compress/rdo_quality_loss=0.0
+compress/hdr_compression=1
+compress/normal_map=0
+compress/channel_pack=0
+mipmaps/generate=false
+mipmaps/limit=-1
+roughness/mode=0
+roughness/src_normal=""
+process/channel_remap/red=0
+process/channel_remap/green=1
+process/channel_remap/blue=2
+process/channel_remap/alpha=3
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/normal_map_invert_y=false
+process/hdr_as_srgb=false
+process/hdr_clamp_exposure=false
+process/size_limit=0
+detect_3d/compress_to=1
diff --git a/addons/virtual_joystick/virtual_joystick_instantiator.gd b/addons/virtual_joystick/virtual_joystick_instantiator.gd
new file mode 100644
index 0000000..858fb1a
--- /dev/null
+++ b/addons/virtual_joystick/virtual_joystick_instantiator.gd
@@ -0,0 +1,17 @@
+@tool
+extends Control
+
+var scene
+
+func _enter_tree():
+ scene = preload("res://addons/virtual_joystick/virtual_joystick_scene.tscn").instantiate()
+ add_child(scene)
+
+ if ProjectSettings.get_setting("input_devices/pointing/emulate_mouse_from_touch"):
+ printerr("The Project Setting 'emulate_mouse_from_touch' should be set to False")
+ if not ProjectSettings.get_setting("input_devices/pointing/emulate_touch_from_mouse"):
+ printerr("The Project Setting 'emulate_touch_from_mouse' should be set to True")
+
+
+func _exit_tree():
+ scene.free()
diff --git a/addons/virtual_joystick/virtual_joystick_instantiator.gd.uid b/addons/virtual_joystick/virtual_joystick_instantiator.gd.uid
new file mode 100644
index 0000000..33a83ee
--- /dev/null
+++ b/addons/virtual_joystick/virtual_joystick_instantiator.gd.uid
@@ -0,0 +1 @@
+uid://b34c6nriehwpl
diff --git a/addons/virtual_joystick/virtual_joystick_plugin.gd b/addons/virtual_joystick/virtual_joystick_plugin.gd
new file mode 100644
index 0000000..5a11aca
--- /dev/null
+++ b/addons/virtual_joystick/virtual_joystick_plugin.gd
@@ -0,0 +1,10 @@
+@tool
+extends EditorPlugin
+
+
+func _enter_tree():
+ add_custom_type("Virtual Joystick", "Control", preload("virtual_joystick_instantiator.gd"), preload("virtual_joystick_icon.png"))
+
+
+func _exit_tree():
+ remove_custom_type("Virtual Joystick")
diff --git a/addons/virtual_joystick/virtual_joystick_plugin.gd.uid b/addons/virtual_joystick/virtual_joystick_plugin.gd.uid
new file mode 100644
index 0000000..f3f22fa
--- /dev/null
+++ b/addons/virtual_joystick/virtual_joystick_plugin.gd.uid
@@ -0,0 +1 @@
+uid://8psmrjhl03dp
diff --git a/addons/virtual_joystick/virtual_joystick_scene.tscn b/addons/virtual_joystick/virtual_joystick_scene.tscn
new file mode 100644
index 0000000..4fd6bba
--- /dev/null
+++ b/addons/virtual_joystick/virtual_joystick_scene.tscn
@@ -0,0 +1,53 @@
+[gd_scene format=3 uid="uid://dmr0fcamx7t56"]
+
+[ext_resource type="Script" uid="uid://chxcpeg4d1phj" path="res://addons/virtual_joystick/virtual_joystick.gd" id="1_8x4dy"]
+[ext_resource type="Texture2D" uid="uid://bm30au8mjfc2f" path="res://addons/virtual_joystick/textures/joystick_base_outline.png" id="2_jhjs2"]
+[ext_resource type="Texture2D" uid="uid://dt13r06u87fib" path="res://addons/virtual_joystick/textures/joystick_tip_arrows.png" id="3_3etdg"]
+
+[node name="Virtual Joystick" type="Control" unique_id=2019740770]
+layout_mode = 3
+anchors_preset = 2
+anchor_top = 1.0
+anchor_bottom = 1.0
+offset_left = -0.99999994
+offset_top = -104.0
+offset_right = 299.0
+offset_bottom = 196.0
+grow_vertical = 0
+scale = Vector2(0.35, 0.35)
+script = ExtResource("1_8x4dy")
+
+[node name="Base" type="TextureRect" parent="." unique_id=1338665392]
+layout_mode = 1
+anchors_preset = 8
+anchor_left = 0.5
+anchor_top = 0.5
+anchor_right = 0.5
+anchor_bottom = 0.5
+offset_left = -100.0
+offset_top = -100.0
+offset_right = 100.0
+offset_bottom = 100.0
+grow_horizontal = 2
+grow_vertical = 2
+pivot_offset = Vector2(100, 100)
+mouse_force_pass_scroll_events = false
+texture = ExtResource("2_jhjs2")
+stretch_mode = 5
+
+[node name="Tip" type="TextureRect" parent="Base" unique_id=1182099131]
+layout_mode = 1
+anchors_preset = 8
+anchor_left = 0.5
+anchor_top = 0.5
+anchor_right = 0.5
+anchor_bottom = 0.5
+offset_left = -50.0
+offset_top = -50.0
+offset_right = 50.0
+offset_bottom = 50.0
+grow_horizontal = 2
+grow_vertical = 2
+pivot_offset = Vector2(50, 50)
+texture = ExtResource("3_3etdg")
+stretch_mode = 5
diff --git a/export_presets.cfg b/export_presets.cfg
index 459c732..f28ad74 100644
--- a/export_presets.cfg
+++ b/export_presets.cfg
@@ -8,7 +8,7 @@ custom_features=""
export_filter="all_resources"
include_filter="*"
exclude_filter=""
-export_path="../../Downloads/2026-03-08 121752 The Third Place/index.html"
+export_path="../../Downloads/2026-03-10 105009 The Third Place/index.html"
patches=PackedStringArray()
patch_delta_encoding=false
patch_delta_compression_level_zstd=19
diff --git a/project.godot b/project.godot
index 8ff76a4..6c9ad09 100644
--- a/project.godot
+++ b/project.godot
@@ -61,6 +61,10 @@ shoot={
]
}
+[input_devices]
+
+pointing/emulate_mouse_from_touch=false
+
[rendering]
textures/canvas_textures/default_texture_filter=0
diff --git a/scenes/enemy_crt.gd b/scenes/enemy_crt.gd
index fd1d0c3..a0aa936 100644
--- a/scenes/enemy_crt.gd
+++ b/scenes/enemy_crt.gd
@@ -84,6 +84,7 @@ func _on_shoot_timer_timeout():
else:
$ShootTimer.start()
+
func _process(delta):
if stunned == true:
diff --git a/scenes/main.gd b/scenes/main.gd
index b333f1c..0c05efd 100644
--- a/scenes/main.gd
+++ b/scenes/main.gd
@@ -19,6 +19,7 @@ const ROWS: int = 3
@onready var start_button = $CanvasLayer/CenterContainer/Start
@onready var game_over = $CanvasLayer/CenterContainer/GameOver
+@onready var virtual_controls = $CanvasLayer/VirtualControls
func _ready():
game_over.hide()
@@ -29,6 +30,7 @@ func _ready():
EventBus.flash_screen.connect(_on_flash_screen)
EventBus.win_game.connect(_on_win_game)
EventBus.secret_win_game.connect(_on_secret_win_game)
+ virtual_controls.hide()
#func spawn_enemies():
@@ -139,6 +141,12 @@ func new_game():
func _input(EventInput):
+
+ if EventInput is InputEventScreenTouch:
+ if EventInput.pressed:
+ virtual_controls.show()
+
+
if EventInput.is_action_pressed("shoot") and playing == false:
print("Input detected!")
start_button.hide()
diff --git a/scenes/main.tscn b/scenes/main.tscn
index 6ccbab0..ed92e3f 100644
--- a/scenes/main.tscn
+++ b/scenes/main.tscn
@@ -2,6 +2,7 @@
[ext_resource type="Script" uid="uid://c51huloycn5as" path="res://scenes/main.gd" id="1_h2yge"]
[ext_resource type="PackedScene" uid="uid://tlcdxoupi2lb" path="res://scenes/parallax_clouds.tscn" id="2_5vw27"]
+[ext_resource type="PackedScene" uid="uid://dmr0fcamx7t56" path="res://addons/virtual_joystick/virtual_joystick_scene.tscn" id="3_jbj1t"]
[ext_resource type="Texture2D" uid="uid://b25w6ms7bxlhu" path="res://sprites/third space bgrnd.png" id="3_tipki"]
[ext_resource type="PackedScene" uid="uid://s6wf3egdqtmh" path="res://scenes/ui.tscn" id="4_1bvp3"]
[ext_resource type="Shader" uid="uid://x02irwg8ynvp" path="res://shaders/pixel_highlight.gdshader" id="4_272bh"]
@@ -11,6 +12,7 @@
[ext_resource type="Shader" uid="uid://blxo4qgrkjiup" path="res://shaders/flash.gdshader" id="8_eb6dy"]
[ext_resource type="PackedScene" uid="uid://cd1kwb8klcsb5" path="res://scenes/goose.tscn" id="9_85g3d"]
[ext_resource type="AudioStream" uid="uid://c3e8sdfqcikp6" path="res://sounds/welcome.wav" id="10_choun"]
+[ext_resource type="Texture2D" uid="uid://bkvmydfn5saxg" path="res://addons/virtual_joystick/textures/joystick_tip.png" id="10_muem4"]
[ext_resource type="PackedScene" uid="uid://drf1k1iulvy6f" path="res://scenes/wave.tscn" id="11_ya4ey"]
[ext_resource type="AudioStream" uid="uid://i61hg7wispq6" path="res://sounds/Ambient 2.0.wav" id="12_trceg"]
[ext_resource type="AudioStream" uid="uid://mjhprrmwn7qk" path="res://sounds/radio_tune.wav" id="14_a8y0u"]
@@ -67,6 +69,9 @@ shader_parameter/pixelate_line = 0
shader = ExtResource("8_eb6dy")
shader_parameter/intensity = 1.0
+[sub_resource type="CircleShape2D" id="CircleShape2D_dp3eg"]
+radius = 46.98
+
[sub_resource type="ShaderMaterial" id="ShaderMaterial_5vw27"]
[node name="Main" type="Node2D" unique_id=128010797]
@@ -119,6 +124,34 @@ offset_bottom = 320.0
size_flags_horizontal = 3
size_flags_vertical = 3
+[node name="VirtualControls" type="Node2D" parent="CanvasLayer" unique_id=728616058]
+
+[node name="Virtual Joystick" parent="CanvasLayer/VirtualControls" unique_id=2019740770 instance=ExtResource("3_jbj1t")]
+process_mode = 3
+modulate = Color(1, 1, 1, 0.44000003)
+z_index = 10
+anchors_preset = -1
+offset_left = -2.999999
+offset_top = 228.0
+offset_right = 297.0
+offset_bottom = 528.0
+pressed_color = Color(0.7477647, 0.718735, 0.89654267, 1)
+clampzone_size = 40.0
+visibility_mode = 1
+action_left = "left"
+action_right = "right"
+action_up = "up"
+action_down = "down"
+
+[node name="TouchScreenButton" type="TouchScreenButton" parent="CanvasLayer/VirtualControls" unique_id=1967993492]
+modulate = Color(1, 1, 1, 0.44000003)
+position = Vector2(180, 263)
+scale = Vector2(0.35, 0.35)
+texture_normal = ExtResource("10_muem4")
+shape = SubResource("CircleShape2D_dp3eg")
+action = "shoot"
+visibility_mode = 1
+
[node name="background" type="Sprite2D" parent="." unique_id=1790434669]
visible = false
z_index = -4
diff --git a/sounds/Ambient 1.0.wav.asd b/sounds/Ambient 1.0.wav.asd
new file mode 100644
index 0000000..90586d1
Binary files /dev/null and b/sounds/Ambient 1.0.wav.asd differ
diff --git a/sounds/HeadInflatePOP.wav.asd b/sounds/HeadInflatePOP.wav.asd
new file mode 100644
index 0000000..e1d091c
Binary files /dev/null and b/sounds/HeadInflatePOP.wav.asd differ