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
I've done some probing in Area2D, and I got the following:
-----_body_inout
Body enter
-----_body_exit_tree
Body exit
-----_body_enter_tree
Body enter
-----_body_inout
Body exit
-----_body_inout
Body enter
-----_body_inout
Body exit
Looking at the code, first thing strange thing I notice is:
Area2D stays connected to the body when it leaves the tree, I see no signal disconnection here and connections aren't one-shots: https://github.com/godotengine/godot/blob/13c2ff932089db24841bb971b645141218bd8586/scene/2d/area_2d.cpp#L134-L148
Then, when the body is re-added to the tree, body_enter is emitted but there is no check wether it still overlaps with the area, but in my opinion it's wrong to not have disconnected the signals: https://github.com/godotengine/godot/blob/13c2ff932089db24841bb971b645141218bd8586/scene/2d/area_2d.cpp#L116-L132
The 3 next emissions in _body_inout are still a mystery to me, but I have to go to sleep for now
This bug/behavior has existed for a long time (03.2016). My only solution was to delete the body and re-create it. You can also try to remove all connections.
I've tried several times to explain that the node2d function
set_pos, set_rot and set_scale has problems with RigidBody2D, KinematicBody2D and Area2D.
Partly works and partly there is a strange behavior. By scale I mean flipping the body around the x/y axis.
In your case, the engine thinks that the body is still in Area2D although it was moved with set_pos to a different location.
example: try teleporting a falling rigidbody2d in Godot3
(set_scale = flipping body on x/y axis)
@puppetmaster- deleting the body (so the entirety of any character entering a teleport) is going to be quite annoying for me. I would like to find a solution to fix this in engine at least for teleporting because it's a PITA to deal with for something that simple... otherwise I'm not doing anything fancy with the kinematic body (just using move(), no rotation, no scale).
The problem on node side seems obvious, but if the issue also extends in the physics engine I'll need some help...
@Noshyaar note this behaviour also exists in 3.0
Edit:
I found that connections made by the area to the body in C++ seem to be completely redundant, because the physics engine already notifies the node with the monitoring callback.
But even with these removed, I still get an extra enter/exit from the Physics2DServer itself.
I tried to yield() one frame at various points of the teleport code to account for the delay the physics engine might have, but it had no effect.
I'm clueless now...
@puppetmaster- if you're interested, I lost some hair of trial and error in order to encapsulate a workaround for my use case, specifically for Godot 2.1.4 (it likely won't work in 3.0), maybe it can be useful to others:
# See https://github.com/godotengine/godot/issues/14578
class BodyTeleporter:
signal done
func exec(body, new_parent, new_local_pos):
# Memorize and clear collision and layer masks,
# so the physics server will stop detecting overlaps
var cached_collision_mask = body.get_collision_mask()
var cached_layer_mask = body.get_layer_mask()
body.set_collision_mask(0)
body.set_layer_mask(0)
# Move the node first, so we get a body_exit coming FROM the Physics Server
# (it's important because I believe Area2D itself sends body_exit when the body exits the tree)
var gpos = new_parent.get_global_transform().xform(new_local_pos)
body.set_global_pos(gpos)
# Wait one physics step for the PhysicsServer to flush the things
yield(body.get_tree(), "fixed_frame")
# Wait again because in my game that wasn't enough apparently...
yield(body.get_tree(), "fixed_frame")
# Now the body is out of the area, has no contact and can't collide anything:
# time to reparent it
body.get_parent().remove_child(body)
new_parent.add_child(body)
# Set position locally,
# because the global one set earlier isn't valid anymore after reparenting
body.set_pos(new_local_pos)
# Restore collision masks so the body works again
body.set_collision_mask(cached_collision_mask)
body.set_layer_mask(cached_layer_mask)
# Emit a signal so the caller can yield until teleport is done
emit_signal("done")
Usage:
var tp = BodyTeleporter.new()
tp.exec(character, dst_room, target_pos)
yield(tp, "done")
This is how you teleport bodies under a new parent, folks.
Fixing this would be really nice, although I understand the frame delay of the physics engine, I believe none of this workaround should be required^^"
@Zylann OMG!! 馃槷 Great workaround! Thanks for your contribution. Let me know if you need some hair, I've got enough left and I'm sure I can borrow some.
Is this still reproducible in the current master branch?
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
I'm pretty certain this is the same issue as #20107. When re-inserting the node the position will be still be the original position and retrigger the enter/exit signals.
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.
I've simplified my minimal project some more, replaced the green level with a button. This makes the bug even more apparent.
After the player collided the first time and the red_level scene got removed and the button enabled,
on clicking the button the fade Animation that should only be triggered if the player is colliding with the Area2D starts immediately playing:

Minimal project:
Area2D_enter_on_remove_bug2.zip
Most helpful comment
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.