Skip to content

Parenting Pause menu to root instead of current scene causes Restart to keep game (scene tree) paused due to close swapped order #457

@hsandt

Description

@hsandt

I'm experimenting with parenting the Pause menu to scene tree root instead of current scene (top node just below the root) because I don't want to destroy the Pause Menu, I want to keep it alive across the scenes of my level.

I made a custom_pause_menu_controller.gd with the following change:

func _ready() -> void:
	pause_menu = pause_menu_packed.instantiate()
	pause_menu.hide()
	# CUSTOM: Add Pause Menu to root, not `current_scene`, so that it is not destroyed when changing room
	get_tree().root.call_deferred("add_child", pause_menu)

However, I found that doing this would cause the Pause menu Restart to keep the game frozen (scene tree paused) for a very weird reason.

Note: this has other implications and causes other bugs but I'm aware of it and ready to fix true logical bugs related to keeping the Pause Menu alive under root. I'm just talking about a specific weird bug that I'm unsure how to fix here.

The issue seems to come from the Restart Confirmation popup:

Image Image

I studied the callstack to find where the scene tree could be paused and found this in overlaid_window.gd:

func close() -> void:
	if not visible: return
	_scene_tree.paused = _initial_pause_state

This code restores the paused state from the level under that window (previously saved in _overlaid_window_setup)

For the PauseMenu, it's false because the in-game before opening the menu was not paused.
For the RestartConfirmation, it's true because the PauseMenu just before had already paused the game.

In normal usage (without my patch, when Pause Menu is under current_scene), when confirming Restart, this is what happens:

  1. RestartConfirmation: reverts _scene_tree.paused to true
  2. PauseMenu: reverts _scene_tree.paused to false

so game is not paused after Restart => OK

Callstack details:

  1. RestartConfirmation: ConfirmationOverlaidWindow._on_confirm_button_pressed > confirm > confirmed.emit() > pause_menu.gd: _on_restart_confirmation_confirmed() > SceneLoader.reload_current_scene() > (RestartConfirmation node removal) > RestartConfirmation _exit_tree() > close() > _scene_tree.paused = _initial_pause_state (true)
  2. Followed by (PauseMenunode removal) > RestartConfirmation _exit_tree() > close() > _scene_tree.paused = _initial_pause_state (false)

With my patch, when Pause Menu is under root, it is not destroyed and therefore doesn't go through _exit_tree but only normal close() call instead. However the close methods are called in reverse order:

  1. PauseMenu: reverts _scene_tree.paused to false
  2. RestartConfirmation: reverts _scene_tree.paused to true

so game is paused after Restart => not OK

Callstack details:

1. RestartConfirmation: `ConfirmationOverlaidWindow._on_confirm_button_pressed > confirm > confirmed.emit() > pause_menu.gd: _on_restart_confirmation_confirmed() > OverlaidWindow.close > _scene_tree.paused = _initial_pause_state (false)`
followed by a normal close:
2. RestartConfirmation: ... > confirm > close > _scene_tree.paused = _initial_pause_state (true)

So it looks like the normal behavior works but counting on node being removed to "force" the intended order (resolving the menu window stack in reverse order) whereas a normal execution would not call close in the desired order, causing the game to be frozen after restart.

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions