• Godot Help
  • Making a drag and drop inventory with panels

I have a question about dragging and dropping for an inventory. If I have been using a panel as my inv_ui_slot that contains the item (Sprite2D) and want to communicate with my inventory every time I drag a slot to another slot it can run my swap function. Would I be able to use a built in dragging function like _get_drag_data etc. Or am I coming at this from a bad approach. Either way how would I go about implementing this.

My node structure looks like this for my Inv_UI_slot:
Panel -> sprite2D, CenterContainer -> panel -> sprite2D, label

This is my inv_ui_slot.gd script:


@onready var item_visual: Sprite2D = $CenterContainer/Panel/item_display
@onready var amount_text: Label = $CenterContainer/Panel/Label

# Function to update the visual representation of the inventory slot
func update(slot: InvSlot):
	if !slot.item:
		# Hide the visual if there's no item
		item_visual.visible = false
		amount_text.visible = false
	else:
		# Show the visual and update the texture if there's an item
		item_visual.visible = true
		item_visual.texture = slot.item.texture
		amount_text.visible = true
		amount_text.text = str(slot.amount)
		
func _get_drag_data(at_position):
	var drag_slot = Panel.new()
	set_drag_preview(drag_slot)

func _can_drop_data(at_position, data):
	pass
	
func _drop_data(at_position, data):
	pass

    swoyer2 I have been working with drag-n-drop recently, let's see if I can help

    swoyer2 If I have been using a panel as my inv_ui_slot that contains the item (Sprite2D) and want to communicate with my inventory every time I drag a slot to another slot it can run my swap function.

    please slow down

    swoyer2 Would I be able to use a built in dragging function like _get_drag_data etc. Or am I coming at this from a bad approach. Either way how would I go about implementing this.

    I don't understand what the question is.

    func update(slot: InvSlot):
    	if !slot.item:
    		# Hide the visual if there's no item
    		item_visual.visible = false
    		amount_text.visible = false
    	else:
    		# Show the visual and update the texture if there's an item
    		item_visual.visible = true
    		item_visual.texture = slot.item.texture
    		amount_text.visible = true
    		amount_text.text = str(slot.amount)

    you don't need to hide every node, when you hide a node it hides all it's children. if you have 2 different nodes that have to be hidden, it would be best to have a parent node and the others as children.

    about your "question", if I understood something, you can pass a reference to self in a dictionary, from there you can obtain the parent of the node that's being dragged or reparent it. you can also pass callables (functions) through the dictionary and call them on the other side.
    for swapping parents you need a temp variable to store one of the parents.

    func _can_drop_data(_at_position, data):
    	return typeof(data) == TYPE_DICTIONARY and has_slot(data.get("slot_type", 0))
    
    func has_slot(data) -> bool:
    	if get_parent().has_method("is_swappable"):
    		if get_parent().is_swappable():
    			return data == get_parent().slot_type
    		else:
    			return true
    	else:
    		return false
    
    func _drop_data(_at_position, data):
    	var oth : Control = data.get("remove")
    	var par : Control = self.get_parent()
    	if get_parent().is_swappable():#I test this in can_drop_data
    		self.reparent(oth.get_parent(), false)
    	oth.reparent(par, false)

    and this is on the item that can be dragged. In my game you can drag the item and drop it onto another item, and if the parent can hold more than one item it is added, otherwise it is replaced.

    edit: You got me thinking so I worked on implementing the dragged-node-becoming-invisible while being dragged.
    I used the node notification system for making it visible when dragging ends:

    func _notification(what):
    	if what == NOTIFICATION_DRAG_END:
    		visible = true

    and you set it invisible during _get_drag_data:

    func _get_drag_data(at_position):
    	set_drag_preview(drag_slot)
    	visible = false

      Jesusemora i tried to implement drag and drop.. it dont work..

      Best approach for godot atm is to pick up slot then put it down in the next slot..

      Been working on this for months to no avail..

        kuligs2 I implemented drag and drop yesterday. The code here is from my game.
        What are you having trouble with?

          Jesusemora No problem atm because i dropped this feature from my todo list after i was testing stuff..

          Problem as i remember was how to handle drop event on "nothing" node and keeping track of previous node so that the item that you grabbed goes back to its origin.. the latter part is no problem, my code was too convoluted.. but the dropped even on nothing like in mid air, it using that notiffication was difficult.. it got triggered multiple times etc..etc.. idk.. i never had luck finding a way to implement user friendly drag n drop.. maybe in 2d is easier cuz you always hava a world node under the mouse or something.. but in 3d you could be aiming in the sky or void.. so you can put a mouse_entered thing on the node that is not a node..

          Maybe you could take a video and show how its working in your case?

            Jesusemora What is your node structure like? I am seeing people use TextureRects with a panel child. Is that what you are doing?

              kuligs2 I used the godot drag-n-drop features, it sounds like you were using raycasting.
              I uploaded a video of the mech workshop, I posted the link in my game showcase (Project Metal Storm). I don't want to spam so I won't post the link again.

              swoyer2 Jesusemora What is your node structure like? I am seeing people use TextureRects with a panel child. Is that what you are doing?

              there are three different slots nodes, they can be whatever. the first that holds the attachments is a VBox container, and each of the slots is a Panel Container I think. Then there's a SINGLE node UI_Attachment which is a Button. Buttons can hold both text and a sprite, so they are very useful for menu items, and have additional features like left and right button click and this can also be disabled. I'm using a theme variation for changing the style, and an autoload resource_manager gives them the textures for sprites.
              During set_drag_preview I instantiate the node and then assign it the same text and sprite (in my case, pilots are assigned text, attachments are assigned a sprite only).

                Jesusemora

                okay! Thank you. I seemed to have fixed some things but I am getting an error when trying to do the set_drag_forwarding. I am getting an error telling me that get_drag_data_fw needs 2 arguments but is given 1. I understand the problem but don't have a solution. Here is my code.

                
                const ITEM_SLOT_SCENE = preload("res://game_objects/inventory/item_slot.tscn")
                
                @onready var item_grid = find_child("item_grid")
                
                var inventory: Inventory = null 
                
                func set_inventory(inventory: Inventory) -> void:
                	self.inventory = inventory
                	
                	inventory.connect("slot_changed", _on_inventory_slot_changed)
                	
                	for i in inventory.max_slots:
                		var stack = inventory.get_item_stack(i)
                		var slot = ITEM_SLOT_SCENE.instantiate()
                		slot.set_drag_forwarding(get_drag_data_fw, can_drop_data_fw, drop_data_fw)
                		item_grid.add_child(slot)
                		slot.set_item(i, stack.item, stack.quantity)
                		
                func can_drop_data_fw(position: Vector2, data, other_control: Control) -> bool:
                	if not other_control is ItemSlot:
                		return false
                		
                	if not data.has("slot") or data.slot.inventory_index == other_control.inventory_index:
                		return false
                	
                	return true
                
                func get_drag_data_fw(position: Vector2, other_control: Control):
                	if not other_control is ItemSlot or not other_control.item:
                		return null
                		
                	other_control = other_control as ItemSlot
                	
                	# set_drag_preview
                	
                	return {"slot": other_control}
                
                func drop_data_fw(position: Vector2, data, other_control: Control) -> void:
                	if not other_control is ItemSlot:
                		return
                	
                	inventory.swap_slots(data.slot.inventory_index, other_control.inventory_index)
                
                func _on_inventory_slot_changed(index: int, old_content: ItemStack, new_content: ItemStack) -> void:
                	for child in item_grid.get_children():
                		if not child is ItemSlot:
                			continue
                		
                		child = child as ItemSlot
                		
                		if child.inventory_index == index:
                			child.set_item(index, new_content.item, new_content.quantity)
                			break

                  swoyer2 wait, are you on godot 3? I'm on godot 4.
                  I don't know the differences with that version, but one is the usage of fw at the end of the function. I now see why you are struggling so much.

                    Jesusemora no but the example code I saw for drag and drop was outdated (3 years old). So I had to change things, but maybe I didn't migrate it all over to godot 4. I will look into seeing if that is the problem

                    Jesusemora I don't see anything online about fw related to godot 3. I don't understand what could be causing this issue still

                      swoyer2 listen bud, stop using godot 3 code and drop the tutorial.
                      You are using set_drag_forwarding but it's not necesary in godot 4, all you need is these in the nodes used for slots and items:

                      _can_drop_data()
                      _get_drag_data()
                      _drop_data()

                      And set_drag_preview()

                      The reason you get an error is because set_drag_forwarding doesn't have special requirements anymore, it uses callables, and it's the same as overriding the 3 functions I told you about.
                      get_drag_data only takes one argument but the callable you are passing has 2 (the function you wrote), so godot is giving it one argument and the error happens.

                      Always read the docs. I learned how to make drag n drop just from the godot Ctrl+Click API reference.

                      Edit: also, drag n drop is used with Controls since they are functions of Control. You can't use it with Node3D or other Node2D inherited nodes. sprite2D is inherited from Node2D.