I am developing using godot 4.0.3.
I have written code that uses Area3D to perform a screen transition when the player enters the Area3D area.

The Area3D is in SceneA and it receives the signal of body_enterned and transitions to SceneB.

I used the code remove_child(SceneA)/ add_child(SceneB) for the transition.

I did not queue_free() SceneA because I want to resume it immediately.

While manipulating the Player in SceneB, I noticed that the Area3D placed in SceneA was sending a signal.

Since the Node in SceneA is remove_child, I am confused by the counter-intuitive behavior.

I am wondering if this is due to the spec.

If so, I would like to know what other best practices there are for switching between 3D scenes.

Thank you.


I can reproduce it with the following code (Player is something that can be manipulated)
In this case _on_area_3d_body_entered is called many times.

extends Node3D

@onready var _area := $Area3D as Area3D

func _ready():
	_area.body_entered.connect(_on_area_3d_body_entered)

func _process(delta):
	pass

func _on_area_3d_body_entered(body: Player):
	if not body is Player:
		return
	print('entered!')
	remove_child(_area)
	print('removed!')
  • The connect function also had options.

    ● CONNECT_DEFERRED = 1
    Deferred connections trigger their Callables on idle time, rather than instantly.

    Normally, this seems to be a good place to start.

There was an error. I was not aware of it.

Removing a CollisionObject node during a physics callback is not allowed and will cause undesired behavior. Remove with call_deferred() instead.

The following fixes the problem.

extends Node3D

@onready var _area := $Area3D as Area3D

func _ready():
	_area.body_entered.connect(_on_area_3d_body_entered)

func _process(delta):
	pass

func _on_area_3d_body_entered(body: Player):
	if not body is Player:
		return
	print('entered!')
	var rm = func(): 
		remove_child(_area)
		print('removed!')
	rm.call_deferred()

The connect function also had options.

● CONNECT_DEFERRED = 1
Deferred connections trigger their Callables on idle time, rather than instantly.

Normally, this seems to be a good place to start.

I think 4.0.3 also added some caching that affects this, which was barely mentioned in the release notes. I noticed that after loading a new scene while in collision with an Area3D, I was still in collision with it even though I'd removed it from the node tree. Once I realized what was happening it was an easy fix.

    DaveTheCoder I didn't really want to go into it, lol.

    I have a variable pointing to the current interaction object.
    When the player collides with a different interaction object, I update this variable.
    Before 4.0.3, I clear that variable to null just before loading a scene.
    But as of 4.0.3, I get a collision in _physics_process() immediately after loading, so my code sees that it's not null and updates the variable.
    NOT clearing the variable prevents this.

    Apparently this works because I'm using ResourceLoader.load_threaded_request() to load the scene, and I clear that variable when loading is complete, in _process(). This might have something to do with the execution order of _process() and _process_physics().

    I feel like this isn't a great solution and I expect it to break again in future Godot versions.