I’ve made this small demo to help me learn Area2Ds.
Please just watch the video, it is short.
I have 3 overlapping shapes, like a Venn diagram, and I want them to react when the mouse hovers over them. I want only the shape that is in the foreground to react to the mouse hover.
This seems surprisingly complicated to do in Godot, but maybe I’m missing something? How would you approach this problem?
The main thing that needs to happen is keeping track of who is on top. Since they are are all on the same z index and canvas layer the rendering order is done by first ready, last rendered.
I added static class information to keep track of who is selected and who has rank. when ever the mouse enters/exits we need to notify all the cards.
This is my attempt. Since there was no code to reorder nodes I didn’t take that into consideration.
Code
extends Node2D
class_name Circle
#---------------------------------
static var selected : Array[Circle] = []
static func push(cir:Circle):
selected.push_back(cir)
selected.sort_custom(func(a:Circle,b:Circle) : return a.rank > b.rank )
for c in selected:
c.hover_changed()
static func remove(cir:Circle):
selected.remove_at(selected.find(cir))
cir.hover_changed()
for c in selected:
c.hover_changed()
static var count : int = 0
#---------------------------------
var rank : int = 0
func _ready() -> void:
$Area2D.connect('mouse_entered', mouse_entered)
$Area2D.connect('mouse_exited', mouse_exited)
rank = Circle.count # first ready, last rendered
Circle.count += 1;
func hover_changed():
if not Circle.selected.is_empty() and self == Circle.selected.front():
$Sprite2D.scale = Vector2.ONE * 1.15
else:
$Sprite2D.scale = Vector2.ONE
func mouse_entered():
Circle.push(self)
func mouse_exited():
Circle.remove(self)
If i were to do this myself I would explore a highlight manager outside of the card class. This would be a ray cast query into 2d space to get selected nearby areas, then do a simpler selected/deselected mechanism. ( this would be contingent on how the ray collides with the areas, but I assume it will be deterministic with either the appropriately rendered on first, or on top, in the ray query) I will explore this next.
PhysicsDirectSpaceState2D contains many of the functions I thought must exist, but I couldn’t find them anywhere. I was thinking the Godot API was just immature and missing these functions, but I just couldn’t find them. I feel a lot better about it now.
So i found out that the physics query is nondeterministic and would flipflop on different bodies on top.
here is my code
CardSelectorManager
extends Node2D
var selected : Circle = null
func _unhandled_input(event):
if event is InputEventMouseMotion:
var space_state = get_world_2d().direct_space_state
var query = PhysicsPointQueryParameters2D.new()
query.position = event.global_position
var result = space_state.intersect_point(query)
if result.is_empty():
if is_instance_valid(selected):
selected.highlight(false)
selected = null
return
result = result.filter(func(r) : return r.collider is Circle)
var top = result.pop_back().collider as Circle
top.highlight(true)
#if is_instance_valid(selected) and selected != top:
# breakpoint
selected = top
for res in result:
res.collider.highlight(false)
Circle
extends StaticBody2D
class_name Circle
func highlight(is_highlted:bool):
if is_highlted :
$Sprite2D.scale = Vector2.ONE * 1.15
else:
$Sprite2D.scale = Vector2.ONE
for this to work easier i had to switch to static bodies.
Update: this is just a thought, since input is checked with each substep of the physics server it may be more stable if we did the query during the physics process. (Will test later)
I was unsuccessful with the ray cast approach it seems to me something similar to z fighting.
bonus: i also tried commanding an area2d selection point and use area overlap, it was more predictable but the order was based on contact order. every new contact would be pushed to the back of the array.
I think the only approach is to imbue the circles with some metadata that maps to their render order. this would most likely be the z index, which would also guarantee their render order.
If you didn’t want to do that, making a hybrid system that renders a 2d/3d scene with physical offsets could make raycast work.
The simplest for what this is just get the last node in the tree. Basically check who cam last, because will be displayed top. Another way would be to use z-index.
add this code in Main node (e.g. main.gd). Also check how you could use z_index.
extends Node2D
var last_id: int
func _ready():
last_id = get_last()
pass
func get_last() -> int:
var last_id: int
for i:Node2D in get_children():
last_id=i.get_instance_id()
print(last_id)
return last_id