Search code examples
scenegodotgdscript

How can I switch between scenes without destroying (freeing) the scene I'm leaving?


Hey there in my game there are battles. Currently the battle is a separate scene and when a player walks up to an NPC we have a little transition and then leave the scene and we go to the battle scene. Once the battle scene is completed I want to return to the previous scene we were at with everything exactly the same. I currently have a scene switching singleton called scene_switcher with the current setup

extends Node

var _params = null

func change_scene(next_scene, params = null):
    _params = params

    # global_data is another singleton I use for storing state and other global data
    global_data.previous_scene = get_tree().get_current_scene()
    get_tree().change_scene(next_scene)

func return_to_previous_scene(params = null):
    _params = params
    get_tree().get_current_scene().free()
    get_tree().get_root().add_child(global_data.previous_scene)
    get_tree().set_current_scene(global_data.previous_scene)

func get_param(name):
    if _params != null and _params.has(name):
        return _params[name]
    return null

when we're going into the battle I use

scene_switcher.change_scene("res://battlescene.tcsn", {players_deck, opponents_deck})

which stores the current scene exactly as it is and passes over player data to the battle scene.

once the battle is over I call return_to_previous_scene() which I call when one of the victory conditions is met like so

scene_switcher.return_to_previous_scene()

however I get the error:

Attempted to free a locked object (calling or emitting).

I've tried deferring the call to idle time based off of this code on the singleton autoload page of the docs however when I do:

 call_deferred("scene_switcher.return_to_previous_scene") 

it never gets called (the game_over function works, it prints out the result and my other tests have it working with the code below, I think the scene never idles which mean the code never gets a chance to fire)

Now I can use:

scene_switcher.change_scene("res://map.tcsn", {})

but that reverts the scene back to it's original state and is not very scalable (how will I know which scene to load if I can battle people in 12 different levels?)

So how can I halt the current scene. leave, and then return to the previous scene with everything how I left it? Thanks :)


Solution

  • I asked this same question on reddit and got an answer that solved my problem. Copying over here for anyone who might find this question in the future. All credit to /u/thomastc

    Their answer:

    call_deferred takes a method name only, so:
    
    scene_switcher.call_deferred("return_to_previous_scene")
    
    Alternatively, just use queue_free instead of free, to defer only the freeing.
    

    I was not using call_deferred correctly.

    I also made the slight modification of using duplicate() on the scene in change_scene to keep a copy that isn't freed.

    so

    global_data.previous_scene = get_tree().get_current_scene()
    

    became

    global_data.previous_scene = get_tree().get_current_scene().duplicate()
    

    which now works as expected :)