In my game no signal, I do a lot of raycasting to find out what the user has clicked on. Generally, my hierarchies look something like this:
ClickableObject
├ ... # Some arbitrary level of nesting
│ └ StaticBody3D
│ └ CollisionShape3D
└ OtherStuff
Since all of the logic for what I want to do is on the ClickableObject
, I’ve gotten into this pattern of recursively searching upwards for the thing that should handle the click, which looks something like this:
@onready var camera: Camera3D = $Camera3D
func parent_where(node: Node, filter) -> Node:
if node == null:
return null
elif filter.call(node):
return node
else:
return parent_where(node.get_parent(), filter)
func do_raycast() -> void:
var mouse_pos := get_mouse_position()
var space_state := get_world_3d().direct_space_state
var origin := camera.project_ray_origin(mouse_pos)
var end := origin + camera.project_ray_normal(mouse_pos) * 100
var query := PhysicsRayQueryParameters3D.create(origin, end)
var raycast := space_state.intersect_ray(query)
if raycast.has("collider"):
var collider: Node = raycast.collider
var clicked_object := parent_where(
collider, func(a: Node) -> bool: return a is ClickableObject
)
# Do something with clicked_object
There are some problems with this approach, but I’m not worried about performance here. Instead, I’ve run into situations where this results in the wrong behavior. For example, it might look up the tree and happen to find a parent that is clickable, but I don’t actually want to click on it. So, that leads to me having to rearrange how everything is parented.
Lately, I’ve been thinking that there must be a better way. Something I’ve been considering is to extend StaticBody3D
, like this:
class_name ClickableStaticBody3D
extends StaticBody3D
signal clicked_on() # Emit this signal from the raycasting code.
Another is to connect to the mouse_entered
and mouse_exited
signals on the StaticBody3D
to the ClickableObject
, but the docs suggest that these signals are not reliable.
I don’t find either solutions particularly satisfying, so I’m curious how y’all do it.