godot: Changing node parent produces Area2D/3D signal duplicates

Godot 2.1.4, Godot 3.0 beta 1 Windows 10 64 bits

I found that if you teleport-away and reparent a node which is inside an Area2D, you will get an area_exit, but ALSO extras notifications body_enter and body_exit, two in my case, even though the object is not anymore in the area.

This gave me headaches for several hours in my game until I narrow it down to this, because this behavior causes my teleport system to trigger everything multiple times, and I can’t find a way to avoid this so far without atrocious hacks… It does this wether I teleport from fixed_process or not, and emptying layer/collision masks before teleport has no effect.

Here is a demo project reproducing it: 2.1.4: TeleportArea2D.zip 3.0b1: TeleportArea2D_Godot3b1.zip

Repro:

  1. Launch main scene, notice you get one body_enter because the body overlaps with the area, wich is expected
  2. Press any key, this teleports the body and reparents it (in code, the node is removed from tree, translated, and then added to the tree)
  3. Notice you get an expected body_exit, but also two unexpected pairs of body_enter and body_exit

About this issue

  • Original URL
  • State: open
  • Created 7 years ago
  • Reactions: 15
  • Comments: 32 (12 by maintainers)

Commits related to this issue

Most upvoted comments

This is still a problem in 4. Reparenting a node while inside an area2d triggers an additional body_entered signal.

Still reproducible in 3.2: https://www.reddit.com/r/godot/comments/g9cosc/area2d_body_entered_firing_when_it_should_not/

The enter signal is emitted immediately after re-adding the red_level scene back to the tree. When I check “body.position” in the “entered” function, it clearly shows the body is far away, so the signal should not be emitted.

Yes I can still reproduce it in 4.3 dev1. I quickly assembled a project to test it. I included 2D and 3D examples.

I’ll leave a zip attached in this comment. If you want to test it, please run (using F6) the world.tscn scene for each example (I didn’t set a main scene sorry)

bugarea-4.3-dev1-2D-and-3D.zip

Forgot to mention, I’ve been testing and apparently, after removing child by using the method remove_child(child_to_remove) if you put yield(get_tree(), "idle_frame") afterwards, the whole issue gets fixed, even on my other project with multiple viewports. bugarea-fixed.zip

Just tested this and it’s still reproducible in Godot 3.3.2

It reproduces in 3.1 alpha 1.

On scene start:

Body enter

Which is expected. Then the reparent-teleport happens, and all these signals get fired, while I expected only one Body exit:

Body exit
Body enter
Body exit
Body enter
Body exit

TeleportArea2D.zip

I tested this against master branch.

I downloaded the bugarea.zip and converted it to Godot 4 (had to fix some collision layers because it didn’t collide with the door) then tested it and it seems to keep happening, signal is fired an indefinite amount of times making the game freeze. I haven’t tried the workarounds though.

That’s for the 2D version, the 3D version (bugarea3d.zip) directly closes the game upon opening it without moving so I assume something in the automatic conversion to Godot 4 went wrong, but I don’t know how to debug that.

Pretty sure I am battling this issue in 3.4.5.stable right now. Here is a minimal reproduction project (Move the capsule with left and right arrow keys): area2d_body_entered_triggered_multiple_times.zip

using call_deferred() for the add_child() seems to fix this for me, but when I exit the game I get errors that I leaked memory: image

I can reproduce this in 3.3.4 in 2D.

I thought I found a workaround for it but so far the best way I’ve found to prevent it is to just not use the event system and just use _process(delta) with get_overlapping_bodies, but that’s not ideal.

I’m going to try and think of something else, I feel like this is a pretty bad bug because IMO the docs lead you right into it https://docs.godotengine.org/en/stable/tutorials/best_practices/scene_organization.html#choosing-a-node-tree-structure

I wonder if some other workaround the docs kind of recommend against because of “bad coding practices” might also work (e.g., adding the player to the scene every time).

I’ll hopefully have more ideas for this later.

Just wanted to point out that this happens too in 3d. The workaround proposed by @AttackButton works in this case too

bugarea3d.zip bugarea3d-workaround.zip

I tested your MRP, and it is entering and exiting 2 times from each function. You can test this putting an counter inside of the functions.

Damn you are absolutely right, I didn’t notice this…

You can avoid this connecting using CONNECT_DEFERRED | CONNECT_ONESHOT every time you change the room. Anyway the bug will still be there in the engine side.

After a lot of testing, it seems that what you mention is the only reliable way of guaranteeing one signal received instead of multiple

I also tested putting some timers between lines of code and it seems that the problem also got fixed, but i find my solution very arbitrary and not very clean, but may help in finding out the issue. For example:

extends Node2D

var count = 0

func _ready():
	$LEFT/Door.connect("body_entered", self, "_player_to_right")
	$RIGHT/Door.connect("body_entered", self, "_player_to_left")

func _player_to_right(player):
	count += 1
	print(count)
	player.set_global_position(Vector2(-100,-100)) # Move the player away from the area
	yield(get_tree().create_timer(0.5), "timeout")
	player.get_parent().remove_child(player)
	$RIGHT.add_child(player)
	player.set_position(Vector2())

[...]

^ This makes the signal emit only once, but if you change the timer for something shorter, let’s say 0.01 like this…

[..]

func _player_to_right(player):
	count += 1
	print(count)
	player.set_global_position(Vector2(-100,-100)) # Move the player away from the area
	yield(get_tree().create_timer(0.01), "timeout")
	player.get_parent().remove_child(player)
	$RIGHT.add_child(player)
	player.set_position(Vector2())

[...]

then the signal emits twice.

For me it seems that the “add” and “remove” child are not in sync with the physics process or something like that (i know nothing about Godot’s internals so I might be completely wrong)