godot: Errors when changing scene using change_scene_to_file()
Tested versions
Reproducible in: 4.2 stable
System information
Win 10, Godot 4.2 stable, Forward +, dedicated Nvidea RTX 1080
Issue description
Edit: currently recommend workaround/fix seems to be to call change_scene_to_file()
deferred.
So instead of
get_tree().change_scene_to_file("res://some_scene.tscn")
write:
get_tree().change_scene_to_file.bind("res://some_scene.tscn").call_deferred()
Original issue description:
Using get_tree().change_scene_to_file("res://some_scene.tscn")
works , but it produces a number of red errors. These seem to be different depending on the circumstances.
To test this, I made two scenes, Scene A and Scene B using Area3Ds and CharacterBody3D (with default template script):
In Scene A I change scene by walking into an Area3D with a CharacterBody3D (template script), triggering a body_entered
signal:
extends Node3D
func _on_area_3d_body_entered(body):
if "Player" in body.name:
get_tree().change_scene_to_file("res://change_3d_scene_b.tscn")
This will produce these two errors:
E 0:00:05:0401 change_3d_scene_A.gd:5 @ _on_area_3d_body_entered(): This function can’t be used during the in/out signal. <C++ Error> Condition “locked” is true. <C++ Source> scene/3d/area_3d.cpp:289 @ _clear_monitoring() <Stack Trace> change_3d_scene_A.gd:5 @ _on_area_3d_body_entered()
E 0:00:05:0401 change_3d_scene_A.gd:5 @ _on_area_3d_body_entered(): Removing a CollisionObject node during a physics callback is not allowed and will cause undesired behavior. Remove with call_deferred() instead. <C++ Source> scene/3d/collision_object_3d.cpp:113 @ _notification() <Stack Trace> change_3d_scene_A.gd:5 @ _on_area_3d_body_entered()
In Scene B I change scene by walking into an Area3D with a CharacterBody3D, triggering a body_entered
signal which will set a can_interact
boolean variable in the character script. If the player then presses an Input action, the character fires a global custom signal, which the level connects to and changes the scene:
Global Autoload:
signal change_level
Character:
var can_interact = false
func _physics_process(delta):
if can_interact:
if Input.is_action_just_pressed("ui_accept"):
Global.change_level.emit()
Level:
extends Node3D
func _ready():
Global.change_level.connect(change_to_other_level)
func _on_area_3d_body_entered(body):
if "Player" in body.name:
body.can_interact = true
func _on_area_3d_body_exited(body):
if "Player" in body.name:
body.can_interact = false
func change_to_other_level():
get_tree().change_scene_to_file("res://change_3d_scene_a.tscn")
This will produce these four errors:
E 0:00:07:0043 change_3d_scene_b.tscn::GDScript_7cg2h:34 @ _physics_process(): Condition “!is_inside_tree()” is true. Returning: Transform3D() <C++ Source> scene/3d/node_3d.cpp:343 @ get_global_transform() <Stack Trace> change_3d_scene_b.tscn::GDScript_7cg2h:34 @ _physics_process()
E 0:00:07:0043 change_3d_scene_b.tscn::GDScript_7cg2h:34 @ _physics_process(): Condition “!is_inside_tree()” is true. Returning: Transform3D() <C++ Source> scene/3d/node_3d.cpp:343 @ get_global_transform() <Stack Trace> change_3d_scene_b.tscn::GDScript_7cg2h:34 @ _physics_process()
E 0:00:07:0043 change_3d_scene_b.tscn::GDScript_7cg2h:34 @ _physics_process(): Parameter “body->get_space()” is null. <C++ Source> servers/physics_3d/godot_physics_server_3d.cpp:915 @ body_test_motion() <Stack Trace> change_3d_scene_b.tscn::GDScript_7cg2h:34 @ _physics_process()
E 0:00:07:0043 change_3d_scene_b.tscn::GDScript_7cg2h:34 @ _physics_process(): Condition “!is_inside_tree()” is true. Returning: Transform3D() <C++ Source> scene/3d/node_3d.cpp:343 @ get_global_transform() <Stack Trace> change_3d_scene_b.tscn::GDScript_7cg2h:34 @ _physics_process()
Note: Line 34 in the character script is just move_and_slide()
Possibly related issue (however that’s a different error): https://github.com/godotengine/godot/issues/84681
Steps to reproduce
Use get_tree().change_scene_to_file("res://some_scene.tscn")
as described above, or test the provided Minimal Reproduction Project below.
Minimal reproduction project (MRP)
About this issue
- Original URL
- State: open
- Created 7 months ago
- Reactions: 8
- Comments: 25 (10 by maintainers)
Both situations are caused by the change in 4.2 that makes
change_scene_to_*
methods no longer being deferred internally. When you call those methods, the current scene is deleted immediately, and the new scene is added to the root shortly after.In this case you’d need to defer both calls to
change_scene_to_file
to work around that change and make it work similarly to what used to happen in 4.1.The main relevant changes were done in https://github.com/godotengine/godot/pull/78988 and https://github.com/godotengine/godot/pull/85184, perhaps this will give you more context on the why.
This was explained here https://github.com/godotengine/godot/issues/85852#issuecomment-1843293368 - specifically ‘manipulation during a physics callback’.
Personally, I’ve always called this method deferred to avoid any potential problems. Usually games also have animations, and loading, instead of an immediate transition. For what it’s worth, a long existing demo (https://github.com/godotengine/godot-demo-projects/blob/master/loading/autoload/global.gd) explained how to make the change manually, with a deferred call for the reasons mentioned, that I used to use, in conjuction with threaded loading, to avoid errors with the old implementation of the methods in question.
Maybe this means
change_scene_to_file()
needs a companion method then? Similarly tofree()
andqueue_free()
? So maybequeue_change_scene_to_file()
?As a new user, I learned very early on to use
queue_free()
rather thanfree()
, even though I did not fully understand the implications. A similarly namedqueue_change_scene_to_file()
which executes deferred by default would be similarly intuitive, I guess. At least it would match existing syntax and would be more self-explanatory.I very much disagree.
get_tree().change_scene()
just worked. I can’t recall it ever failing or produce any errors in all those years. Thanks to it, changing scenes was one of the least confusing aspects about Godot. Now with, not so much anymore.I’m telling you as a long time user: It’s not. For you, for akien or other contributors and C++ magicians, what is written in the release nodes and API description might sound totally obvious and self-explanatory. I have read both the documentation and the release post many times before creating this issue. To me, a non C++ user who does not work with the Godot source every day: Why we are getting these errors now, when used as explained in the OP and MRP, is totally opaque, non transparent. Please accept this point of view.
I feel like these methods (
change_scene_to_file
andchange_scene_to_packed
) should in fact be called asdeferred
implicitly as they once used to, because that is arguably the safer approach. Its safer because otherwise it throws errors during physics callbacks, and changing scenes during a physics callback is a really common scenario.If someone wants to change scene immediately (which is unsafe because it will give errors if invoked during physics callbacks, which again is really common), separate functions should be provided (such as
change_scene_to_file_now
andchange_scene_to_packed_now
).My Elaboration: We should streamline it as much as possible for the casual user. From the perspective of the casual user, all he wants is to invoke a function and his scene should change without any errors (no matter how busy his scene is with physics objects in motion during the scene change). It can be mentioned in the docs that these methods change the scene as deferred, not immediately, for more clarity.
Now if someone wants to change the scene immediately instead of Godot doing it smoothly over time (to avoid errors) for him, he can invoke these methods appended with
_now
in their method names, which will change it immediately without defer. Think of the casual user and a streamlined experience.You can argue against this all you want, I can’t change your anyone’s mind, but the fact stands that the user now has to type more and think more about his physics objects possibly getting offed while in motion resulting in errors, which is overall a less streamlined experience than before where the method took care of it.
I tried this in the MRP and it works with no errors, using v4.3.dev.custom_build [84692c625]. You can also do
get_tree().call_deferred("change_scene_to_file", "res://change_3d_scene_a.tscn")
and it also works, in both scene changes of the MRP, without any errors.Notice that, in the demo above, the function that calls
free()
instead ofqueue_free()
is being called deferred, which is why it says it’s safe to do so.As for the docs you mention, they probably should mention ‘deferred’ somewhere, I agree.
Thanks to everyone who commented and tries to shine a light on these errors! As someone who has used Godot for years, I would like to state that I feel like non of this is transparently communicated to the user. Not through the errors, not through documentation, not through method names.
I don’t think the method names makes it clear on how to use them, neither does the description, and even after you all explaining, I still don’t have a firm grasp on what is going on, why it is necessary we now have changing scenes be much more complicated, and confusing when it worked fine before (at least in my usage).
That’s from someone with more than 5 years of Godot experience. I can only imagine stuff like this would be a lot more confusing for someone starting out. Please keep in mind how most of you are daily work with the Godot source, that’s not the experience or background knowledge of most Godot users.
I’m not sure what is the solution here, if it is just better documentation or something else, but I feel like something needs to be done to make something as central to using Godot as changing scenes a lot smoother and less error prone.
The physics callback comes from area, not body. If you do
get_parent().remove_child(self)
you should be getting the same error.I am a new user who is accustomed to using C #, so the correct code for this issue is:
GetTree().CallDeferred("change_scene_to_file",path);
And the original code was:GetTree().ChangeSceneToFile(path);
This is a bit against habit
The change was made with the goal of making scene transitions safer and smoother
And adding a specific error if changing the scene when in this state is being investigated, but the method name doesn’t give any indication either way to the behavior, it’s not named
change_scene_to_file_now
or something similar, most method don’t communciate their timing except when there’s a companion method that doesn’t defer part of the call. Note that the method is equally confusing in the name in the previous behavior where it did all at the end of the frame, without any name indicating it wasn’t instant.But I don’t see how it’s not transparent? It is documented and in the release post. This change was done early in the 4.2 release cycle and has been tested extensively and been there for testing by people using this behavior 🙂
@akien-mga Thank you for the links!
This does not help. If the user is expected to only call this method deferred, it should say so in the method description. Moreover having to
call_deferred()
is also not mentioned in the other method this links to:It’s discussed in https://godotengine.org/article/godot-4-2-arrives-in-style/#critical-and-breaking-changes with a link to the PR that changed it. There wasn’t much discussion there at the time, as it was an attempt at fixing critical issues and we hadn’t considered all implications. This was further discussed in #85251.
See the note you quoted 😃
The workaround works properly, but how do you get the result from the
change_scene_to_file
method when deferring the call?call_deferred()
is documented to always returnnull
. I’d like to receive and handle the errors returned by the method.Except for UI, I have a hard time to imagine what kind of situations these might be. Most scene changing situations in my games and most games I see made by others are triggered by a physics body entering an Area or interacting with an Area, triggering a custom signal. Such as I tried to model here in the MRP.
I just tested this in Scene A of the MRP and I’m not sure what you mean. Both
body.get_parent().remove_child(body)
andbody.queue_free()
work fine just as expected without any errors. Evenbody.free()
works without any errors in the above MRP.It’s not intended to only be called deferred. In most situations you can call it as is and have the scene change done right away.
The errors you get come from doing tree manipulation during a physics callback, as they mention. You would have the same issue trying to remove a physics node in
_on_area_3d_body_entered
- which is what changing the scene ends up doing too.I assume there are good reasons for this, but from a user perspective, it seems like more logical, user friendly and intuitive would be to deferred it internally as it was in earlier versions of Godot. Where can I read up on the reasoning for this change?
Either way, these errors do not help much to find the cause or the fix for this issue.
It’s also not documented in the methods description: