godotengine / godot

Godot Engine – Multi-platform 2D and 3D game engine
https://godotengine.org
MIT License
89.17k stars 20.22k forks source link

Updating Area2D collision layer does not emit entered signal of already intersecting Area2Ds #93183

Open pmoosi opened 3 months ago

pmoosi commented 3 months ago

Tested versions

System information

Godot v4.3.beta.mono (e09a3e931) - Ubuntu 22.04.4 LTS 22.04 - Wayland - Vulkan (Forward+) - integrated AMD Unknown (RADV RENOIR) - AMD Ryzen 7 5800H with Radeon Graphics (16 Threads)

Issue description

I have a scene with two Area2Ds that are overlapping. The collision layers and masks are set to not trigger any collisions. When I now set one area's collision layer to the collision mask of the other area, no area_entered signal is emitted. This only applies to intersecting areas. If the layer of an intersecting body is updated, the body_entered signal is emitted.

I'm not sure if this is intended behavior or a bug. I found some similar older issues that were fixed by #39894. In Godot 3.5.3, the signals are emitted.

Steps to reproduce

The MRP contains an Area2D with collision mask '1'. A CharacterBody2D and another Area2D that overlap the first area have collision layer '0'. A script connects the body and area entered signals for which it prints a short message ('Body/Area entered'). It then sets the collision layers of the body and the second area to '1' after 1s delay. Only the message for the body entered signal is printed.

Minimal reproduction project (MRP)

mrp.zip

huwpascoe commented 3 months ago

To interact, Areas need to be able to scan each-other.

After the timer

SomeArea layer=0 SomeArea mask=1. Can scan layer1. Scans SomeOtherArea and SomeBody.

SomeOtherArea layer=1 SomeOtherArea mask=0. Can't scan anything.

Correction

SomeArea layer=1 SomeArea mask=1. Can scan layer1. Scans SomeOtherArea and SomeBody.

SomeOtherArea layer=1 SomeOtherArea mask=1. Can scan layer1. Scans SomeArea and SomeBody.

pmoosi commented 3 months ago

I'm not sure I understand. I'm only talking about the events of SomeArea

SomeArea layer=0 SomeArea mask=1. Can scan layer1. Scans SomeOtherArea and SomeBody.

So, as I understand it, SomeArea should emit the area_entered event when the collision layer of SomeOtherArea is changed.

Or are you saying that both areas need to scan for each other so that one emits the event? I don't think that's the case. When I set the collision layer when they are not overlapping and only then move SomeOtherArea inside SomeArea the event is emitted. The same is also true when I set the layer directly in the editor (or in _ready), not after some delay in the script. Also it's emitted for the body which has the same layer/mask setup.

huwpascoe commented 3 months ago

Physics server only tries to collide objects that have moved so it's better to keep masks and layers consistent.

But, looking at the source, a layer change does count as having moved so, let's see. Two tests:

Test 1

Does it happen because one is inactive?

SomeOtherArea.collision_layer = 1
SomeArea.collision_mask = SomeArea.collision_mask # poke!

Test 2

Could it be a timing problem?

var p := false
func _ready():
  # await timeout
  p = true

func _physics_process(_delta):
  if p:
    # set layers

Can't do it myself right now but feel free.

pmoosi commented 3 months ago

Physics server only tries to collide objects that have moved so it's better to keep masks and layers consistent.

Good to know. Maybe this should be mentioned somewhere in the documentation (if it's not fixed)? The thing is that it is inconsistent between body_entered and area_entered and Godot 3 and 4 which might lead to confusion - at least it did for me.

I tried both tests and neither emitted the signal.

huwpascoe commented 3 months ago

If both tests failed, only possibility (that I can imagine) is that the areas are already considered to be overlapping and it'll only signal area_exit.

pmoosi commented 3 months ago

Ok, so I played around with it a little bit more and I found that when I 'poke' twice it triggers the signal. Interestingly, the first does not have to happen close to the changing of the layer.

So this works:

func _ready():

  # await timeout
  # set layers
  SomeArea.collision_mask = SomeArea.collision_mask # poke!
  SomeArea.collision_mask = SomeArea.collision_mask # poke again!

And this:

func _ready():
  SomeArea.collision_mask = SomeArea.collision_mask # poke!

  # await timeout
  # set layers
  SomeArea.collision_mask = SomeArea.collision_mask # poke again!
huwpascoe commented 3 months ago

Okay, two pokes work. Does the connection timing make a difference to the poke? Like if it's connected through the node panel in the inspector instead of code, does it differ?

pmoosi commented 3 months ago

Nope, connecting through the inspector does not change anything.