Pre-loading large level scenes in the background

Hi,

I have a problem and, despite days of research and trying, I just can’t seem to get anywhere.

My level scenes are quite large.
I want to preload a next level in the background when the current level has been completed and an info animation is running. The next level should then start immediately without a long wait.

Situation:

  • When a level is completed, it emits a “Level Completed” signal that is connected to the “Next Level Loader.”
  • There, the path for the next level is generated (counting up the level number).
  • How do I proceed from here?
    How do I load the next large scene into the background and start it as soon as it is available?

I had a solution for the start screen (see image). But this is too slow for the end of the level.

How can I implement level changes with scenes preloaded in the background?

I am grateful for any suggestions. I am running out of ideas and have not had any success so far.

Thank you.

Jeff

You need to use threaded loading via ResourceLoader.

1 Like

Thank you very much. I had already come up with that example, but I had made some errors. After trying again, I realized that I had accidentally passed the uninstantiated scene class as a parameter instead of the newly created scene object.

Everything works now.

I would like to share the code with you in case others want to switch levels using a loader as well.

Valid for Godot 4.5.1


Global.sd (enabled in Autoload)

This script contains a function that changes scenes and can be called from anywhere in the game.

signal sigPreLoadNextLevel

func change_scene(next_scene_instance):
	var tree = get_tree()
	var cur_scene = tree.get_current_scene()
	tree.get_root().add_child(next_scene_instance)
	tree.get_root().remove_child(cur_scene)
	tree.set_current_scene(next_scene_instance)

Level_1.gd

The next level is preloaded in the background as soon as a level starts.

func _ready() -> void:
	# Trigger pre-loading
	Global.sigPreLoadNextLevel.emit()

LevelLoader.gd

The LevelLoader script responds to the preload signal by assembling the new level name and loading the level scene in the background. Once the scene is loaded, you can switch to it.

const LEVEL_FOLDER = "res://scenes/levels/level_"
var NEXT_SCENE
var next_level_path 		
var allowProcess = false
func _ready() -> void:
	Global.sigPreLoadNextLevel.connect(signalFunc_PreLoadNextLevel)
func signalFunc_PreLoadNextLevel():
	# Count up level and create level name (Level_2.tscn, Level_3.tscn,...)
	var current_scene_file = get_tree().current_scene.scene_file_path
	var next_level_number = current_scene_file.to_int() + 1		
	next_level_path = LEVEL_FOLDER + str(next_level_number) + ".tscn"
		
	# Preload next level
	ResourceLoader.load_threaded_request(next_level_path)
	
    # From here on, check the status permanently.
    allowProcess = true
func _process(delta: float):
	# Check the preloading status
	if allowProcess:
		var progress = []
		var status = ResourceLoader.load_threaded_get_status(next_level_path, progress)
		
		# Preloading done
		if status == ResourceLoader.ThreadLoadStatus.THREAD_LOAD_LOADED:
			
			# Disable this process
			allowProcess  = false
						
			# Get the new level scene
			var loaded_scene = ResourceLoader.load_threaded_get(next_level_path)
			
			# Create an instance
			NEXT_SCENE = loaded_scene.instantiate()

            # Change the scene here or anywhere else you need to.
		    Global.change_scene(NEXT_SCENE)

In my case, however, I use a Timeout function to change the preloaded scene because I display an animation beforehand.

I hope this example helps others as well.

Jeff

I was having problems switching from one scene to another by adding children and removing the current one as shown above. Somehow, the new scene was accessing old variable values from scene 1 that were only local to that scene. Weird.

I have now changed the scene in the “classic” way. Changes I made:

Global.gd

func change_scene(next_scene):
	get_tree().change_scene_to_packed(next_scene)

LevelLoad.gd (changed line)

# Create an instance
NEXT_SCENE = loaded_scene # no .instantiate()