I don’t understand why on_body_entered in my door script fires twice when the player enters it.
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.
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?
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.
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:
If the new parent is the same as the old parent, nothing happens.
The node is removed from being the previous parent’s child.
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.
Had the same issue. Don’t know the reason but I know a workaround.
Run your event handler in the _physics_process
call area2d.monitoring = false before reparenting
# disable _physics_process if you don't need it
# in this scene to cut back on processing
func _ready():
set_physics_process(false)
# store bodies to process in _physics_process
var bodies = []
# run your event handler here
func _physics_process(_delta):
for i in bodies.size():
my_event_handler(bodies.pop_back())
# when you're done
# disable processing again
set_physics_process(false)
func _on_body_entered(body):
# usually you would call 'my_event_handler(body)' here
# but we won't do that
# instead store body and process it later
# in _physics_process
bodies.push_back(body)
# enable processing
set_physics_process(true)
func my_event_handler(body):
my_area2d.monitoring = false
body.reparent(new_parent_node)
my_area2d.monitoring = true