Errors when transitioning between levels (loading different scenes)

Godot Version

4.2.2-stable.mono ( after some threads I migrated to 4.3 )

Question

I have been following The ultimate introduction to Godot 4, and I was trying to refactor the code a little bit and to make it more modular, but I have been getting errors.

In essence, I have a BaseLevel class that is extended by two level classes called OutsideLevel and InsideLevel. Both levels have an Area2D node that sends a signal to the parent node and is received by their script (called OutsideLevel and InsideLevel).

The OutsideLevel script is supposed to load the InsideLevel and vice versa.
Here is some sample code:

BaseLevel

# BaseLevel.gd
class_name BaseLevel extends Node2D

# Utility function that implements the transition to a new scene with a proper transition animation
func transition_to_scene(scene: PackedScene) -> void:
	get_tree().change_scene_to_packed(scene)

InsideLevel

# InsideLevel.gd
extends BaseLevel

const outside_level_scene: PackedScene = preload("res://scenes/levels/outside_level.tscn")

# Signal receptor for when player exits the building gate
func _on_exit_gate_area_body_entered(_body):
	self.transition_to_scene(outside_level_scene)

OutsideLevel

# OutsideLevel.gd
extends BaseLevel

const inside_level_scene: PackedScene = preload("res://scenes/levels/inside_level.tscn")

# Signal receptor for when player enters the building gate
func _on_gate_player_entered_gate():
	self.transition_to_scene(inside_level_scene)

When I run the main scene (outside), I instantly get the following errors:

Error 1

E 0:00:01:0250   _parse_ext_resource: res://scenes/levels/outside_level.tscn:55 - Parse Error: [ext_resource] referenced non-existent resource at: res://scenes/levels/outside_level.gd
  <C++ Source>   scene/resources/resource_format_text.cpp:163 @ _parse_ext_resource()

Error 2

E 0:00:01:0262   set_path: Another resource is loaded from path 'res://scenes/levels/outside_level.tscn' (possible cyclic resource inclusion).
  <C++ Error>    Method/function failed.
  <C++ Source>   core/io/resource.cpp:75 @ set_path()

I assume that these first two errors are somehow related to how the preloading works, but I’m quite lost on the minutia honestly.

Then, as I enter the building, it happens, but I get the following 2 errors:

Error 3

E 0:01:28:0512   level.gd:31 @ transition_to_scene(): This function can't be used during the in/out signal.
  <C++ Error>    Condition "locked" is true.
  <C++ Source>   scene/2d/area_2d.cpp:328 @ _clear_monitoring()
  <Stack Trace>  level.gd:31 @ transition_to_scene()
                 outside.gd:9 @ _on_gate_player_entered_gate()
                 gate.gd:6 @ _on_area_2d_body_entered()

Error 4

E 0:01:28:0512   level.gd:31 @ transition_to_scene(): Removing a CollisionObject node during a physics callback is not allowed and will cause undesired behavior. Remove with call_deferred() instead.
  <C++ Source>   scene/2d/collision_object_2d.cpp:98 @ _notification()
  <Stack Trace>  level.gd:31 @ transition_to_scene()
                 outside.gd:9 @ _on_gate_player_entered_gate()
                 gate.gd:6 @ _on_area_2d_body_entered()

These next 2 seem to concern a change from godot 4.0 (I think that the video was recorded in that version), and godot 4.2 which might be related to this issue. I however lack the general knowledge of a lot of those topics, so some context would really help.

Finally, as I exit the building I get errors 3 and 4 again, and then I find myself unable to re-enter the building.

Could someone help explain this lil mess? :slight_smile:

1 Like

I believe your signal process AND the colliding process is STILL ongoing while you attempt to transition to another scene, which causes the error.
Maybe try making use of call_deferred() to access the transition_to_scene() method?

Using the following workaround worked for errors 3 and 4, however, the initial 2 errors keep appearing, and I cannot re-enter the building after I have already entered it once. Is there a way to avoid the possible cyclic resource inclusion happening here?

Workaround:

get_tree().change_scene_to_packed.bind(packed_scene).call_deferred()

Error 1 is happening because of a circular reference which means that your scene script for OutsideLevel.gd uses a type hint for a variable that doesn’t exist yet. This begs the question if you somehow extend a class and uses that as a variable type hint?
Can you open your outsidelevel.tscn in a texteditor and check for line 55? What kind of resource had failed to load?

Error 2: Are you perhaps using two variables somewhere in your code with the same name?
For example, your BaseLevel.gd has a const variable but then your extending scripts for Inside/Outside levels try to recreate a new const, or preload the same resources.

Oh did not expect the file to not be gibberish hahaha.

Btw, the name of both the scenes and the scripts are actually Outside and Inside, I just changed the names in the sample code I provided to make it more readable.

Error 1
This is what I see on line 54 and 55 respectively.

[node name="Outside" instance=ExtResource("1_g6k2e")]
script = ExtResource("2_4q0hw")

Upon searching for 2_4q0hw, I found this on line 4:

[ext_resource type="Script" path="res://scenes/levels/outside.gd" id="2_4q0hw"]

Seems to be a reference to the script itself?

Error 2
No, I do not seem to have such repetitions or conflicts :confused:

seems like the problem is quite complex.
Is it possible for you to upload the project somewhere, like a github?

I have no idea which files actually exist

  • res://scenes/levels/outside_level.gd
  • res://scenes/levels/outside.gd

I installed the git plugin and pushed the repo here.

I hadn’t closed and re-opened the editor all day, and now when I do so either the inside.tscn or outside.tscn appear corrupted and show some errors on the console :confused:

Sample errors:

scene/resources/resource_format_text.cpp:284 - res://scenes/levels/inside.tscn:19 - Parse Error: 
  Failed loading resource: res://scenes/levels/inside.tscn. Make sure resources have been imported by opening the project in the editor at least once.
  Another resource is loaded from path 'res://scenes/levels/inside.tscn' (possible cyclic resource inclusion).
  editor/editor_data.cpp:880 - Index p_idx = 4 is out of bounds (edited_scene.size() = 4).
  editor/editor_data.cpp:683 - Index p_idx = 4 is out of bounds (edited_scene.size() = 4).
  editor/editor_data.cpp:745 - Index p_idx = 4 is out of bounds (edited_scene.size() = 4).
  Failed to instantiate scene state of "res://scenes/levels/inside.tscn", node count is 0. Make sure the PackedScene resource is valid.

Okay I fixed it.
It’s because you start the game right with outside.tscn, which is preloading stuff BUT it also needs to preload the stuff from the inherited class BaseLevel which takes longer than a frame.
By creating a cheap title screen which transitions to outside.tscn, you can be sure that everything will be properly preloaded. Just make sure that the title screen does not inherit from BaseLevel.

I hadn’t closed and re-opened the editor all day, and now when I do so either the inside.tscn or outside.tscn appear corrupted and show some errors on the console :confused:

Cant you just go to Project → Reload Current Project?
Or delete the .godot folder.

Yes! Your solution seems to have broken this dependency issue. (However I am curious as to how you were able to set an anchor point on a control node whose parent is a Node2D and not a CanvasLayer).

I was fiddling around with the git-plugin which seemed to have some issues, and after that, I migrated to Godot 4.3.

There seems to be a similar issue appearing now, which is also probably related to the preloads. I saw a StackOverflow post here and I’ve also noticed some other posts of people complaining about scenes being corrupted.

The new errors occur when I enter the building and then try to exit it.

Errors

E 0:00:08:0543   instantiate: Failed to instantiate scene state of "", node count is 0. Make sure the PackedScene resource is valid.
  <C++ Error>    Condition "nc == 0" is true. Returning: nullptr
  <C++ Source>   scene/resources/packed_scene.cpp:142 @ instantiate()

E 0:00:08:0543   change_scene_to_packed: Parameter "new_scene" is null.
  <C++ Source>   scene/main/scene_tree.cpp:1433 @ change_scene_to_packed()

I pushed everything on the dev branch, and I’m hoping that if we resolve this, I can also post it on StackOverflow so that maybe people stumbling on this will have an easier time ( especially fellow newbies ).

Okay I solved it.
The big problem is that Godot does not like reusing the same PackedScene again and again after being thrown away via changed_scene_to_packed() because although the PackedScene is gone, it’s actually still in the cache while the game is running.

Therefore we will switch from loaded packed scenes into loading the resource and then instantiating the scenes everytime we require them. Think of the resource as the blueprint and then we mold it into a scene everytime we load the scene. Additionally, I abused the TransitionLayer as a central place to load scenes, therefore TransitionLayer knows the location of all scenes.

Therefore please refer to the following code

# transition_layer.gd
extends CanvasLayer

# Variable holds the currently loaded scene as a reference
var current_scene

# Variable that holds ALL scene paths
var SCENES: Dictionary = {
	"outside" : load("res://scenes/levels/outside.tscn"),
	"inside" : load("res://scenes/levels/inside.tscn")
}

# We force the TransitionLayer to spend as much as time as it wants until all SCENES are loaded
func _ready() -> void: await self.ready

# Utility function that implements the transition to a new scene with a proper transition animation
func transition_to_scene(levelname: String) -> void:
	if SCENES[levelname] == null:
		push_error("Levelname: ", levelname, " does NOT exist!")
		return
		
	$AnimationPlayer.play("fade_to_black")
	await $AnimationPlayer.animation_finished
	
	# We make a switcheroo of scenes. First add the new scene to the root, then remove the old scene.
	var instance = SCENES[levelname].instantiate()
	get_tree().root.add_child(instance)
	get_tree().root.remove_child(current_scene)
	current_scene = instance
	
	$AnimationPlayer.play_backwards("fade_to_black")
# title.gd
extends CanvasLayer

# On startup, the TransitionLayer is told what the loaded scene is
func _ready() -> void: TransitionLayer.current_scene = self

func _on_button_button_up(): TransitionLayer.transition_to_scene("outside")
# outside.gd
extends BaseLevel

# Signal receptor for when player enters the building gate
func _on_gate_player_entered_gate():
	var tween: Tween = create_tween()
	tween.tween_property($Player, "speed", 0 , 0.5)
	tween.tween_callback(func(): TransitionLayer.transition_to_scene("inside"))
# inside.gd
extends BaseLevel

# Signal receptor for when player exits the building gate
func _on_exit_gate_area_body_entered(_body):
	var tween: Tween = create_tween()
	tween.tween_property($Player, "speed", 0 , 0.5)
	tween.tween_callback(func(): TransitionLayer.transition_to_scene("outside"))

For the future, you might need an autoload the holds the position of the player or some stats. You might also want to think about where to “place” the player after the scene loads, right now the player always spawns in the same position.

Yup, that seems to have solved it. Thanks!

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.