I don't understand why on_body_entered on my area2d fires twice

Godot Version

4.2

Question

I don’t understand why on_body_entered in my door script fires twice when the player enters it.

Screenshot 2024-01-03 090329
1

extends Area2D

@export var next_level : PackedScene
@export var camera_path : NodePath

func _ready():
	self.body_entered.connect(_on_body_entered)

func _on_body_entered(body):
	if body is CharacterBody2D:
		print("firing")
		
		var root = get_tree().get_root()
		var old_scene =  root.get_child(0)
		var next_scene = next_level.instantiate()

		root.add_child(next_scene)
		
		var camera_instance = get_node(camera_path)		
		camera_instance.reparent(next_scene)
		body.reparent(next_scene)
		
		old_scene.queue_free()

The player’s collision layers are 1 and 2. The door’s collision layer and mask is set to layer 2.

can you check if the firing happened after the reparent?

1 Like

So I added

print("firing2")

after the reparent and renamed the first print to ‘firing1’ and, odd enough, it never prints ‘firing2’. I just get 2 ‘firing1’ prints.
I wondered why the old scene never freed and I guess it’s because the code doesn’t run anything after the reparent.

So I guess I now have 2 questions:

Why is it firing twice?
Why isn’t any code after the reparent running?

Can you write down the new code?
There’s inherently nothing in the reparent function that should make the script stop running.

Also, try printing the stack instead of just printing “firing”, maybe we’ll get a little bit more context.

1 Like

new code:

extends Area2D

@export var next_level : PackedScene
@export var camera_path : NodePath

func _ready():
	self.body_entered.connect(_on_body_entered)

func _on_body_entered(body):
	if body is CharacterBody2D:
		print("firing1")
		print_stack()		
		
		var root = get_tree().get_root()
		var old_scene =  root.get_child(0)
		var next_scene = next_level.instantiate()

		root.add_child(next_scene)
		
		var camera_instance = get_node(camera_path)		
		camera_instance.reparent(next_scene)
		body.reparent(next_scene)
		print("firing2")
		print_stack()
		
		old_scene.queue_free()
		

I placed print stack at both firing print statements

here’s what is printed:

firing1
Frame 0 - res://Scenes/Door.gd:12 in function '_on_body_entered'
firing1
Frame 0 - res://Scenes/Door.gd:12 in function '_on_body_entered'
Frame 1 - res://Scenes/Door.gd:22 in function '_on_body_entered'

so this just need to somehow invalidate the old scene
try:

func _on_body_entered(body):
	if body is CharacterBody2D:
		print("firing1")
		print_stack()		
		
		var root = get_tree().get_root()
		var old_scene =  root.get_child(0)
		var next_scene = next_level.instantiate()
		root.remove_child(old_scene)
		root.add_child(next_scene)
		
		var camera_instance = get_node(camera_path)		
		camera_instance.reparent(next_scene)
		body.reparent(next_scene)
		print("firing2")
		print_stack()
		
		old_scene.queue_free()

this could possible make an error if your body or camera is inside the old_scene

if above is true, then you really need to make a temporary node just to hold these two, so you can remove the old scene first (after moving the body/camera nodes) before adding and reparenting from temporary node

I think i’m going to accept that either i’m doing something wrong by trying to use on_body_entered or that sometimes events are going to fire twice for no apparent reason and move on, tbh.

I’m at the point where none of this is really making any sense to me.

it’s just you reparent the player or “body” into exact same position but the old scene havent been removed or queue freed, hence why double firing

I was originally rather confused regarding why your body_entered signal fires off twice, but, it makes a lot of sense, actually.

The Stack

Let’s parse the stack output real quick:

firing1
Frame 0 - res://Scenes/Door.gd:12 in function '_on_body_entered'
firing1
Frame 0 - res://Scenes/Door.gd:12 in function '_on_body_entered'
Frame 1 - res://Scenes/Door.gd:22 in function '_on_body_entered'

Line 12 is obviously the current line, being:

print_stack()

This makes Frame 1 (line 22), the previous call that led us here be the following:

body.reparent(next_scene)

Well, this kind of solves the problem of “why does the signal fire twice”, since this is part of how reparenting works.

The Behaviour of Reparent

Reparenting nodes works the following way:

  1. If the new parent is the same as the old parent, nothing happens.
  2. The node is removed from being the previous parent’s child.
  3. The node is added as a child of the new parent.

Generally speaking, this looks fairly innocent, but it has an implication when it comes to tree signals specifically.
Both adding and removing children calls the _set_tree method on the node, which calls tree_entered and thus on Area2D it calls _body_enter_tree, and, well… you get get the idea.