├── .gitignore ├── Code ├── joystick.gd ├── Player.gd └── Knob.gd ├── art ├── joyring.png ├── joyknobpng.png ├── joyring.png.import └── joyknobpng.png.import ├── .gitattributes ├── project.godot ├── icon.svg ├── icon.svg.import └── Scenes ├── main.tscn └── joystick.tscn /.gitignore: -------------------------------------------------------------------------------- 1 | # Godot 4+ specific ignores 2 | .godot/ 3 | -------------------------------------------------------------------------------- /Code/joystick.gd: -------------------------------------------------------------------------------- 1 | extends Node2D 2 | 3 | var posVector: Vector2 4 | @export var deadzone = 15 5 | -------------------------------------------------------------------------------- /art/joyring.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AdamJStankiewicz/VirtualJoystick/HEAD/art/joyring.png -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Normalize EOL for all files that Git considers text files. 2 | * text=auto eol=lf 3 | -------------------------------------------------------------------------------- /art/joyknobpng.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AdamJStankiewicz/VirtualJoystick/HEAD/art/joyknobpng.png -------------------------------------------------------------------------------- /Code/Player.gd: -------------------------------------------------------------------------------- 1 | extends CharacterBody2D 2 | 3 | @onready var joystick = $"../Camera2D/Joystick" 4 | 5 | var speed = 300 6 | 7 | func _physics_process(delta): 8 | var direction = joystick.posVector 9 | if direction: 10 | velocity = direction * speed 11 | else: 12 | velocity = Vector2(0,0) 13 | 14 | move_and_slide() 15 | -------------------------------------------------------------------------------- /project.godot: -------------------------------------------------------------------------------- 1 | ; Engine configuration file. 2 | ; It's best edited using the editor UI and not directly, 3 | ; since the parameters that go here are not all obvious. 4 | ; 5 | ; Format: 6 | ; [section] ; section goes between [] 7 | ; param=value ; assign values to parameters 8 | 9 | config_version=5 10 | 11 | [application] 12 | 13 | config/name="joystick" 14 | run/main_scene="res://Scenes/main.tscn" 15 | config/features=PackedStringArray("4.1", "Forward Plus") 16 | config/icon="res://icon.svg" 17 | -------------------------------------------------------------------------------- /art/joyring.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://bidhxucp2kd66" 6 | path="res://.godot/imported/joyring.png-fac39d5eaf09b5c04c0ea0e420512486.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://Art/joyring.png" 14 | dest_files=["res://.godot/imported/joyring.png-fac39d5eaf09b5c04c0ea0e420512486.ctex"] 15 | 16 | [params] 17 | 18 | compress/mode=0 19 | compress/high_quality=false 20 | compress/lossy_quality=0.7 21 | compress/hdr_compression=1 22 | compress/normal_map=0 23 | compress/channel_pack=0 24 | mipmaps/generate=false 25 | mipmaps/limit=-1 26 | roughness/mode=0 27 | roughness/src_normal="" 28 | process/fix_alpha_border=true 29 | process/premult_alpha=false 30 | process/normal_map_invert_y=false 31 | process/hdr_as_srgb=false 32 | process/hdr_clamp_exposure=false 33 | process/size_limit=0 34 | detect_3d/compress_to=1 35 | -------------------------------------------------------------------------------- /art/joyknobpng.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://hg42snfhm6ei" 6 | path="res://.godot/imported/joyknobpng.png-d7c9cf91c5f377ad8fbeda25296eecf6.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://Art/joyknobpng.png" 14 | dest_files=["res://.godot/imported/joyknobpng.png-d7c9cf91c5f377ad8fbeda25296eecf6.ctex"] 15 | 16 | [params] 17 | 18 | compress/mode=0 19 | compress/high_quality=false 20 | compress/lossy_quality=0.7 21 | compress/hdr_compression=1 22 | compress/normal_map=0 23 | compress/channel_pack=0 24 | mipmaps/generate=false 25 | mipmaps/limit=-1 26 | roughness/mode=0 27 | roughness/src_normal="" 28 | process/fix_alpha_border=true 29 | process/premult_alpha=false 30 | process/normal_map_invert_y=false 31 | process/hdr_as_srgb=false 32 | process/hdr_clamp_exposure=false 33 | process/size_limit=0 34 | detect_3d/compress_to=1 35 | -------------------------------------------------------------------------------- /icon.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /icon.svg.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://dudqyrjsgcu5d" 6 | path="res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://icon.svg" 14 | dest_files=["res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex"] 15 | 16 | [params] 17 | 18 | compress/mode=0 19 | compress/high_quality=false 20 | compress/lossy_quality=0.7 21 | compress/hdr_compression=1 22 | compress/normal_map=0 23 | compress/channel_pack=0 24 | mipmaps/generate=false 25 | mipmaps/limit=-1 26 | roughness/mode=0 27 | roughness/src_normal="" 28 | process/fix_alpha_border=true 29 | process/premult_alpha=false 30 | process/normal_map_invert_y=false 31 | process/hdr_as_srgb=false 32 | process/hdr_clamp_exposure=false 33 | process/size_limit=0 34 | detect_3d/compress_to=1 35 | svg/scale=1.0 36 | editor/scale_with_editor_scale=false 37 | editor/convert_colors_with_editor_theme=false 38 | -------------------------------------------------------------------------------- /Scenes/main.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=5 format=3 uid="uid://d20ga7d1n8k7w"] 2 | 3 | [ext_resource type="PackedScene" uid="uid://dm6q0abakck7u" path="res://Scenes/joystick.tscn" id="1_s8jiq"] 4 | [ext_resource type="Texture2D" uid="uid://dudqyrjsgcu5d" path="res://icon.svg" id="2_0kghb"] 5 | [ext_resource type="Script" path="res://Code/Player.gd" id="2_fn81i"] 6 | 7 | [sub_resource type="RectangleShape2D" id="RectangleShape2D_5bqtj"] 8 | size = Vector2(129, 129) 9 | 10 | [node name="Main" type="Node2D"] 11 | 12 | [node name="Camera2D" type="Camera2D" parent="."] 13 | 14 | [node name="Joystick" parent="Camera2D" instance=ExtResource("1_s8jiq")] 15 | position = Vector2(-422, 163) 16 | scale = Vector2(3, 3) 17 | 18 | [node name="Player" type="CharacterBody2D" parent="."] 19 | script = ExtResource("2_fn81i") 20 | 21 | [node name="Sprite2D" type="Sprite2D" parent="Player"] 22 | texture = ExtResource("2_0kghb") 23 | 24 | [node name="CollisionShape2D" type="CollisionShape2D" parent="Player"] 25 | position = Vector2(-0.5, 0.5) 26 | shape = SubResource("RectangleShape2D_5bqtj") 27 | -------------------------------------------------------------------------------- /Scenes/joystick.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=5 format=3 uid="uid://dm6q0abakck7u"] 2 | 3 | [ext_resource type="Script" path="res://Code/joystick.gd" id="1_ii2vn"] 4 | [ext_resource type="Texture2D" uid="uid://bidhxucp2kd66" path="res://Art/joyring.png" id="2_w2jn3"] 5 | [ext_resource type="Texture2D" uid="uid://hg42snfhm6ei" path="res://Art/joyknobpng.png" id="3_voruw"] 6 | [ext_resource type="Script" path="res://Code/Knob.gd" id="4_g4kk1"] 7 | 8 | [node name="Joystick" type="Node2D"] 9 | script = ExtResource("1_ii2vn") 10 | 11 | [node name="Ring" type="Sprite2D" parent="."] 12 | scale = Vector2(0.055, 0.055) 13 | texture = ExtResource("2_w2jn3") 14 | 15 | [node name="Knob" type="Sprite2D" parent="."] 16 | scale = Vector2(0.02, 0.02) 17 | texture = ExtResource("3_voruw") 18 | script = ExtResource("4_g4kk1") 19 | 20 | [node name="Button" type="Button" parent="."] 21 | self_modulate = Color(1, 1, 1, 0) 22 | offset_left = -51.0 23 | offset_top = -51.0 24 | offset_right = 51.0 25 | offset_bottom = 52.0 26 | 27 | [connection signal="button_down" from="Button" to="Knob" method="_on_button_button_down"] 28 | [connection signal="button_up" from="Button" to="Knob" method="_on_button_button_up"] 29 | -------------------------------------------------------------------------------- /Code/Knob.gd: -------------------------------------------------------------------------------- 1 | extends Sprite2D 2 | 3 | @onready var parent = $".." 4 | 5 | var pressing = false 6 | 7 | @export var maxLength = 50 8 | var deadzone = 15 9 | 10 | func _ready(): 11 | deadzone = parent.deadzone 12 | maxLength *= parent.scale.x 13 | 14 | func _process(delta): 15 | if pressing: 16 | if get_global_mouse_position().distance_to(parent.global_position) <= maxLength: 17 | global_position = get_global_mouse_position() 18 | else: 19 | var angle = parent.global_position.angle_to_point(get_global_mouse_position()) 20 | global_position.x = parent.global_position.x + cos(angle)*maxLength 21 | global_position.y = parent.global_position.y + sin(angle)*maxLength 22 | calculateVector() 23 | else: 24 | global_position = lerp(global_position, parent.global_position, delta*50) 25 | parent.posVector = Vector2(0,0) 26 | 27 | func calculateVector(): 28 | if abs((global_position.x - parent.global_position.x)) >= deadzone: 29 | parent.posVector.x = (global_position.x - parent.global_position.x)/maxLength 30 | if abs((global_position.y - parent.global_position.y)) >= deadzone: 31 | parent.posVector.y = (global_position.y - parent.global_position.y)/maxLength 32 | 33 | func _on_button_button_down(): 34 | pressing = true 35 | 36 | 37 | func _on_button_button_up(): 38 | pressing = false 39 | --------------------------------------------------------------------------------