Previous scene remains loaded after change_scene_to_packed is called

Godot Version

4.3 stable on Ubuntu

Question

I am learning game dev by making a Breakout game. I have a main menu screen that allows to select a level and play. When the player loses its 3 lifes, the Game scene, by means of an AnimationPlayer, displays a label and at the end of the animation it ends up calling the to_main_menu() method:

# res://scenes/game.gd

...

func to_main_menu():
	get_tree().change_scene_to_file("res://scenes/UI/main_menu.tscn")
    # If I don't put this line, the Game scene is still shown when changing to main menu scene (see end of video attached)	
    # queue_free()  

...

It does not let me upload attachments as a new user, but the video should of the issue should be accessible in this drive link (breakout_bug.mp4 - Google Drive).

I have noticed that if I add the queue_free() call just after changing scenes, then the issues is solved. However, my understanding from the Godot docs is that the change_scene_to_file() method should already delete the current scene and I would not have to explicitly call the queue_free().

It would be nice if someone could explain what I am missing here.

Also, the project is available in github fully (GitHub - EloiSanchez/breakout).

Thanks!

I don’t think the issue comes from get_tree().change_scene_to_file() in your situation.

I tried to clone your project but the main~HEAD does not have your last modifications, I tried to implement it this way:

##game.gd

func _input(event):
	if event is InputEventKey:
		if Input.is_action_pressed("ui_cancel"):
			to_main_menu()


func to_main_menu():
	get_tree().change_scene_to_file("res://scenes/UI/main_menu.tscn")

And it works just fine (tho I am on Godot 4.4)

I think you might be adding two scenes to the root when doing the level selection, then when you call change_scene_to_file() it removes only the current one.

Hey!

Thanks for your reply. You are indeed correct. I have now pushed the commits I had forgotten to push.

From your comment I realized that indeed it has to be something in the level selection way to change scene. Below is the script in the main_menu.gd that changes to scene into a level. If the user presses Start I use the change_to_scene, while if the user goes trough level selection I have to instantiante the Game scene and change its level_number attribute. I followed the Godot docs to change scene manually but clearly I messed something up, since the error only occurs when the game is started from the Level Selection menu.

# res://scripts/UI/main_menu.gd
...

var game = preload("res://scenes/game.tscn")

func _ready():
	for button in level_selection.get_children():
		button.pressed.connect(_to_level.bind(button.text))

...

# Called when main menu button START is pressed
func _on_start_button_pressed() -> void:
    # here I can just change_scene_to_packed because game.level_number attribute defaults to 1
	get_tree().change_scene_to_packed(game)

...

# Called when main menu goes to level selection and user presses a level to play
func _to_level(level: String):
    # Here I need to instantiate game to change its level_number attribute to the value that user pressed
	var game_instance = game.instantiate()
	game_instance.level_number = int(level)
	get_tree().root.add_child(game_instance)
	queue_free()

To reproduce error

  1. Press Select Level in main menu
  2. Click on any level
  3. Press escape
  4. The main menu will be shown on top of the game scene, which will still continue to display and process

This does not happen when starting the game using the Start button in the main menu.

What is it that I am messing up in the _to_level function when changing scenes to game?

Thanks,
Eloi

Aaaaah it’s a sneaky one!

func _to_level(level: String):
	var game_instance = game.instantiate()
	game_instance.level_number = int(level)
	get_tree().root.add_child(game_instance)
	get_tree().current_scene = game_instance #You have to set the current scene for change_scene_to_file() to work later
	queue_free()

get_tree().current_scene is used by get_tree().change_scene_to_xxx to know which scene to remove, and those methods also automatically set current_scene

It’s quite awkward that you have to do it manually, but I don’t think that adding a scene to the root should automatically set it as current_scene either … but hey :man_shrugging:

Damn ok I didn’t know that! Thanks a lot for your help, adding that line makes it work as intended!!

Yeah it’s really hard to find, honestly I am only aware that it’s a thing because I recently tried to make a scene loading system and read this page in the docs

It’s kinda weird that it is this obscure, but I guess most people either use a custom thing or the get_tree().change_scene_ but not both …

(Mainly replying to link the Custom scene switcher tutorial, since it’s quite related to the subject)