Sander @ March 18, 2023 at 12:06 am

Godot simplified drag n drop tutorial

I was "following" Generalist Programmer's Godot drag and drop tutorial and I made a few refinements that I wanted to share. Please read the tutorial and then follow along.

Drop-in behaviour

Wouldn't it be cool to give a node drag and drop behaviour simply by dropping in a node with the script, independent of any other scripting?

We can do this by creating a new child node (here called Drag-and-drop Dropin), and attaching the script to it. It must extend from the dropin Node2D.

Node structure

In the script itself we make two changes:

  1. Disconnect the KinematicBody2D's input_event signal, and write it in code in the dropin's _ready function. Call it on it's parent: get_parent().connect("input_event", self, "_on_KinematicBody2D_input_event"). We want to process the input_event of the KinematicBody2D, not the dropin.
  2. In the _process function, assign the mouse position to the parent of the dropin: get_parent().position = Vector2(mousepos.x, mousepos.y).
  3. Same for the last line in the script where we set the position of the parent.

The script then reads as follows:

extends Node2D

var dragging = false

signal dragsignal

func _ready():
    connect("dragsignal", self, "_set_drag_pc")
    get_parent().connect("input_event", self, "_on_KinematicBody2D_input_event")

func _process(delta):
    if dragging:
        var mousepos = get_viewport().get_mouse_position()
        get_parent().position = Vector2(mousepos.x, mousepos.y)

func _set_drag_pc():
    dragging = !dragging

func _on_KinematicBody2D_input_event(viewport, event, shape_idx):
    if event is InputEventMouseButton:
        if event.button_index == BUTTON_LEFT and event.pressed:
            emit_signal("dragsignal")
        elif event.button_index == BUTTON_LEFT and !event.pressed:
            emit_signal("dragsignal")
    elif event is InputEventScreenTouch:
        if event.pressed and event.get_index() == 0:
            get_parent().position = event.get_position()

Simplifying the script

I was refactoring this, and it turns out we can simplify this further.

  1. We don't need a dragsignal because the signal is both emitted and consumed within the same script. We can just replace the emit_signal calls with calls to _set_drag_pc(). We can then remove the connect() call in the _ready function, and the signal dragsignal.
  2. The same function is called when the left mouse button is both pressed and not pressed, so we can remove that conditional and remove the elif statement in the input event handler.
  3. As there is only one invocation of _set_drag_pc() we can inline it.

The final script becomes:

extends Node2D

var dragging = false

func _ready():
    get_parent().connect("input_event", self, "_on_KinematicBody2D_input_event")

func _process(delta):
    if dragging:
        var mousepos = get_viewport().get_mouse_position()
        get_parent().position = Vector2(mousepos.x, mousepos.y)

func _on_KinematicBody2D_input_event(viewport, event, shape_idx):
    if event is InputEventMouseButton:
        if event.button_index == BUTTON_LEFT:
            dragging = !dragging
    elif event is InputEventScreenTouch:
        if event.pressed and event.get_index() == 0:
            get_parent().position = event.get_position()

So this dropin can now be added to any node to add Drag-and-Drop behaviour. I think the script can be improved a little so that the node is not centered under the mouse cursor but takes account the offset where it is picked up.

Let me know your thoughts! #godot