How to calc initial state of overlapping areas after setting monitorable to true

Godot Version

Godot Engine v4.4.dev.custom_build.cb411fa96

Question

I’m trying to track the overlap status between two different areas. Generally it works fine, so everything seems to be setup okish, like

Body1 has a hurt area on layer 7 and masks layer 4 with monitorable set to false
Body2 has a hit area on layer 4 and masks layer 7 with monitoring set to true

Placing them apart from each other so that the areas don’t overlap,
then setting Body1 monitorable to true
and finally moving them towards each other until the areas overlap triggers area_entered on Body2.

In special cases i want to set Body1 monitorable to true while their areas are already overlapping.
This won’t trigger area_entered on Body2.
They first have so be moved apart so that the areas stop overlapping and then moved towards each other so that the areas begin to overlap again to finally trigger area_entered on Body2.

Is this expected behavior?

To work around this state i guess i could just set body1 monitorable to true from the beginning and suppress its action with a flag but it feels hackish.

What’s the idiomatic way to initialize the overlapping status in this special situation?

Here’s a minimal reproducible example:

var e
var t
var s : int = 0
func nxt():
  s += 1
  match s:
    1: 
      e = spawn_enemy(enemy1, Vector2(500,500))
      t = spawn_tower(tower1, Vector2(600,500))
      t.hurt_area.monitorable = false
    2:
      t.global_position = Vector2(550,500)
    3:
      t.hurt_area.monitorable = true

      # doesn't force the event to signal: t.global_position = t.global_position
      # doesn't force the event to signal: t.set_deferred("global_position", t.global_position)
      # this forces the event to signal
      #var op = t.global_position
      #t.global_position = Vector2(600,500)
      #get_tree().create_timer(.0).connect("timeout", func (): t.global_position = op, ConnectFlags.CONNECT_ONE_SHOT)

func _input(event):
    if event is InputEventMouseButton and event.pressed:
        match event.button_index:
            MOUSE_BUTTON_LEFT:
                nxt()

e is Body2 and
t is Body1

nxt() will take one step through a state machine on each left mouse click.

I’d expect the area_entered event on Body2 to signal on step 3 but it doesn’t.
Setting the position to itself didn’t work in my tests, also not deferred.
What helped was to first move it out of the area and set a timer to move it to its previous position again.
This feels pretty ugly.
Is there a less complex and more idiomatic way which doesn’t involve timers?

I also posted the question on reddit.

EDIT:
I edited the code based on the suggestions (from events to Area2D.get_overlapping_areas()) and pushed it to github.

Yes

Use Area2D.get_overlapping_areas()

2 Likes

@matheusmdx

OMG. I did not know that method existed. I can streamline so much code now! I only thought you could on_entered and on_exited. I can drop all my arrays collecting areas that entered and removing them when they exited.

It says in the docs:

“for performance reasons…this list is modified once during the physics step, not immediately after objects are moved. Consider using signals instead.”

Is this a heavy process one should avoid, or not? (If you have lots of areas).

You misunderstand the docs, what that means is if you call this func in the same physics step the result will be not updated because the overlap check happens one time per physics tick, call this func has no cost

1 Like

Thanks for the answer! And sorry - took me quite some time to try this out.

This doesn’t change the behavior for me. I removed all event based code and replaced it with checking hit_area.get_overlapping_areas() in _physics_process but the area of Body1 still needs to be moved out and in again into the area of Body2 to get_overlapping_areas to return a list with size > 0.

The hit_area.monitoring is true when you use get_overlapping_areas?

Yes, it’s set to true in the editor and i don’t change it programmatically.

Verified it with print("hit_area.monitoring = ", hit_area.monitoring) in _physics_process

I pushed the code to github. I know it’s a lot to ask, but I would really appreciate it if you could have a look at it.

Ok, i took a look on your project and i think i found what’s happening, if i remember well, Godot has a bug that if you change Area2D.monitoring/monitorable when they already overlapping they will not update until you move away the areas and overlap again (your Area2D.monitorable in tower is setted in code to true), as workaround you can disable/enable the CollisionShape2D instead.

func nxt4():
	s += 1
	print(s)
	match s:
		1: 
			e = spawn_enemy(enemy1, Vector2(500,500))
			t = spawn_tower(tower1, Vector2(600,500))
			#t.hurt_area.monitorable = false
			t.hurt_area.get_node("HurtBox").set_deferred("disabled", true)
		2:
			t.global_position = Vector2(550,500)
		3:
			#t.hurt_area.monitorable = true
			t.hurt_area.get_node("HurtBox").set_deferred("disabled", false)

Also check you debugger, your project throw errors about use move_and_collide with sync_to_physics enabled: