I wrote a post on t/suki about raycasting in no signal that I’ve copied below:


I’ve been thinking about this again, because while I was finishing up development tasks for no signal I soured on the recursive search strategy I originally suggested in the original post. So, I want to take some time to explain why.

I ended up finding out that I would have very many different kinds of clickable objects in my game, some of which I found could not be parent nodes at all due to technical limitations, and ended up with this pair of functions in my code quite often:

var raycast: RayCast3D
 
# Find the clickable object
func find_clickable() -> Node:
	var collider: Node = raycast.get_collider()
 
	var parent_clickable := parent_where(
		raycast.get_collider() as Node,
		func(a: Node) -> bool:
			return \
				a is Item or \
				a is SlidingPuzzle # etc...
	)
	if is_instance_valid(parent_clickable):
		return parent_clickable
 
	return child_where(
		collider,
		func(a: Node) -> bool:
			return \
				a is AnimatedButton or \
				a is AnimatedDoor #etc...
	)
 
# Click on a clickable object
func handle_click() -> void:
	var clickable = find_clickable()
	if clickable is Item:
		pass # elided
	elif clickable is SlidingPuzzle:
		pass # elided
	elif clickable is AnimatedButton:
		pass # elided
	elif clickable is AnimatedDoor:
		pass # elided
	# etc...

There were two problems with this approach:

  • I needed to manually make sure that find_clickable and handle_click were always in sync. Sometimes I would miss this, causing bugs that I wouldn’t notice until much later.
  • Since my input handling code is closely coupled with specific clickable classes and I have multiple input handlers, there were sometimes subtle differences between the different input handlers that made bugs difficult to track down.

I think it’s pretty obvious here that I should have used a signal, but it’s not clear to me exactly how I would set that up.

You could make the argument that I could extend the EditorScenePostImport class to automatically add a script which uses the collider extension pattern as mentioned above by @ColdEmber and @outfrost to every collider in every imported scene. But, this still wouldn’t make it easy for me to set up because I would need to set every imported scene as editable in order to access the signal on the collider. @Titanseeker created hundreds of models for no signal that I wanted to be able to drag directly into whatever scene I wanted them to be included in, so this kind of hitch in the workflow would have been unacceptable.

I also can’t relay the signal to a custom script on the root node to avoid needing to mark the scene as editable because I already have a custom script on the root node that has its own separate responsibility.

Hopefully this explains why I’ve soured on the recursive search strategy for this after working on no signal. I’m not really sure what I would prefer instead, yet. Maybe collider extension would work well if I just tried it out in practice and saw how I went… or maybe there’s some other secret way to handle the situation.