Scene.Free() doesn't seem to work as I'd expect

:information_source: Attention Topic was automatically imported from the old Question2Answer platform.
:bust_in_silhouette: Asked By KatteKwaad

I have built a scene switcher similar as what is shown in the documentation https://docs.godotengine.org/en/3.0/getting_started/step_by_step/singletons_autoload.html, but I am confused as to why my scene are not actually swapping out, but rather rendering op top of the old one, with both active processing and memory consumed.

I have attached a screenshot to show how I am swapping out my scenes:
Before scene switch:

After scene Switch: You can see the new scene is actually loaded behind the previous one. When I debug in the code, the weird thing is that the ‘CurrentScene’ object returns null after CurrentScene.Free(), so it does seem to free but I have no clue why it is still showing up.

Below is the code that I use to swap out the scenes, I follow pretty much the same code as with the guidance on the documentation, except that I am trying to keep my root folder, GameWorld, in memory whilst only attaching the new child to that instead. As you can see from the after picture, it overlaps.

public void DeferredGotoScene(string path)
    {
        if (CurrentScene != null)
        {
            //Get parent
            var parentScene = CurrentScene.GetParent();

            // It is now safe to remove the current scene
            CurrentScene.Free();

            // Load a new scene.
            var nextScene = (PackedScene)GD.Load(path);

            // Instance the new scene.
            CurrentScene = nextScene.Instance();

            // Add it to the active scene, as child of parent.
            parentScene.AddChild(CurrentScene);
            //GetTree().Root.AddChild(CurrentScene);

            // Optionally, to make it compatible with the SceneTree.change_scene() API.
            GetTree().CurrentScene = CurrentScene;            
        }
    }

Any advice would be welcome
PS: removing the GetTree().CurrentScene on line 22, doesn’t seem to affect anything.

:bust_in_silhouette: Reply From: Inces

I have suspicion, that Your scenes create new visual effects, that are not parented to these scenes. In your screens we can see overlapping tilemaps. Perhaps first scene is completely gone, but it instanced tilemap and added it as a child to high scope node/ to its parent ? Can You show code and structure of your level scenes ?

You might have a point with the structure. The way I do my Levels are as such:

and the level I wish to transition to:

Each of the doors in the levels have an body_shape_entered and exited signal so that I can know when to transition the player. (there is also an animation to open/close the door)

public void OnSpaceDoorTopExitBodyShapeEntered(RID objectEnteredRid, KinematicBody2D objectEntered, int bodyShapeIndex, int localShapeIndex)
    {
        if (objectEntered.Name == "Party")
        {
            //Entered the animation area. Animate door to open
            _spaceDoorSprite.Animation = "open";

            if (localShapeIndex == 1)
            {
                // //Transition
                var sceneManager = GetNode<SceneManager>("/root/GameWorld/SceneManager");
                sceneManager.SwitchScene(TransitionScene, DoorName);                
            }
        }
    }

Now in my SceneManager, there is a SwitchScene method, that does a little bit of a fade animation, with a signal to itself via an AnimationPlayer that will perform the actual switch when the animation complates:

    public void SwitchScene(string scenePath, string doorName)
    {
        TransitionDoorName = doorName;
        TransitionPath = scenePath;
        var animationPlayer = GetNode<AnimationPlayer>("AnimationPlayer");
        animationPlayer.Play("fade_to_black");
        GD.Print("Fading to black");
    }

    private void OnAnimationPlayerAnimationFinished(string animationName)
    {
        if (string.Equals(animationName, "fade_to_black"))
        {
            GD.Print("Faded to black");
            //EmitSignal(nameof(Transitioned), "fade_to_black");            
            var animationPlayer = GetNode<AnimationPlayer>("AnimationPlayer");
            animationPlayer.Play("fade_to_normal");
            //EmitSignal(nameof(Transitioned));
            //GD.Print("Signal transmitted");
            GotoScene(TransitionPath);

        }
        if (string.Equals(animationName, "fade_to_normal"))
        {
            GD.Print("Finished fading");
        }
    }

I’ve already posted the ‘GotoScene’ method above.

So these doors call into the SceneManager node, that sits under the GameWorld Node /root/GameWorld/SceneManager and when switched out, should change, for example, /root/GameWorld/BFZuluQuarters with /root/GameWorld/BFZuluWestHallway.
Each of the Levels have code in the _Ready function to determine where to spawn the player:

    public override void _Ready()
    {
        
        var sceneManager = GetNode<SceneManager>("/root/GameWorld/SceneManager");        
        if (!string.IsNullOrWhiteSpace(sceneManager.TransitionDoorName))
        {
            var playerNode = GetNode<Party>("Party");
            if (string.Equals(sceneManager.TransitionDoorName, "QuartersDoor"))
            {
                playerNode.Position = new Vector2(480,416);
            }
        }
    }

Could it be that these doors, which sit inside each levels, that call out to the Singleton/Autoloaded SceneManager, to replace its own parent level, that causes this effect? I would imagine if a child node calls out to a grandparent/higher up node to replace its own parent is not an issue.

KatteKwaad | 2022-04-02 16:20

I actually didn’t find what I expected there. Tilemaps are separate elements childed to each levels, so they should be freed correctly. Problem must lie elsewhere, perhaps something is called twice and more than one scene is instanced, while reference to original is lost so it can never be referred to be freed. It is especially propable since You are using high scope variable CurrentScene in goto method. This means, that if CurrentScene is changed few times in one frame, it will be changed in the middle of goto() function, creating new scene and hiding reference to itself behind new CurrentScene. I suspect it is something about collision with your doors - player triggers them twice, or triggers door of new scene in the same time he triggers door of old scene. Examine your remote tab ( during project run, above scene tree hierarchy new tab appears next to “local” tab ). It will help You see what exactly has been duplicated - just tilemap, or whole scene. Also, set up some prints in goto() to ensure how many times it is called per door.

Inces | 2022-04-03 07:46

Thanks so much for the troubleshooting. I managed to find what my issue is, and it was due to a misunderstanding of what I had on what a Singleton is in Godot. My GameWorld was added as a Singleton and I assumed that when it launches, the GameWorld scene I had under my root would be loaded once, but i was not! I therefore had the duplication all along, I just couldn’t see it as the first scene is perfectly rendered on top of the duplicate, and only noticeable when swapping scenes.

I refactored my project and now only have my Globals as a singleton. Thanks once again for the assist.

KatteKwaad | 2022-04-03 17:50