Added touchscreen controls!

This commit is contained in:
2026-01-13 22:47:30 +01:00
parent 89a41397d1
commit 0c0998cd05
65 changed files with 1672 additions and 3270 deletions

View File

@@ -0,0 +1,135 @@
@tool
@icon("TouchScreenButtonControl.svg")
class_name TouchScreenButtonControl
extends TextureButton
const DefaultValues := {
"expand": true,
"ignore_texture_size": true,
"stretch_mode": TextureButton.STRETCH_KEEP_ASPECT_CENTERED,
"action_mode": TextureButton.ACTION_MODE_BUTTON_PRESS,
"focus_mode": TextureButton.FOCUS_NONE,
}
@export var use_default_values := true
@export var touchscreen_only := false
# The actual variable that holds the data
var input_action: String = ""
# 2. MATCHED the property name here...
func _get_property_list() -> Array:
var properties: Array = []
if Engine.is_editor_hint():
InputMap.load_from_project_settings()
# Get actions and format them for the dropdown
var actions = InputMap.get_actions()
var action_list = ",".join(actions)
properties.append({
"name": "input_action",
"type": TYPE_STRING,
"hint": PROPERTY_HINT_ENUM_SUGGESTION,
"hint_string": ",".join(actions),
"usage": PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_EDITOR
})
return properties
func _set(property: StringName, value: Variant) -> bool:
if property == "input_action":
input_action = value
return true
return false
func _get(property: StringName) -> Variant:
if property == "input_action":
return input_action
return null
# Track multiple simultaneous touches for multitouch support
var active_touches := {} # Dictionary: touch_index -> bool (true if pressed)
var initial_press_touches := {} # Dictionary: touch_index -> bool (true if this button was pressed on initial touch)
func _init():
if use_default_values:
for k in DefaultValues.keys():
self.set(k, DefaultValues.get(k))
# Set mouse_filter to IGNORE so the button doesn't consume touch events
# This allows multiple buttons to detect the same touch simultaneously
mouse_filter = Control.MOUSE_FILTER_IGNORE
if touchscreen_only and not DisplayServer.is_touchscreen_available():
hide()
func press():
var input_event: InputEvent = InputMap.action_get_events(input_action)[0]
input_event.pressed = true
Input.parse_input_event(input_event)
func release():
var input_event: InputEvent = InputMap.action_get_events(input_action)[0]
input_event.pressed = false
Input.parse_input_event(input_event)
func is_in(pos: Vector2) -> bool:
# Get the global rect of this Control node in screen coordinates
var global_rect = get_global_rect()
# Check if the touch position is within the button's global rect
if pos.x >= global_rect.position.x and pos.x <= global_rect.position.x + global_rect.size.x:
if pos.y >= global_rect.position.y and pos.y <= global_rect.position.y + global_rect.size.y:
return true
return false
func _input(event):
# Use _input to receive all touch events
# IMPORTANT: _input is called on ALL nodes in the scene tree, so multiple buttons
# can receive the same event. We don't consume the event so others can too.
if event is InputEventScreenTouch:
if event.pressed:
# Check if this touch is within this button's bounds
if is_in(event.position):
# Start tracking this touch for this button
if event.index not in active_touches:
active_touches[event.index] = true
initial_press_touches[event.index] = true # Mark as initial press
press()
else:
# Touch released - check if we were tracking it
if event.index in active_touches:
# This touch was being tracked by this button, release it
active_touches.erase(event.index)
initial_press_touches.erase(event.index)
# Only release the action if no other touches are active for this button
if active_touches.is_empty():
release()
elif event is InputEventScreenDrag:
# Handle touch drag with smart re-activation logic
if event.index in active_touches:
# We're tracking this touch
if initial_press_touches.has(event.index):
# This was an initial press - keep it active even if touch moves outside
# Don't release until touch ends
pass
else:
# This was a re-activation (touch moved back into bounds)
# Check if touch moved back out of bounds
if not is_in(event.position):
# Touch moved out of bounds, release this re-activated button
active_touches.erase(event.index)
if active_touches.is_empty():
release()
else:
# We're not tracking this touch - check if it moved into our bounds
# This allows "tapping" behavior - touch can re-enter and activate button
if is_in(event.position):
# Touch moved into bounds, start tracking (but not as initial press)
active_touches[event.index] = true
press()
# CRITICAL: Never call get_viewport().set_input_as_handled() here
# This would prevent other buttons from receiving the same touch event
# mouse_filter is set to IGNORE so the TextureButton doesn't consume mouse/touch events

View File

@@ -0,0 +1 @@
uid://bh5a3ydiu51eo

View File

@@ -0,0 +1 @@
<svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><defs/><g><path d="M3,1 C2.7249,1 2.4771,1.1087 2.2929,1.2929 C2.1087,1.4771 2,1.7249 2,2 L2,4 C2,4.2751 2.1087,4.5229 2.2929,4.7071 C2.4771,4.8913 2.7249,5 3,5 L5,5 L5,4 L3,4 L3,2 L11,2 L11,4 L9,4 L9,5 L11,5 C11.2751,5 11.5229,4.8913 11.7071,4.7071 C11.8913,4.5229 12,4.2751 12,4 L12,2 C12,1.7249 11.8913,1.4771 11.7071,1.2929 C11.5229,1.1087 11.2751,1 11,1 L3,1 Z M8,8 L8,4 C8,3.4015 7.5531,3 7,3 C6.4469,3 6,3.4015 6,4 L6,11.05 L3.5,9.25 C2.9376,8.8026 2.2507,8.8435 1.8125,9.3125 C1.3692,9.787 1.3561,10.4968 1.875,11 L6,15 L12,15 C12.5502,15 13.0459,14.7826 13.4142,14.4142 C13.7826,14.0459 14,13.5502 14,13 L14,10 C14,9.4498 13.7826,8.9541 13.4142,8.5858 C13.0459,8.2174 12.5502,8 12,8 L8,8 Z" fill="rgb(142,239,151)" opacity="1"/></g></svg>

After

Width:  |  Height:  |  Size: 807 B

View File

@@ -0,0 +1,43 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://ckvwnehwdvwyq"
path="res://.godot/imported/TouchScreenButtonControl.svg-ec8e7f48e271699010d76650daf1583d.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/touchscreenbuttoncontrol/TouchScreenButtonControl.svg"
dest_files=["res://.godot/imported/TouchScreenButtonControl.svg-ec8e7f48e271699010d76650daf1583d.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

View File

@@ -0,0 +1,6 @@
[gd_scene load_steps=2 format=3 uid="uid://dsj828xm2e28w"]
[ext_resource type="Script" path="res://addons/touchscreenbuttoncontrol/TouchScreenButtonControl.gd" id="1_5k641"]
[node name="TouchScreenButtonControl" type="TextureButton"]
script = ExtResource("1_5k641")

View File

@@ -0,0 +1,15 @@
@tool
extends EditorPlugin
var MyCustomNode = preload("res://addons/touchscreenbuttoncontrol/TouchScreenButtonControl.gd")
func _enter_tree():
add_custom_type(
"TouchScreenButtonControl",
"Control",
TouchScreenButtonControl,
preload("res://addons/touchscreenbuttoncontrol/TouchScreenButtonControl.svg")
)
func _exit_tree():
remove_custom_type("TouchScreenButtonControl")

View File

@@ -0,0 +1 @@
uid://cq0bml8epsqa

View File

@@ -0,0 +1,13 @@
[gd_resource type="StyleBoxFlat" format=3 uid="uid://cv5o175a2677e"]
[resource]
bg_color = Color(0.3, 0.3, 0.3, 0.7)
border_width_left = 2
border_width_top = 2
border_width_right = 2
border_width_bottom = 2
border_color = Color(0.8, 0.8, 0.8, 0.9)
corner_radius_top_left = 40
corner_radius_top_right = 40
corner_radius_bottom_right = 40
corner_radius_bottom_left = 40

View File

@@ -0,0 +1 @@
<svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><defs/><g><path d="M3,1 C2.7249,1 2.4771,1.1087 2.2929,1.2929 C2.1087,1.4771 2,1.7249 2,2 L2,4 C2,4.2751 2.1087,4.5229 2.2929,4.7071 C2.4771,4.8913 2.7249,5 3,5 L5,5 L5,4 L3,4 L3,2 L11,2 L11,4 L9,4 L9,5 L11,5 C11.2751,5 11.5229,4.8913 11.7071,4.7071 C11.8913,4.5229 12,4.2751 12,4 L12,2 C12,1.7249 11.8913,1.4771 11.7071,1.2929 C11.5229,1.1087 11.2751,1 11,1 L3,1 Z M8,8 L8,4 C8,3.4015 7.5531,3 7,3 C6.4469,3 6,3.4015 6,4 L6,11.05 L3.5,9.25 C2.9376,8.8026 2.2507,8.8435 1.8125,9.3125 C1.3692,9.787 1.3561,10.4968 1.875,11 L6,15 L12,15 C12.5502,15 13.0459,14.7826 13.4142,14.4142 C13.7826,14.0459 14,13.5502 14,13 L14,10 C14,9.4498 13.7826,8.9541 13.4142,8.5858 C13.0459,8.2174 12.5502,8 12,8 L8,8 Z" fill="rgb(142,239,151)" opacity="1"/></g></svg>

After

Width:  |  Height:  |  Size: 807 B

View File

@@ -0,0 +1,43 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://x5ggo5vlsqwn"
path="res://.godot/imported/icon.svg-e7770945a21c9fca528355bbf6b1708f.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/touchscreenbuttoncontrol/icon.svg"
dest_files=["res://.godot/imported/icon.svg-e7770945a21c9fca528355bbf6b1708f.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

View File

@@ -0,0 +1,7 @@
[plugin]
name="TouchScreenButtonControl"
description="A Control Node version to TouchScreenButton2D"
author="Matata.exe"
version="1.0"
script="TouchScreenButtonControlPlugin.gd"

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 7.5 KiB

View File

@@ -0,0 +1,43 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://ndaordejurrl"
path="res://.godot/imported/icon.svg-3eaf29e1973fdc580c89279657f85e37.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/virtual_joystick/icon.svg"
dest_files=["res://.godot/imported/icon.svg-3eaf29e1973fdc580c89279657f85e37.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

View File

@@ -0,0 +1,8 @@
[plugin]
name="Virtual Joystick"
description="Virtual Joystick is a lightweight and fully configurable plugin that adds a virtual joystick to the screen, ideal for mobile games or touch-based interfaces.
With it, you can control player movement or any other input action using a simple, responsive, and customizable visual stick."
author="Saulo de Souza"
version="1.0.7"
script="plugin.gd"

View File

@@ -0,0 +1,11 @@
@tool
extends EditorPlugin
var icon = preload("res://addons/virtual_joystick/icon.svg")
var script_main = preload("res://addons/virtual_joystick/virtual_joystick.gd")
func _enter_tree():
add_custom_type("VirtualJoystick", "Control", script_main, icon)
func _exit_tree():
remove_custom_type("VirtualJoystick")

View File

@@ -0,0 +1 @@
uid://e7m2yqefj838

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

View File

@@ -0,0 +1,40 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://mfjv5h7sdlhy"
path="res://.godot/imported/joystick_texture_1.png-9fe11098e467f8f7500bf2529275c515.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/virtual_joystick/resources/textures/joystick_texture_1.png"
dest_files=["res://.godot/imported/joystick_texture_1.png-9fe11098e467f8f7500bf2529275c515.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

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

View File

@@ -0,0 +1,40 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://cppp7p7l040i5"
path="res://.godot/imported/joystick_texture_2.png-bd8e129c570c51a41d34619540e549b1.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/virtual_joystick/resources/textures/joystick_texture_2.png"
dest_files=["res://.godot/imported/joystick_texture_2.png-bd8e129c570c51a41d34619540e549b1.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

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

View File

@@ -0,0 +1,40 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://ct6r0wagtmapd"
path="res://.godot/imported/joystick_texture_3.png-d28d8fdffe3dd01bcd800f8da8ffad5c.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/virtual_joystick/resources/textures/joystick_texture_3.png"
dest_files=["res://.godot/imported/joystick_texture_3.png-d28d8fdffe3dd01bcd800f8da8ffad5c.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

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

View File

@@ -0,0 +1,40 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://rxah3fc1lih3"
path="res://.godot/imported/joystick_texture_4.png-288403f1a78edebf82b0917d63d71545.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/virtual_joystick/resources/textures/joystick_texture_4.png"
dest_files=["res://.godot/imported/joystick_texture_4.png-288403f1a78edebf82b0917d63d71545.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

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

View File

@@ -0,0 +1,40 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://01qhightlcso"
path="res://.godot/imported/joystick_texture_5.png-b4d7bb661b1dfa9d44580770afad2948.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/virtual_joystick/resources/textures/joystick_texture_5.png"
dest_files=["res://.godot/imported/joystick_texture_5.png-b4d7bb661b1dfa9d44580770afad2948.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

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

View File

@@ -0,0 +1,40 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://don118m3unno4"
path="res://.godot/imported/joystick_texture_6.png-2b34481992ebc7f5529547a31713f9e4.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/virtual_joystick/resources/textures/joystick_texture_6.png"
dest_files=["res://.godot/imported/joystick_texture_6.png-2b34481992ebc7f5529547a31713f9e4.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

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

View File

@@ -0,0 +1,40 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://dv7m8ofd7vjkg"
path="res://.godot/imported/stick_texture_1.png-b7322c6c9fa9ceca47042e9525a31e3d.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/virtual_joystick/resources/textures/stick_texture_1.png"
dest_files=["res://.godot/imported/stick_texture_1.png-b7322c6c9fa9ceca47042e9525a31e3d.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

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View File

@@ -0,0 +1,40 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://bra3aeybagspi"
path="res://.godot/imported/stick_texture_2.png-56a0dc5972515a4c813acb0018d59897.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/virtual_joystick/resources/textures/stick_texture_2.png"
dest_files=["res://.godot/imported/stick_texture_2.png-56a0dc5972515a4c813acb0018d59897.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

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

View File

@@ -0,0 +1,40 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://b76m3oio3bpve"
path="res://.godot/imported/stick_texture_3.png-ac8c0e09219e9294012c766efb28a6dd.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/virtual_joystick/resources/textures/stick_texture_3.png"
dest_files=["res://.godot/imported/stick_texture_3.png-ac8c0e09219e9294012c766efb28a6dd.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

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -0,0 +1,40 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://dt4ujdvuod0ei"
path="res://.godot/imported/stick_texture_4.png-ea228bad22a241c375a4eb387cc66da7.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/virtual_joystick/resources/textures/stick_texture_4.png"
dest_files=["res://.godot/imported/stick_texture_4.png-ea228bad22a241c375a4eb387cc66da7.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

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

View File

@@ -0,0 +1,40 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://dywm4x5vqdtsm"
path="res://.godot/imported/stick_texture_5.png-f76da9fbec9ae506d29c25a5b2e273b4.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/virtual_joystick/resources/textures/stick_texture_5.png"
dest_files=["res://.godot/imported/stick_texture_5.png-f76da9fbec9ae506d29c25a5b2e273b4.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

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

View File

@@ -0,0 +1,40 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://fts4nb53arsl"
path="res://.godot/imported/stick_texture_6.png-0fdb4087c1eca258d05fa6e2a535cbbe.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/virtual_joystick/resources/textures/stick_texture_6.png"
dest_files=["res://.godot/imported/stick_texture_6.png-0fdb4087c1eca258d05fa6e2a535cbbe.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

View File

@@ -0,0 +1,496 @@
@tool
@icon("res://addons/virtual_joystick/icon.svg")
class_name VirtualJoystick
extends Control
#region Signals =================================================
## Emitted when the stick is moved.
signal analogic_changed(
value: Vector2,
distance: float,
angle: float,
angle_clockwise: float,
angle_not_clockwise: float
)
## Emitted when the stick enters the dead zone.
signal deadzone_enter
## Emitted when the stick leaves the dead zone.
signal deadzone_leave
#endregion Signals ===============================================
#region Private Properties ======================================
var _joystick: VirtualJoystickCircle
var _stick: VirtualJoystickCircle
var _joystick_radius: float = 100.0
var _joystick_border_width: float = 10.0
var _joystick_start_position: Vector2 = Vector2(_joystick_radius + _joystick_border_width, _joystick_radius + _joystick_border_width)
var _stick_radius: float = 45.0
var _stick_border_width: float = -1.0
var _stick_start_position: Vector2 = _joystick_start_position
var _drag_started_inside := false
var _click_in := false
var _delta: Vector2 = Vector2.ZERO
var _in_deadzone: bool = false:
set(value):
if value != _in_deadzone:
_in_deadzone = value
if not active:
return
if _in_deadzone:
deadzone_enter.emit()
else:
deadzone_leave.emit()
var _real_size: Vector2 = size * scale
var _warnings: PackedStringArray = []
var _DEFAULT_JOYSTICK_TEXTURE = preload("res://addons/virtual_joystick/resources/textures/joystick_texture_1.png")
var _JOYSTICK_TEXTURE_2 = preload("res://addons/virtual_joystick/resources/textures/joystick_texture_2.png")
var _JOYSTICK_TEXTURE_3 = preload("res://addons/virtual_joystick/resources/textures/joystick_texture_3.png")
var _JOYSTICK_TEXTURE_4 = preload("res://addons/virtual_joystick/resources/textures/joystick_texture_4.png")
var _JOYSTICK_TEXTURE_5 = preload("res://addons/virtual_joystick/resources/textures/joystick_texture_5.png")
var _JOYSTICK_TEXTURE_6 = preload("res://addons/virtual_joystick/resources/textures/joystick_texture_6.png")
var _DEFAULT_STICK_TEXTURE = preload("res://addons/virtual_joystick/resources/textures/stick_texture_1.png")
var _STICK_TEXTURE_2 = preload("res://addons/virtual_joystick/resources/textures/stick_texture_2.png")
var _STICK_TEXTURE_3 = preload("res://addons/virtual_joystick/resources/textures/stick_texture_3.png")
var _STICK_TEXTURE_4 = preload("res://addons/virtual_joystick/resources/textures/stick_texture_4.png")
var _STICK_TEXTURE_5 = preload("res://addons/virtual_joystick/resources/textures/stick_texture_5.png")
var _STICK_TEXTURE_6 = preload("res://addons/virtual_joystick/resources/textures/stick_texture_6.png")
enum _preset_enum {
## Nothing
NONE,
## Default preset texture
PRESET_DEFAULT,
## Texture 2
PRESET_2,
## Texture 3
PRESET_3,
## Texture 4
PRESET_4,
## Texture 5
PRESET_5,
## Texture 6
PRESET_6,
}
#endregion Private Properties ====================================
#region Public Properties =======================================
## Normalized joystick direction vector (X, Y).
var value: Vector2 = Vector2.ZERO
## Distance of the stick from the joystick center (0.0 to 1.0).
var distance: float = 0.0
## Angle in degrees (universal reference, 0° = right).
var angle_degrees: float = 0.0
## Angle in degrees, measured clockwise.
var angle_degrees_clockwise: float = 0.0
## Angle in degrees, measured counter-clockwise.
var angle_degrees_not_clockwise: float = 0.0
#endregion Public Properties =====================================
#region Exports ===================================================
@export_category("Virtual Joystick")
## Enables or disables the joystick input.
@export var active: bool = true
## Deadzone threshold (0.0 = off, 1.0 = full range).
@export_range(0.0, 0.9, 0.001, "suffix:length") var deadzone: float = 0.1
## Global scale factor of the joystick.
@export_range(0.1, 2.0, 0.001, "suffix:x", "or_greater") var scale_factor: float = 1.0:
set(value):
scale_factor = value
scale = Vector2(value, value)
_update_real_size()
queue_redraw()
## If true, the Joystick will only be displayed on the screen on mobile devices.
@export var only_mobile: bool = false:
set(value):
only_mobile = value
if only_mobile == true and OS.get_name().to_lower() not in ["android", "ios"]:
visible = false
else:
visible = true
@export_category("Joystick")
## Enable the use of textures for the joystick.
@export var joystick_use_textures: bool = true:
set(value):
joystick_use_textures = value
if value and joystick_texture == null:
_set_joystick_preset(joystick_preset_texture)
_verify_can_use_border()
update_configuration_warnings()
queue_redraw()
## Select one of the available models. More models will be available soon.
@export var joystick_preset_texture: _preset_enum = _preset_enum.PRESET_5: set = _set_joystick_preset
## Select a texture for the joystick figure.
@export var joystick_texture: Texture2D = _JOYSTICK_TEXTURE_5:
set(value):
joystick_texture = value
update_configuration_warnings()
_verify_can_use_border()
queue_redraw()
## Base color of the joystick background.
@export_color_no_alpha() var joystick_color: Color = Color.WHITE:
set(value):
joystick_color = value
if _joystick:
_joystick.color = value
_joystick.opacity = joystick_opacity
queue_redraw()
## Opacity of the joystick base.
@export_range(0.0, 1.0, 0.001, "suffix:alpha") var joystick_opacity: float = 0.8:
set(value):
joystick_opacity = value
if _joystick:
_joystick.opacity = value
queue_redraw()
## Width of the joystick base border.
@export_range(1.0, 20.0, 0.01, "suffix:px", "or_greater") var joystick_border: float = 1.0:
set(value):
joystick_border = value
_joystick.width = value
_joystick_border_width = value
_joystick_start_position = Vector2(_joystick_radius + _joystick_border_width, _joystick_radius + _joystick_border_width)
_joystick.position = _joystick_start_position
_stick_start_position = Vector2(_joystick_radius + _joystick_border_width, _joystick_radius + _joystick_border_width)
_stick.position = _stick_start_position
update_configuration_warnings()
queue_redraw()
@export_category("Stick")
## Enable the use of textures for the stick.
@export var stick_use_textures: bool = true:
set(value):
stick_use_textures = value
if value and stick_texture == null:
_set_stick_preset(stick_preset_texture)
update_configuration_warnings()
queue_redraw()
## Select one of the available models. More models will be available soon.
@export var stick_preset_texture: _preset_enum = _preset_enum.PRESET_5: set = _set_stick_preset
## Select a texture for the stick figure.
@export var stick_texture: Texture2D = _STICK_TEXTURE_5:
set(value):
stick_texture = value
update_configuration_warnings()
queue_redraw()
## Stick (thumb) color.
@export_color_no_alpha() var stick_color: Color = Color.WHITE:
set(value):
stick_color = value
if _stick:
_stick.color = value
_stick.opacity = stick_opacity
queue_redraw()
## Opacity of the stick.
@export_range(0.0, 1.0, 0.001, "suffix:alpha") var stick_opacity: float = 0.8:
set(value):
stick_opacity = value
if _stick:
_stick.opacity = value
queue_redraw()
#endregion Exports =================================================
#region Engine Methods =============================================
func _init() -> void:
_joystick = VirtualJoystickCircle.new(_joystick_start_position, _joystick_radius, _joystick_border_width, false, joystick_color, joystick_opacity)
_stick = VirtualJoystickCircle.new(_stick_start_position, _stick_radius, _stick_border_width, true, stick_color, stick_opacity)
queue_redraw()
func _ready() -> void:
set_size(Vector2(_joystick_radius * 2 + _joystick_border_width * 2, _joystick_radius * 2 + _joystick_border_width * 2))
_update_real_size()
func _draw() -> void:
if joystick_use_textures and joystick_texture:
var base_size = joystick_texture.get_size()
var base_scale = (_joystick_radius * 2) / base_size.x
draw_set_transform(_joystick.position, 0, Vector2(base_scale, base_scale))
draw_texture(joystick_texture, -base_size / 2, Color(joystick_color.r, joystick_color.g, joystick_color.b, joystick_opacity))
draw_set_transform(Vector2.ZERO, 0, Vector2.ONE)
else:
_joystick.draw(self, false)
if stick_use_textures and stick_texture:
var stick_size = stick_texture.get_size()
var stick_scale = (_stick_radius * 2) / stick_size.x
draw_set_transform(_stick.position, 0, Vector2(stick_scale, stick_scale))
draw_texture(stick_texture, -stick_size / 2, Color(stick_color.r, stick_color.g, stick_color.b, stick_opacity))
draw_set_transform(Vector2.ZERO, 0, Vector2.ONE)
else:
_stick.draw(self, false)
scale = Vector2(scale_factor, scale_factor)
set_size(Vector2((_joystick_radius * 2) + (_joystick_border_width * 2), (_joystick_radius * 2) + (_joystick_border_width * 2)))
func _gui_input(event: InputEvent) -> void:
if event is InputEventScreenTouch:
if event.pressed:
distance = event.position.distance_to(_joystick.position)
_drag_started_inside = distance <= _joystick.radius + _joystick.width / 2
if _drag_started_inside:
_click_in = true
_update_stick(event.position)
else:
_click_in = false
else:
if _click_in:
_reset_values()
_update_emit_signals()
_click_in = false
_drag_started_inside = false
_stick.position = _stick_start_position
queue_redraw()
elif event is InputEventScreenDrag:
if _drag_started_inside:
_update_stick(event.position)
func _get_configuration_warnings() -> PackedStringArray:
_warnings = []
if joystick_use_textures and (joystick_texture == null):
_warnings.append("The joystick_texture properties must be set when using joystick_use_textures = true.")
if stick_use_textures and (stick_texture == null):
_warnings.append("The stick_texture properties must be set when using stick_use_textures = true.")
if joystick_use_textures and joystick_texture != null and joystick_preset_texture != _preset_enum.NONE and joystick_border > 1.0:
_warnings.append("When using a texture preset, the ideal border height would be 1.0.")
return _warnings
#endregion Engine Methods =============================================
#region Private Methods ============================================
func _update_stick(_position: Vector2) -> void:
_delta = _position - _stick_start_position
if _delta.length() > _joystick.radius:
_delta = _delta.normalized() * _joystick.radius
_stick.position = _stick_start_position + _delta
queue_redraw()
var processed = _apply_deadzone(_delta / _joystick.radius)
value = processed.value
distance = processed.distance
angle_degrees = processed.angle_degrees
angle_degrees_clockwise = processed.angle_clockwise
angle_degrees_not_clockwise = processed.angle_not_clockwise
_update_emit_signals()
func _reset_values() -> void:
_delta = Vector2.ZERO
value = Vector2.ZERO
distance = 0.0
angle_degrees = 0.0
angle_degrees_clockwise = 0.0
angle_degrees_not_clockwise = 0.0
_stick.position = _stick_start_position
var length = (_delta / _joystick.radius).length()
var dz = clamp(deadzone, 0.0, 0.99)
if length <= dz:
_in_deadzone = true
queue_redraw()
## Applies linear deadzone adjustment and calculates resulting angles.
func _apply_deadzone(input_value: Vector2) -> Dictionary:
var length = input_value.length()
var result = Vector2.ZERO
var dz = clamp(deadzone, 0.0, 0.99)
if length <= dz:
_in_deadzone = true
result = Vector2.ZERO
length = 0.0
else:
_in_deadzone = false
# Re-scale linearly between deadzone and full range
var adjusted = (length - dz) / (1.0 - dz)
result = input_value.normalized() * adjusted
length = adjusted
var angle_cw = _get_angle_delta(result * _joystick.radius, true, true)
var angle_ccw = _get_angle_delta(result * _joystick.radius, true, false)
var angle = _get_angle_delta(result * _joystick.radius, false, false)
if active:
return {
"value": result,
"distance": length,
"angle_clockwise": angle_cw,
"angle_not_clockwise": angle_ccw,
"angle_degrees": angle
}
else:
return {
"value": Vector2.ZERO,
"distance": 0.0,
"angle_clockwise": 0.0,
"angle_not_clockwise": 0.0,
"angle_degrees": 0.0
}
func _update_emit_signals() -> void:
if not active:
return
if _in_deadzone:
analogic_changed.emit(
Vector2.ZERO,
0.0,
0.0,
0.0,
0.0
)
else:
analogic_changed.emit(
value,
distance,
angle_degrees,
angle_degrees_clockwise,
angle_degrees_not_clockwise
)
func _update_real_size() -> void:
_real_size = size * scale
pivot_offset = size / 2
## Calculates the angle of a vector in degrees.
func _get_angle_delta(delta: Vector2, continuous: bool, clockwise: bool) -> float:
var angle_deg = 0.0
if continuous and not clockwise:
angle_deg = rad_to_deg(atan2(-delta.y, delta.x))
else:
angle_deg = rad_to_deg(atan2(delta.y, delta.x))
if continuous and angle_deg < 0.0:
angle_deg += 360.0
return angle_deg
func _set_joystick_preset(_value: _preset_enum) -> void:
joystick_preset_texture = _value
match (_value):
_preset_enum.PRESET_DEFAULT:
joystick_texture = _DEFAULT_JOYSTICK_TEXTURE
_preset_enum.PRESET_2:
joystick_texture = _JOYSTICK_TEXTURE_2
_preset_enum.PRESET_3:
joystick_texture = _JOYSTICK_TEXTURE_3
_preset_enum.PRESET_4:
joystick_texture = _JOYSTICK_TEXTURE_4
_preset_enum.PRESET_5:
joystick_texture = _JOYSTICK_TEXTURE_5
_preset_enum.PRESET_6:
joystick_texture = _JOYSTICK_TEXTURE_6
_preset_enum.NONE:
if joystick_texture in [_DEFAULT_JOYSTICK_TEXTURE, _JOYSTICK_TEXTURE_2, _JOYSTICK_TEXTURE_3, _JOYSTICK_TEXTURE_4, _JOYSTICK_TEXTURE_5, _JOYSTICK_TEXTURE_6]:
joystick_texture = null
_verify_can_use_border()
update_configuration_warnings()
func _set_stick_preset(_value: _preset_enum) -> void:
stick_preset_texture = _value
match (_value):
_preset_enum.PRESET_DEFAULT:
stick_texture = _DEFAULT_STICK_TEXTURE
_preset_enum.PRESET_2:
stick_texture = _STICK_TEXTURE_2
_preset_enum.PRESET_3:
stick_texture = _STICK_TEXTURE_3
_preset_enum.PRESET_4:
stick_texture = _STICK_TEXTURE_4
_preset_enum.PRESET_5:
stick_texture = _STICK_TEXTURE_5
_preset_enum.PRESET_6:
stick_texture = _STICK_TEXTURE_6
_preset_enum.NONE:
if stick_texture in [_DEFAULT_STICK_TEXTURE, _STICK_TEXTURE_2, _STICK_TEXTURE_3, _STICK_TEXTURE_4, _STICK_TEXTURE_5, _STICK_TEXTURE_6]:
stick_texture = null
func _verify_can_use_border() -> bool:
if joystick_use_textures and not joystick_texture == null:
joystick_border = 1.0
return false
return true
#endregion Private Methods ===========================================
#region Public Methods =============================================
## Returns the current joystick vector value.
func get_value() -> Vector2:
return value
## Returns the joystick distance (0 to 1).
func get_distance() -> float:
return distance
## Returns the current joystick angle (clockwise).
func get_angle_degrees_clockwise() -> float:
return angle_degrees_clockwise
## Returns the current joystick angle (counter-clockwise).
func get_angle_degrees_not_clockwise() -> float:
return angle_degrees_not_clockwise
## Returns a specific angle configuration.
func get_angle_degrees(continuous: bool = true, clockwise: bool = false) -> float:
return _get_angle_delta(_delta, continuous, clockwise)
#endregion Public Methods ============================================
#region Classes ====================================================
class VirtualJoystickCircle extends RefCounted:
var position: Vector2
var radius: float
var color: Color
var width: float
var filled: bool
var antialiased: bool
var opacity: float:
set(value):
opacity = value
self.color.a = opacity
func _init(_position: Vector2, _radius: float, _width: float = -1.0, _filled: bool = true, _color: Color = Color.WHITE, _opacity: float = 1.0, _antialiased: bool = true):
self.position = _position
self.radius = _radius
self.color = _color
self.width = _width
self.filled = _filled
self.antialiased = _antialiased
self.opacity = _opacity
self.color.a = _opacity
func draw(canvas_item: CanvasItem, offset: bool) -> void:
var pos = self.position + (Vector2(self.radius, self.radius) if offset else Vector2.ZERO)
canvas_item.draw_circle(pos, self.radius, self.color, self.filled, self.width, self.antialiased)
#endregion Classes ===================================================

View File

@@ -0,0 +1 @@
uid://dhn1uax7qtt1v