If you use get_tree().change_scene_to_file() or change_scene_to_packed then it will remove the currently loaded scene from the tree, so anything you added into the overworld scene while it was loaded, like that enemy you just created will be gone.
You could save all of the data you needed from the overworld before you leave it and restore it all when you load it again, but I think there’s a much simpler way to handle it:
You’ll want to make use of the process_mode of everything in your overworld scene that needs to be paused while you’re away, but if it’s all set to the default of Inherit then that’s exactly what you need already.
When you want to show a side-scene like the combat scene you first need to create an instance and add it to the tree, adding it into a CanvasLayer that is higher than the main scene’s canvas layer is a good way to make sure that nothing in the main scene will show up over the side-scene. Then the layer SideSceneLayer should have process_mode set to Pausable and the main scene will set itself to process_mode = Disabled when it adds a side scene.
So we do something like:
# In player or detector script or whatever
new_combat = preload("res://Scenes/Game/CombatScene.tscn").instantiate()
get_node("main scene node here").show_side_scene(new_combat)
# In the main scene root script
func show_side_scene(side_scene: Node) -> void:
$SideSceneLayer.add_child(side_scene)
process_mode = Node.PROCESS_MODE_DISABLED
# Connect to a signal on the canvas layer to be able to detect when
# the side scene is removed so we can re-enable the main scene.
func _on_SideSceneLayer_child_exiting_tree(_node: Node) -> void:
process_mode = Node.PROCESS_MODE_PAUSABLE
side_scene_finished.emit() # Maybe some objects want to know when we just came back
With this, anything in the main scene will have it’s processing disabled which should keep it suspended pretty nicely but there’s a few things to remember
- If you are using global time values directly obviously you will have an issue when you resume
- If you were using a
get_tree().create_timer() that timer wont properly pause because the whole game isn’t paused, use a tween registered to the node itself (create_tween()) or a temporary Timer node instead
_process, _physics_process and _input wont be called on the suspended nodes but the physics engine itself wont be paused, however the default disable_mode on physics bodies removes them from the physics simulation when they have process disabled, you might need to play around with this setting on them if you use physics
- Rendering might be faster if you hide some or all of the stuff in the main scene while it’s being covered (assuming you can’t see through the side scene) Hiding the main scene root should work because
CanvasLayer doesn’t inherit the visibility of it’s parent.
This can also simplify passing data back and forth between the main scene and side scenes to pass in:
func show_side_scene(side_scene: Node, extra_stuff: Dictionary) -> void:
$SideSceneLayer.add_child(side_scene)
if extra_stuff:
side_scene.custom_setup(extra_stuff) # Make sure that scene has a custom_setup method
process_mode = Node.PROCESS_MODE_DISABLED
And to get stuff back, have the side scene emit a return_some_stuff signal and emit it right before it gets removed
# Whatever place we're starting the side scene
new_combat = preload("res://Scenes/Game/CombatScene.tscn").instantiate()
new_combat.return_some_data.connect(handle_stuff_returned, CONNECT_ONE_SHOT)
get_node("main scene node here").show_side_scene(new_combat)
func handle_stuff_returned(stuff: Dictionary) -> void:
...
If the side scene doesn’t always emit the return_some_stuff signal make sure to watch for when it leaves and disconnect the signal if you still have it connected.