├── icon.png ├── .gitignore ├── default_env.tres ├── Drag_and_Drop_Sprite.tscn ├── icon.png.import ├── World.tscn ├── project.godot ├── LICENSE ├── Sprite.gd ├── Main.gd └── README.md /icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JCAguilera/better-drag-and-drop-sprite/HEAD/icon.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Godot-specific ignores 3 | .import/ 4 | export.cfg 5 | export_presets.cfg 6 | 7 | # Mono-specific ignores 8 | .mono/ 9 | -------------------------------------------------------------------------------- /default_env.tres: -------------------------------------------------------------------------------- 1 | [gd_resource type="Environment" load_steps=2 format=2] 2 | 3 | [sub_resource type="ProceduralSky" id=1] 4 | radiance_size = 4 5 | sky_top_color = Color( 0.0470588, 0.454902, 0.976471, 1 ) 6 | sky_horizon_color = Color( 0.556863, 0.823529, 0.909804, 1 ) 7 | sky_curve = 0.25 8 | ground_bottom_color = Color( 0.101961, 0.145098, 0.188235, 1 ) 9 | ground_horizon_color = Color( 0.482353, 0.788235, 0.952941, 1 ) 10 | ground_curve = 0.01 11 | sun_energy = 16.0 12 | 13 | [resource] 14 | background_mode = 2 15 | background_sky = SubResource( 1 ) 16 | fog_height_min = 0.0 17 | fog_height_max = 100.0 18 | ssao_quality = 0 19 | -------------------------------------------------------------------------------- /Drag_and_Drop_Sprite.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=4 format=2] 2 | 3 | [ext_resource path="res://icon.png" type="Texture" id=1] 4 | [ext_resource path="res://Sprite.gd" type="Script" id=2] 5 | 6 | [sub_resource type="RectangleShape2D" id=1] 7 | extents = Vector2( 31.0413, 30.1325 ) 8 | 9 | [node name="Sprite" type="Sprite"] 10 | texture = ExtResource( 1 ) 11 | script = ExtResource( 2 ) 12 | 13 | [node name="Area2D" type="Area2D" parent="."] 14 | 15 | [node name="CollisionShape2D" type="CollisionShape2D" parent="Area2D"] 16 | shape = SubResource( 1 ) 17 | [connection signal="mouse_entered" from="Area2D" to="." method="_on_Area2D_mouse_entered"] 18 | [connection signal="mouse_exited" from="Area2D" to="." method="_on_Area2D_mouse_exited"] 19 | -------------------------------------------------------------------------------- /icon.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/icon.png-487276ed1e3a0c39cad0279d744ee560.stex" 6 | metadata={ 7 | "vram_texture": false 8 | } 9 | 10 | [deps] 11 | 12 | source_file="res://icon.png" 13 | dest_files=[ "res://.import/icon.png-487276ed1e3a0c39cad0279d744ee560.stex" ] 14 | 15 | [params] 16 | 17 | compress/mode=0 18 | compress/lossy_quality=0.7 19 | compress/hdr_mode=0 20 | compress/bptc_ldr=0 21 | compress/normal_map=0 22 | flags/repeat=0 23 | flags/filter=true 24 | flags/mipmaps=false 25 | flags/anisotropic=false 26 | flags/srgb=2 27 | process/fix_alpha_border=true 28 | process/premult_alpha=false 29 | process/HDR_as_SRGB=false 30 | process/invert_color=false 31 | stream=false 32 | size_limit=0 33 | detect_3d=true 34 | svg/scale=1.0 35 | -------------------------------------------------------------------------------- /World.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=3 format=2] 2 | 3 | [ext_resource path="res://Main.gd" type="Script" id=1] 4 | [ext_resource path="res://Drag_and_Drop_Sprite.tscn" type="PackedScene" id=2] 5 | 6 | [node name="Node" type="Node"] 7 | script = ExtResource( 1 ) 8 | 9 | [node name="Sprite1" parent="." instance=ExtResource( 2 )] 10 | position = Vector2( 434, 233 ) 11 | 12 | [node name="Sprite2" parent="." instance=ExtResource( 2 )] 13 | position = Vector2( 203, 143 ) 14 | z_index = 1 15 | 16 | [node name="Sprite3" parent="." instance=ExtResource( 2 )] 17 | position = Vector2( 611, 103 ) 18 | z_index = 2 19 | 20 | [node name="Status" type="RichTextLabel" parent="."] 21 | margin_left = 13.0 22 | margin_top = 531.0 23 | margin_right = 737.0 24 | margin_bottom = 547.0 25 | text = "dummy" 26 | __meta__ = { 27 | "_edit_use_anchors_": false 28 | } 29 | 30 | [node name="SpriteList" type="RichTextLabel" parent="."] 31 | margin_left = 13.0 32 | margin_top = 555.0 33 | margin_right = 737.0 34 | margin_bottom = 575.0 35 | text = "dummy" 36 | -------------------------------------------------------------------------------- /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=4 10 | 11 | _global_script_classes=[ ] 12 | _global_script_class_icons={ 13 | 14 | } 15 | 16 | [application] 17 | 18 | config/name="Better Drag and Drop" 19 | run/main_scene="res://World.tscn" 20 | config/icon="res://icon.png" 21 | 22 | [input] 23 | 24 | left_click={ 25 | "deadzone": 0.5, 26 | "events": [ Object(InputEventMouseButton,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"button_mask":0,"position":Vector2( 0, 0 ),"global_position":Vector2( 0, 0 ),"factor":1.0,"button_index":1,"pressed":false,"doubleclick":false,"script":null) 27 | ] 28 | } 29 | 30 | [rendering] 31 | 32 | environment/default_environment="res://default_env.tres" 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 JuanCarlos Aguilera 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Sprite.gd: -------------------------------------------------------------------------------- 1 | extends Sprite 2 | 3 | var mouse_in = false 4 | var dragging = false 5 | var mouse_to_center_set = false 6 | var mouse_to_center 7 | var sprite_pos 8 | var mouse_pos 9 | 10 | func _input(_event): 11 | if (Input.is_action_just_pressed("left_click") && mouse_in): #When clicking 12 | #First we set mouse_to_center as a static vector 13 | #for preventing the sprite to move its center to the mouse position 14 | mouse_pos = get_viewport().get_mouse_position() 15 | mouse_to_center = restaVectores(self.position, mouse_pos) 16 | 17 | func _process(_delta): 18 | if (dragging): 19 | mouse_pos = get_viewport().get_mouse_position() 20 | #Set the position of the sprite to 21 | #mouse position + static mouse_to_center vector 22 | set_position(sumaVectores(mouse_pos, mouse_to_center)) 23 | 24 | func _on_Area2D_mouse_entered(): 25 | mouse_in = true 26 | get_parent()._add_sprite(self) #Add the sprite to the sprite list 27 | 28 | func _on_Area2D_mouse_exited(): 29 | mouse_in = false 30 | get_parent()._remove_sprite(self) #Remove the sprite from the sprite list 31 | 32 | func restaVectores(v1, v2): #vector substraction 33 | return Vector2(v1.x - v2.x, v1.y - v2.y) 34 | 35 | func sumaVectores(v1, v2): #vector sum 36 | return Vector2(v1.x + v2.x, v1.y + v2.y) 37 | -------------------------------------------------------------------------------- /Main.gd: -------------------------------------------------------------------------------- 1 | # Made with love by JuanCarlos "Juanky" Aguilera 2 | # Github Repo: https://github.com/JCAguilera/godot3-drag-and-drop 3 | 4 | extends Node 5 | 6 | var sprites = [] 7 | var top_sprite = null 8 | 9 | onready var status = $Status 10 | onready var spriteList = $SpriteList 11 | 12 | func _input(_event): 13 | if Input.is_action_just_pressed("left_click"): #When we click 14 | top_sprite = _top_sprite() #Get the sprite on top (largest z_index) 15 | if top_sprite: #If there's a sprite 16 | top_sprite.dragging = true #We set dragging to true 17 | if Input.is_action_just_released("left_click"): #When we release 18 | if top_sprite: 19 | top_sprite.dragging = false #Set dragging to false 20 | top_sprite = null #Top sprite to null 21 | _print_status() 22 | 23 | class SpritesSorter: #Custom sorter 24 | static func z_index(a, b): #Sort by z_index 25 | if a.z_index > b.z_index: 26 | return true 27 | return false 28 | 29 | func _add_sprite(sprt): #Add sprite to list 30 | if not sprites.find(sprt) == -1: #If sprite exists 31 | return #Do nothing 32 | sprites.append(sprt) #Add sprite to list 33 | 34 | func _remove_sprite(sprt): #Remove sprite from list 35 | var idx = sprites.find(sprt) #find the index 36 | sprites.remove(idx) #remove 37 | 38 | func _top_sprite(): #Get the top sprite 39 | if len(sprites) == 0: #If the list is empty 40 | return null 41 | sprites.sort_custom(SpritesSorter, "z_index") #Sort by z_index 42 | return sprites[0] #Return top sprite 43 | 44 | #Print status 45 | func _print_status(): 46 | var aux_sprt = [] 47 | var aux_sprt_can_drag = [] 48 | for i in sprites: 49 | aux_sprt.append(i.z_index) 50 | for i in sprites: 51 | aux_sprt_can_drag.append(i.dragging) 52 | if not top_sprite == null: 53 | status.text = "Top: " + str(top_sprite.z_index) + " - Dragging: " + str(top_sprite.dragging) 54 | spriteList.text = "Sprites: " + str(aux_sprt) + " - Can drag: " + str(aux_sprt_can_drag) 55 | else: 56 | status.text = "Top: null - Dragging: False" 57 | spriteList.text = "Sprites: " + str(aux_sprt) + " - Can drag: " + str(aux_sprt_can_drag) 58 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Better Drag-and-drop Sprite 2 | Easy way to drag and drop in Godot without using Collision Shapes. This is based on the [Drag-and-drop Sprite](https://github.com/GodotGarden/drag-and-drop-sprite) example by [Godot Garden](https://github.com/GodotGarden). 3 | 4 | ## Improvements: 5 | 6 | ### Issue 1: Sprite's center moves to the mouse position on click 7 | 8 | First issue I found was that when a sprite was clicked, the center of the sprite moved to the mouse position. That causes some problems when trying to drag multiple sprites, and also, it didn't look good. 9 | I fix it using vectors: 10 |

11 | 12 |

13 | The `mouse_pos` vector, is the position where we first click the sprite to move it around, and the `mouse_to_center` vector is a static vector that represents the distance between where we are grabbing the sprite to the center of it. 14 | To calculate the `mouse_to_center` vector I did the following: First, when we click the sprite, we calculate the `mouse_to_center` vector using the sprite position (at the moment when we clicked). So, `mouse_to_center = sprite_pos - mouse_pos`. 15 | Then, when dragging the sprite, we calculate the "new" sprite position as the `mouse_position` plus the `mouse_to_center` vector. That's it! 16 | 17 | ### Issue 2: Using multiple sprites (Overlapping sprites) 18 | 19 | When we have multiple sprites in one place, we always want to move the sprite on the top. To get the top sprite, a lot of drag and drop examples use a picker object that is "attached" to the mouse (It's actually following the mouse around). 20 | Now, that method requires the use of Collision Shapes and Area2D nodes, and I didn't want to use them. So I figured out another way to do it: When the mouse enters a Sprites area, is added to a sprites array, so then, when we click, we order the list by `z_index` and get the top sprite that we want to move. Really easy! 21 | 22 | ## Help me improve this example 23 | 24 | My brain was not working at a 100% when I made this example, so I think a lot of code can be improved. Please feel free to help me improve this example, so we can help more people. 25 | Thanks [cezuriku](https://github.com/cezuriku) for simplyfing the code and updating it to Godot 3.2! 26 | 27 | ## License 28 | 29 | MIT License - see [LICENSE](LICENSE) for more details. 30 | --------------------------------------------------------------------------------