+1 vote

I am trying to write a scene manager in Godot and keep getting the following error:

Invalid get index... (on base: 'previously freed instance')

I think this error is generated because certain nodes in my scene are trying to access attributes of other nodes which were just freed in my scene change code.

Here is the scene change code which is called from a globals.gd singleton:

func set_scene(scene_path):
    # remove current scene
    current_scene.queue_free()
    print("FREED SCENE")
    # instance new scene
    var s = load(scene_path)
    current_scene = s.instance()
    print("INSTANCED SCENE")
    # add scene instance to tree        
    get_tree().get_root().add_child(current_scene)
    print("ADDED SCENE TO TREE")

These print statements confirm that the error is being thrown after the new scene is added to the tree. In fact, the error always has to do with references to attributes of other nodes that are accessed via variables.

My question is: Why is this error being thrown even when I free everything from the previous scene before I instance the new scene?

I may be able to protect my code from this kind of error using weakref(), as mentioned here, but I really would prefer to avoid this since it muddies up the code. I have also tried to use call_deferred() with set_scene() but it doesn't help.

NOTE: I originally asked this question on the Game Dev Stack Exchange, but figured this would be a better venue. I will echo any answer I get on either site.

in Engine by (1,586 points)

hm. I did a similar thing as you did. but I don't have such problem.
did you try it with a just empty scene?

1 Answer

+1 vote
Best answer

Tested it with one of my scenes and it works as follows:

extends Node2D

onready var current_scene = $Cube

func set_scene(scene_path):
    # remove current scene
    current_scene.queue_free()
    print("FREED SCENE")
    # instance new scene
    var s = load(scene_path)
    current_scene = s.instance()
    print("INSTANCED SCENE")  
    call_deferred("_add_new")  

func _add_new():
    # add scene instance to tree  
    get_tree().get_root().add_child(current_scene)

func _ready():
    set_scene("res://scenes/Cube.tscn")

If this won't work , try this:

extends Node2D

onready var current_scene = $Cube

func set_scene(scene_path):
    # remove current scene
    current_scene.queue_free()
    print("FREED SCENE")
    yield(current_scene, "tree_exited")
    # instance new scene
    var s = load(scene_path)
    current_scene = s.instance()
    print("INSTANCED SCENE")  
    get_tree().get_root().add_child(current_scene) 

func _ready():
    set_scene("res://scenes/Cube.tscn")

Both should work. However, calldeferred logically only works if you wrap only addchild in it, because that's the part that has to wait for the old scene to be removed completely and the future references not to be "fighting" with old references.

by (840 points)
selected by
Welcome to Godot Engine Q&A, where you can ask questions and receive answers from other members of the community.

Please make sure to read Frequently asked questions and How to use this Q&A? before posting your first questions.
Social login is currently unavailable. If you've previously logged in with a Facebook or GitHub account, use the I forgot my password link in the login box to set a password for your account. If you still can't access your account, send an email to [email protected] with your username.