Finding the top sprite of overlapping sprites

:information_source: Attention Topic was automatically imported from the old Question2Answer platform.
:bust_in_silhouette: Asked By Beechside

Greetings all.

  1. I’m creating a 2D sliding game with sprites that can overlap.
  2. When a sprite is clicked, I set z_index for all sprites so that no two ever have the same index and selected sprite has the highest z_index.
  3. I have a raycast (cast to 0, 0) under my cursor at all times to see what’s under the cursor.

The problem is that the raycast doesn’t alway see the top-rendered item. The documentation says it finds the “closest object” but that’s not always the top item. Seems to go by the Scene Tree order. I’ve also tried setting z_index for the raycast to high but I guess that’s doing nothing, if z_index is only for rendering and not for selection.

Various signals with mouse entering and exiting don’t seem to help. Three questions, if I may:

  1. Am I right in thinking that z_index is a render only parameter?
  2. What’s the easiest way of having the highest rendered sprite be the one that’s selected?
  3. Would this best be done as a flat 3D game so that there is a genuine “above” value on the y-axis?

Thank you!

:bust_in_silhouette: Reply From: Zylann

Raycasts find the closest object in 2D space. Z-index is not part of 2D space, so trying to find top-most with a raycast makes no sense. If the ray has a null size it makes even less sense to do one in the first place. You could just as well use intersect_point: Physics2DDirectSpaceState — Godot Engine (3.1) documentation in English

Z-index it’s only about rendering indeed, and is combined with index in the tree to obtain their drawing order. Note that you could then disregard Z-index and simply re-order your node within its parent to simplify this.
Note that Control nodes may be an alternative to your needs (because they know when they are hovered and clicked through input and they DO respect tree order), though I imagine it’s not necessarily fit depending on your game.

You may want to use a different collision detection method which gives you all shapes under your cursor (like intersect_shape or get_overlapping_bodies), then you can sort them to find the one with highest Z-order.

If you had a 3D game then you’d be able to do a raycast in front of you with the expected result, although you’d need to change your whole workflow to be 3D.

Thank you, I appreciate your taking time for this.

Actually, I find using a null-size raycast under the cursor very useful. It provides a lot of very easy to access information. It’s not a technique I’ve seen elsewhere but I guess there are better ways of doing it. I’ll certainly check out what you suggest, in particular Control nodes.

I realise that raycast can’t help with depth but I’m still looking for the tool that would mimic human perception. Even in a flatland world, there are still sprites “on top” of each other. I want to be able to pick that up, in the same way as a person playing a game would expect to be able to pick the sprite up with a drag gesture. It’s a sort of 3d world represented in 2D, much as playing cards are in many games.

Back to the drawing board but seriously - thank you again.

Beechside | 2020-01-14 22:55

Ah - thank you for adding the reference to intersect_point. That was unknown to me - I haven’t delved into the low-level routines at all. I think a combination of that and sorting by z_order should do the trick.

Beechside | 2020-01-15 16:09

:bust_in_silhouette: Reply From: Beechside

OK, thanks to Zylann, I got this working. It’s not elegant but it allows you to select the node under the cursor with the highest z_index, regardless of the order of nodes in the tree. Posted here in case it has some merit (assumes nodes under the cursor are Area2D)

func select_piece() -> Area2D:
var pos: Vector2
var intersects: Array
var top_piece: Area2D
var top_z: = -1

pos = get_viewport().get_mouse_position()
intersects = get_world_2d().get_direct_space_state().intersect_point(
		pos, 32, [], 0x7FFFFFFF, true, true)
for piece in intersects:
	if piece.collider.z_index > top_z:
		top_piece = piece.collider
		top_z = piece.collider.z_index
return top_piece