Reparenting node to new scene

Godot Version

4.3

Question

I have an issue when trying to reparent the player character to new rooms. Trying to enter a door crashes the game. It only works if I have a node in the new rooms called “Player”. Then it will let me take control of that version of the character, but starting from wherever I have dropped it in the scene, not from the spawning position. The instance of the player that entered the door will spawn at the spawning position if i skip the queue_free() at the end, but then I get two player characters. I can change the name of an empty node to Player and then it seemingly works at first but strange things happen.

I guess there are some issues with the reparenting but I’m not sure what. I tried call_deferred on the queue_free to not get rid of the original scene before the player had been reparented but that did not solve it.

If i remove the deferred_call on change_room() i get an error message for the line:
get_tree().get_root().add_child(new_room)
Error message:
“change_room(): Can’t change this state while flushing queries. Use call_deferred() or set_deferred() to change monitoring state instead.”

The only script that has anything to do with switching scenes is this:

extends Area2D

@export var spawn_position : String
@export var target_room_path : String

func _on_body_entered(body: Node2D) → void:
if body.is_in_group(“player”):
call_deferred(“change_room”)

func change_room():
var player = get_tree().get_current_scene().get_node(“Player”)
var current_room = get_tree().get_current_scene()

var new_room = load(target_room_path).instantiate()


get_tree().get_root().add_child(new_room)
get_tree().set_current_scene(new_room)

var spawn_point = new_room.get_node_or_null("spawns/" + spawn_position)
if spawn_point:
	player.global_position = spawn_point.global_position
else:
	push_warning("Missing spawn point: spawns/" + spawn_position)

current_room.call_deferred("queue_free")

You’ll have to add the player to the new_room scene before you free the old one. e.g.

var new_room = load(target_room_path).instantiate()
new_room.add_child(player)

My guess it crashes the game because some other part of your game checks if a player character exists. Which is why it’s allowing you to play when you add a dummy player to the scene.

Ah, yes, i messed around a bit with the ordering last night, trying different things. Added the actual reparenting back now, but still doesn’t work. I’ve checked a bit more with the scene in an otherwise empty project and believe that there is some issue with how i’ve set up the ordering. Not sure what though.

I think an autoload would be better anyway so i will go for that but i am curious about what is happening and why it doesn’t work.

Are any of the nodes in the level trying to access the Player node before you transition the scene? This could be in your new level, but also the old one. I.e. sometimes .queue_free() calls a body_exited() signal from removing the body from the scene entirely which crashes the game. The error message should be a good pointer on where to look in that case.

I’ve only every coded scene transitions using a PlayerStats Singleton and adding a new player instance so not really my area of expertise unfortunately, but I’ve had the above happen before.

Some news!
Tried to check exactly what happened using a lot of print statements along the entire process.
The player did appear at the correct coordinates and all of that but refused to reparent to the new scene. There were a lot of issues with very odd infinite loops, ordering the steps correctly and unintuitively, using call_deferred etc on the right step, awaiting next frame to not collide with physics processes etc. I think it would have been possible to fix the problem but it felt like whatever solution i’d be able to come up with would end up causing other errors for other parts of the game. So in case anyone finds this thread when searching for information on how to reparent to new scenes at runtime, i’d recommend skipping it unless you have a good grasp of how the engine works underneath the hood and have no better options.

1 Like