godot-docs icon indicating copy to clipboard operation
godot-docs copied to clipboard

Teach how to work with `change_scene_to_packed` and `change_scene_to_file`

Open stephanbogner opened this issue 1 year ago • 2 comments

Your Godot version:

4.2.1

Game context:

  1. Player leaves level
  2. Screen fades to black
  3. Scene is switched
  4. Black fades away to reveal the new scene

Issue description:

I just upgraded Godot and now logic executed directly after scene switching (via change_scene_to_packed) is broken. I assume due to the following (docs):

The current scene node is immediately removed from the tree. From that point, Node.get_tree called on the current (outgoing) scene will return null. current_scene will be null, too, because the new scene is not available yet.

I generally like that the behaviour is now more predictable ... but I couldn't find any information on how to work with it.

What didn't work:

  • await get_tree().process_frame -> Doesn't work due to get_tree() being null
  • await get_tree().create_timer(duration).timeout -> Doesn't work due to get_tree() being null
  • await Engine.get_main_loop().process_frame didn't work (idea source)
  • call_deferred() -> Never got triggered (I assume because scene didn't exist anymore)
  • No signal exists like tree_created()

What would have worked:

  • I assume the new scene's _ready function would have triggered correctly but as the transitions should happen in each level switch I didn't want to add code to each level's _ready-function

My current workaround

In one scene – e.g. level_1 – I now call the autoload singleton LevelSwitcher.switch_to("res://level_2.tscn") instead of executing the code directly. This works, but my approach to detect the new scene is really dirty (see below).

level_switcher.gd

extends Node

var current_scene
signal scene_changed

func _process(delta):
	# Feels dirty
	if current_scene != get_tree().get_current_scene():
		emit_signal("scene_changed")
		current_scene = get_tree().get_current_scene()

func switch_to(scene_file_path):
	# 1. Trigger "leaving level"-transition and await its end

	# 2. Switch scene
	var scene = ResourceLoader.load_threaded_get(scene_file_path) # This was loaded before somewhere else
	get_tree().change_scene_to_packed(scene)
	
	# 3. Await switch
	await scene_changed
	
	# 4. Spawn player and trigger "entering level"-transition

URL to the documentation page (if already existing):

Proposal

Change the "Change scenes manually"-page to "How to change scenes" with the following content:

  • Automatic scene switching: Most basic example for change_scene_to_file
  • Automatic scene switching (advanced): Loading scene in the background using change_scene_to_packed and trigger something right after the scene switch (I still don't know how to do this correctly)
  • Manual scene switching: The content the page currently is focussed on

This probably relates to #8846

stephanbogner avatar Jan 30 '24 20:01 stephanbogner

I agree that better docs info on this would be super helpful! I'm running into a similar problem right now. After I use change_scene_to_packed, my _ready() functions on scripts in the new scene don't run.

amarraff avatar May 16 '24 19:05 amarraff

I can also recommend this tutorial by "The Shaggy Dev". I now combined my approach mentioned above with his transition logic and it works really well.

stephanbogner avatar Aug 01 '24 11:08 stephanbogner