Godot Version
4.2.1
Intro
Hi, I’m new to Godot and trying to learn. I’m enjoying the engine so far and thin it has a lot to offer but I’m having some difficulty adjusting. I have found many other posts asking similar things but all with different answers, none of which quite seem to address the particular issue I’m having, and to be honest (with no disrespect to other’s solutions) they all feel like a bodge rather than a proper solution. I have tried to incorporate some of the ideas in similar posts but I’m still having no joy.
Question
What is the best way to have a click interaction work with only the top game object in a scene? I have tried this using a button element and it sort of worked but I have no way to control the shape - it’s always just a rectangle - so would like to get this to work on a 2D node so I can couple the interaction to a collider shape.
Setup
I have:
- A 2d node called main, basically just serves as a root for me to put other things in
- A StaticBody2D with a sprite and a collider - we’ll call this the stage
- A StaticBody2D with a sprite and a collider - we’ll call this item
The stage is added to the main scene and has a script attached so that when the user clicks on the stage (inside the collider) it instantiates a new item at that position (have tried both making the new item a child of the stage and tried making the new item a child of the main scene). It also adds the new item to an array for referencing later. The item scene is dropped into the inspector through an exported variable.
The item has a script attached to allow drag and click interactions - the drag interaction moves the item, the click operation for the moment just prints something to the console.
The problem
When interacting with the items on the stage this also procs the click interaction on the stage spawning a new item. A new item should only be instantiated when the click isn’t interacting with one of the items already present.
Code
Script on stage:
extends StaticBody2D
@export var item : PackedScene
var items : Array
func _unhandled_input(event):
#func _on_input_event(viewport, event, shape_idx):
#func _input(event):
if event is InputEventMouseButton and event.is_pressed():
#print(event)
var new_item = item.instantiate()
new_item.global_position = event.position - position
items.append(new_item)
add_child(new_item)
As you can see I have tried different input handler function calls.
Script on items
extends StaticBody2D
var button_down : bool = false
var dragging : bool = false
var drag_distance = 2
var mouse_start : Vector2 = Vector2(0,0)
var pos_delta : Vector2 = Vector2(0,0)
func _physics_process(delta):
if dragging:
drag()
func drag():
global_position = get_global_mouse_position() + pos_delta
func _on_input_event(viewport, event, shape_idx):
get_parent().get_viewport().set_input_as_handled()
get_viewport().set_input_as_handled()
if event is InputEventMouseButton:
if event.is_pressed():
button_down = true
pos_delta = global_position - get_global_mouse_position()
mouse_start = get_global_mouse_position()
if event.is_released():
if not dragging:
print("click")
button_down = false
dragging = false
if event is InputEventMouseMotion:
if button_down:
if get_global_mouse_position().distance_to(mouse_start) > drag_distance:
dragging = true
If I disable the script on the stage and manually add a item to the game the drag and click functions work fine. I have also tried setting the input as handled but it doesn’t seem to make any difference.
Notes
I’ve previously done some work in Unity, so I’m used to having built in functions that allow you to click on the “top” item in a scene. I’m pretty sure in Unity it works by ray casting from the mouse and stopping on the first collider. Is there something like this in Godot?
Seems like a bit of a gap in the engine that you can’t let the engine itself figure out the difference between a click and a drag etc or to determine itself which object was clicked on. (Unless I’ve missed something, but judging by how many other people I’ve found asking similar things )
In Finishing
I’m 100% here to learn so if I’m going down a rabbit hole that doesn’t conform to the way I should be working in Godot please let me know. Thanks in advance.