How to save scene within scene?

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

I read the tutorial SAVING GAME from Godot and at the end it didnt give explanation on how to save scene within a scene. Like I have a scene named Player and inside that scene I have another scene named Ship. First time I click save i get normal parent path:

“Parent”:“/root/SystemIce/Player”

And the game loads nice. But the second time I click save I get this:

“Parent”:“/root/SystemIce/@Player@5”

The code is same as the code in that tutorial…

a link to “that tutorial” would greatly help your cause

Wakatta | 2022-11-20 14:49

Sure THE GODOT’S ONLY SAVE GAME TUTORIAL

GoldSpark | 2022-11-20 14:51

:bust_in_silhouette: Reply From: Wakatta

That tutorial seems pretty straight forward all nodes you want saved, add it to a Persist group.

So open all scenes you want saved and add them to that group

Then when you call save_game() all nodes in that group in the sceneTree get their data saved

The Parent":"/root/SystemIce/@Player@5 suggests that you are cloning objects as that naming scheme is used when a node is added to the tree while another node with the same name is already present

Yeah I already did that and its not working. What I did, and its working, is each of the parent nodes that require saves I added that code for each of them and its working good now. I need to meddle more into this save game stuff but at least this one is working for now. Its not the best but at least something is being done.

Thank you!

GoldSpark | 2022-11-20 15:23

Whoa there no no no no no !!!

Will have to stop you before you develop a bad programming habit.
You must at all cost and can’t stress this enough, avoid redundancy as it makes your code hard to repair / understand later on and it wastes CPU cycles.

So gonna walk you through exactly how this code works

Lets say your scene tree looks like this

Root  
┖╴Main (World.gd)
    ┠╴HUD
    ┃  ┖╴Camera2D *
    ┠╴Player      *
    ┃  ┖╴Ship     *
    ┖╴Enemy       *

All nodes with the * are part of the Persist group
Groups

Only the Main node has a scrip that looks like this

# World.gd

func _ready():
    load_game()

func _exit_tree():
    save_game()

func save_game():
    # all save game function data

func load_game():
    all load game function data

In the save / load blocks

# This line gets all nodes within the persist group
# In the above example that will be var save_nodes = [Camera2D, Player, Ship, Enemy]
var save_nodes = get_tree().get_nodes_in_group("Persist")
# This line loops through each node in save_nodes
# assigning each value to (`i`)
for i in save_nodes:

    # so i = Camera2D
    # then i = Player
    # then i = Ship
    # then i = Enemy

    # then you can modify each node (`i`) as needed for example
    i.queue_free()    #deletes the node

Notes

You can assign the the save_game() and load_game() function to button presses instead

Wakatta | 2022-11-20 16:13

Hey thank you. Yes I did that and it crashes the second time I save the game. I have NPC Ships which have different values and inventory and player. The player parent path keeps showing like @Player@5 and I have no idea how to fix that. I know you’ve said that its because Player node is already in the scene but that player node is being deleted every time in that load function at first so I don’t understand how its getting duplicated.

Thank you you are right about the habit but I can’t see other way around this its insanely complicated at least for me for now.

What I will do I will make only one script which saves all these nodes in separate files. Its only 3 nodes which requires this.

So here is my player load and save:

 public void SaveScene()
    {
        File saveGame = new File();
        saveGame.Open("user://player.dat", File.ModeFlags.Write);


        Godot.Collections.Array saveNodes = GetTree().GetNodesInGroup("Persist");
        foreach (Node saveNode in saveNodes)
        {
            if (IsAParentOf(saveNode))
            {
                //Check the node is an instanced scene so it can be instanceds again during load
                if (saveNode.Filename.Empty())
                {
                    GD.Print(String.Format("persistent node '{0}' is not an instanced scene, skipping", saveNode.Name));
                    continue;
                }

                if (!saveNode.HasMethod("Save"))
                {
                    GD.Print(String.Format("persistent node '{0}' is missing a Save() function, skipping", saveNode.Name));
                    continue;
                }

                //Call the nodes save func
                var nodeData = saveNode.Call("Save");

                //Store the save dictionary as a new line in the save file
                saveGame.StoreLine(JSON.Print(Save()));
                saveGame.StoreLine(JSON.Print(nodeData));
                
            }
        }

        saveGame.Close();
    }


    public void LoadScene()
    {
        var saveGame = new File();

        if (!saveGame.FileExists("user://player.dat"))
            return;

        var saveNodes = GetTree().GetNodesInGroup("Persist");
        foreach (Node saveNode in saveNodes)
        {
            if (IsAParentOf(saveNode))
            {
                saveNode.Free();
            }
        }

        //Load the file line by line and process that dictionary to restore the object
        //it represents
        saveGame.Open("user://player.dat", File.ModeFlags.Read);
        bool firstLine = true;
        while (saveGame.GetPosition() < saveGame.GetLen())
        {
            //get the saved dictionary from the next line in the save file
            var nodeData = new Godot.Collections.Dictionary<string, object>(
                    (Godot.Collections.Dictionary)JSON.Parse(saveGame.GetLine()).Result
                    );

            if (!firstLine)
            {

                //Firstly, we need to create the object and add it to the tree and sets its position
                var newObjectScene = (PackedScene)ResourceLoader.Load(nodeData["Filename"].ToString());
                Ship newObject = newObjectScene.Instance<Ship>();
                newObject.Set("global_position", new Vector2((float)nodeData["PosX"], (float)nodeData["PosY"]));

                foreach (KeyValuePair<string, object> entry in nodeData)
                {
                    string key = entry.Key.ToString();
                    if (key == "Filename" || key == "Parent" || key == "PosX" || key == "PosY")
                        continue;


                    newObject.Set(key, entry.Value);
                }
                AddChild(newObject);


                //Now set the remaining variables


                //Set up weapons
                {
                    newObject.LoadGameShipWeapons();
                }
            }
            else
            {
                foreach (KeyValuePair<string, object> entry in nodeData)
                {
                    string key = entry.Key.ToString();
                    if (key == "Filename")
                        continue;

                    string v = "" + entry.Value;
                    Set(key, entry.Value);
                    if(key == "credit")
                    {
                        credit = Int32.Parse(v);
                    }
                }
                firstLine = false;
            }

        }
        
        saveGame.Close();
    }

and here is my normal npc ships load and save:

 public void SaveScene()
{
    File saveGame = new File();
    saveGame.Open("user://npcships.dat", File.ModeFlags.Write);


    Godot.Collections.Array saveNodes = GetTree().GetNodesInGroup("Persist");
    foreach (Node saveNode in saveNodes)
    {
        if (IsAParentOf(saveNode))
        {
            //Check the node is an instanced scene so it can be instanceds again during load
            if (saveNode.Filename.Empty())
            {
                GD.Print(String.Format("persistent node '{0}' is not an instanced scene, skipping", saveNode.Name));
                continue;
            }

            if (!saveNode.HasMethod("Save"))
            {
                GD.Print(String.Format("persistent node '{0}' is missing a Save() function, skipping", saveNode.Name));
                continue;
            }

            //Call the nodes save func
            var nodeData = saveNode.Call("Save");

            //Store the save dictionary as a new line in the save file
            saveGame.StoreLine(JSON.Print(nodeData));
        }
    }

    saveGame.Close();
}


public void LoadScene()
{
    var saveGame = new File();

    if (!saveGame.FileExists("user://npcships.dat"))
        return;

    var saveNodes = GetTree().GetNodesInGroup("Persist");
    foreach (Node saveNode in saveNodes)
    {
        if (IsAParentOf(saveNode))
        {
            saveNode.QueueFree();
        }
    }

    //Load the file line by line and process that dictionary to restore the object
    //it represents
    saveGame.Open("user://npcships.dat", File.ModeFlags.Read);

    while (saveGame.GetPosition() < saveGame.GetLen())
    {
        //get the saved dictionary from the next line in the save file
        var nodeData = new Godot.Collections.Dictionary<string, object>(
                (Godot.Collections.Dictionary)JSON.Parse(saveGame.GetLine()).Result
                );



        //Firstly, we need to create the object and add it to the tree and sets its position
        var newObjectScene = (PackedScene)ResourceLoader.Load(nodeData["Filename"].ToString());
        Ship newObject = newObjectScene.Instance<Ship>();
        //Now set the remaining variables
        foreach (KeyValuePair<string, object> entry in nodeData)
        {
            string key = entry.Key.ToString();
            if (key == "Filename" || key == "Parent" || key == "PosX" || key == "PosY")
                continue;
            newObject.Set(key, entry.Value);
        }
        AddChild(newObject);
        newObject.Set("global_position", new Vector2((float)nodeData["PosX"], (float)nodeData["PosY"]));


        

        //Set up weapons
        {
            newObject.LoadGameShipWeapons();
        }

    }

    saveGame.Close();
}

They are different and I don’t know how to make all of this in one function…

I also made sure the game auto saves every 10 minutes and when the user exists so it does not waste cycles so much.

Also the ship sometimes that the player is using loads before the player and it crashes the game. Its so complicated I just think that having three separate functions is good enough.

Then what happens is some nodes require those nodes and those some nodes are or arentloaded first and it crashes ah … my brain is completely gone , if I even had one…

GoldSpark | 2022-11-20 16:25

All I do is call this one function `time += delta;

    if (time > increase)
    {
        Godot.Collections.Array saveNodes = GetTree().GetNodesInGroup("Saveable");

        foreach(Node node in saveNodes)
        {
            node.Call("SaveScene");
        }

        GD.Print("Game Saved");
        increase = 8 * 60;
        time = 0f;
    }

`

and in turn it calls the nodes that has those children and its all well…

GoldSpark | 2022-11-20 16:53