Saving a map state

Discussion and help for Easy Save 3
Post Reply
Emolk
Posts: 10
Joined: Tue Nov 19, 2019 8:35 am

Saving a map state

Post by Emolk »

I have a scene and a gameobject called 'Map', which contains all the gameobjects that generate my games map. This map is similar to the one in slay the spire.

The map:

Image

The map is generated and players can move up the map by clicking on various nodes, where they are then loaded into a new scene based on the node.

Basically the way my map is generated is through this process:

1. Generate the maximum nodes
2. Cull nodes based on chance
3. Generate paths between nodes based on chance
4. Ensure there are always a path upwards on each column
5. Change node types

This is all done on the 'Map.cs' script. Each node contains two list of gameObjects, forwardNodes and backwardNodes.

My question is how can i effectively save the map state, so that if my game was closed and the Player opened up their profile and resumed gameplay, the map would be the same as they left it?

I have tried saving the map gameobject (which contains all the nodes, paths etc as children) by doing:

Code: Select all

ES3.Save<GameObject>("map", map);
and then loading the map by:

Code: Select all

ES3.Load<GameObject>("map");
This works if the game is on the same scene. However when changing scenes and then loading the map, only the map object is loaded with no children attached. Also the sprite renderer has a missing sprite and the map.cs script is no longer attached to the map gameObject.

Thanks.

Map object showing its children 'nodes':

Image

Map object showing its generated lines to represent the paths between nodes:

Image
User avatar
Joel
Moodkie Staff
Posts: 4826
Joined: Wed Nov 07, 2012 10:32 pm

Re: Saving a map state

Post by Joel »

Hi there,

It is likely unable to load in a different scene because the references will only exist in the scene it was saved.

One thing you can try is to copy the ES3ReferenceMgr Component of the Easy Save 3 Manager from the scene you're saving in to the scene you're loading in. You should also check that you have Save Child GameObjects selected in Window > Easy Save 3 > Settings.

If this doesn't manage to resolve the issue (i.e. the dependent references only exist in the original scene) then instead of saving the GameObjects you will need to save the Components required to recreate the Map rather than the entire GameObjects. There's an example of doing this here: https://moodkie.com/forum/viewtopic.php?f=16&t=1438

All the best,
Joel
Joel @ Moodkie Interactive
Purchase Easy Save | Contact | Guides | Docs | Getting started
Emolk
Posts: 10
Joined: Tue Nov 19, 2019 8:35 am

Re: Saving a map state

Post by Emolk »

How can i save the references? The prefabs are instantiated at runtime?

I have managed to save and load most things. However the following still isn't working:

1. The nodes i save and then load up are correct, however they are completely missing their script component altogether? i save the nodes like this"

ES3.Load<List<GameObject>>("nodes");

2. When i save and load my lines (paths between the nodes), the line renderer component is missing?

ES3.Save<List<GameObject>>("lines", lines);

3. The particle component of the nodes (child object) is missing its particle sprite


With the 'Saving and Loading Prefabs Instantiated at Runtime', how do i make this work? Do i just slap the script onto the scene?
User avatar
Joel
Moodkie Staff
Posts: 4826
Joined: Wed Nov 07, 2012 10:32 pm

Re: Saving a map state

Post by Joel »

Hi there,

Just to check, have you followed the instructions in the Saving and Loading GameObjects and Prefabs guide? Particularly the Saving and loading prefab instances section.

With regards to the example I sent, this is only intended as an example. I recommend reading the comments in the code to see what is happening in the script, which should give you an idea of how to do something similar in your project.

Essentially, to save references when references cannot be automatically inferred, you will need to find a way of uniquely identifying each Node, and save that unique identifier instead of saving a reference to the GameObject. The easiest way to do this would be to simply add a Component to the Node GameObjects which generates a unique ID. I.e.
using UnityEngine;

public UniqueID : MonoBehaviour
{
    public string id = System.Guid.NewGuid().ToString();
}
So now let's assume we've saved our nodes. To save our forward- and backwardNodes we simply need to convert them to a List of these unique IDs. E.g.
var forwardIDs = new List<string>();
// Add the ID of each forwardNode to a List.
foreach(GameObject node in forwardNodes)
    forwardIDs.Add(node.GetComponent<UniqueID>().id);
ES3.Save<List<string>>("forwardIDs", forwardIDs);
And then to load them back (assuming we've already loaded our nodes), we simply load the List of unique IDs, and then find the GameObject with the given ID and use that as a reference:
Now each Node will have a unique ID which we can use to reference it. We can get a List of all of these unique IDs using:

[code2=csharp]// Get an array of all of our UniqueIDs which we can search.
var nodeIDs = Object.FindObjectOfType<UniqueID>();
// Load our unique IDs
var forwardIDs = ES3.Load<List<string>>("forwardIDs");
// Clear our forwardNodes List ready to load the new data into.
forwardNodes.Clear();
// Search for the UniqueID which matches each forwardNode ID and add it's GameObject to the forwardNodes List.
foreach(var forwardID in forwardIDs)
    foreach(var nodeID in nodeIDs)
        if(nodeID.id == forwardID)
             forwardNodes.Add( nodeID.gameObject );
All the best,
Joel
Joel @ Moodkie Interactive
Purchase Easy Save | Contact | Guides | Docs | Getting started
Emolk
Posts: 10
Joined: Tue Nov 19, 2019 8:35 am

Re: Saving a map state

Post by Emolk »

Joel wrote:Hi there,

Just to check, have you followed the instructions in the Saving and Loading GameObjects and Prefabs guide? Particularly the Saving and loading prefab instances section.

With regards to the example I sent, this is only intended as an example. I recommend reading the comments in the code to see what is happening in the script, which should give you an idea of how to do something similar in your project.

Essentially, to save references when references cannot be automatically inferred, you will need to find a way of uniquely identifying each Node, and save that unique identifier instead of saving a reference to the GameObject. The easiest way to do this would be to simply add a Component to the Node GameObjects which generates a unique ID. I.e.
using UnityEngine;

public UniqueID : MonoBehaviour
{
    public string id = System.Guid.NewGuid().ToString();
}
So now let's assume we've saved our nodes. To save our forward- and backwardNodes we simply need to convert them to a List of these unique IDs. E.g.
var forwardIDs = new List<string>();
// Add the ID of each forwardNode to a List.
foreach(GameObject node in forwardNodes)
    forwardIDs.Add(node.GetComponent<UniqueID>().id);
ES3.Save<List<string>>("forwardIDs", forwardIDs);
And then to load them back (assuming we've already loaded our nodes), we simply load the List of unique IDs, and then find the GameObject with the given ID and use that as a reference:
Now each Node will have a unique ID which we can use to reference it. We can get a List of all of these unique IDs using:

[code2=csharp]// Get an array of all of our UniqueIDs which we can search.
var nodeIDs = Object.FindObjectOfType<UniqueID>();
// Load our unique IDs
var forwardIDs = ES3.Load<List<string>>("forwardIDs");
// Clear our forwardNodes List ready to load the new data into.
forwardNodes.Clear();
// Search for the UniqueID which matches each forwardNode ID and add it's GameObject to the forwardNodes List.
foreach(var forwardID in forwardIDs)
    foreach(var nodeID in nodeIDs)
        if(nodeID.id == forwardID)
             forwardNodes.Add( nodeID.gameObject );
All the best,
Joel

Sorry am i missing something? I can't seem to save any List<type> properly?

I have created a node class to bypass the prefab problem.

When i save the list of nodes of type node:

Code: Select all

ES3.Save<List<Node>>("nodesN", nodesN);
But when i try to load and read them:

Code: Select all

var nodesTest = ES3.Load<List<Node>>("nodesN");

        foreach (var node in nodesTest)
        {
            print(node.ID);
        }
I get an object reference error? This is in the same scene and same script as well.

When i read from the actual nodesN list its fine, but when i try to load the list into a variable and read them i get the error.
User avatar
Joel
Moodkie Staff
Posts: 4826
Joined: Wed Nov 07, 2012 10:32 pm

Re: Saving a map state

Post by Joel »

Hi there,

Would you be able to show me the error your are getting with stack trace, and also show me what your Node class looks like? And please could you PM me a very basic project which replicates it, along with instructions?

All the best,
Joel
Joel @ Moodkie Interactive
Purchase Easy Save | Contact | Guides | Docs | Getting started
Emolk
Posts: 10
Joined: Tue Nov 19, 2019 8:35 am

Re: Saving a map state

Post by Emolk »

Joel wrote:Hi there,

Would you be able to show me the error your are getting with stack trace, and also show me what your Node class looks like? And please could you PM me a very basic project which replicates it, along with instructions?

All the best,
Joel
Sent you a PM, thanks.
User avatar
Joel
Moodkie Staff
Posts: 4826
Joined: Wed Nov 07, 2012 10:32 pm

Re: Saving a map state

Post by Joel »

Hi there,

Thanks for sending that over. The error is occurring because you are trying to create your Node Components using new rather than using AddComponent, which is not allowed in Unity (see the warnings which are appearing in console in your project mentioning that this is not allowed).

When you create a Component using new, it leaves the Component in an unstable state and Unity evaluates it to NULL, so it will always be serialised as NULL.

All the best,
Joel
Joel @ Moodkie Interactive
Purchase Easy Save | Contact | Guides | Docs | Getting started
Emolk
Posts: 10
Joined: Tue Nov 19, 2019 8:35 am

Re: Saving a map state

Post by Emolk »

Hi Joel, im a little confused. When im creating a new node with new<Node> its creating an instance of a class, not a component, no?Since Node is a class in the cs file MapNode.cs

If i cant do this, how else would i save the nodes for later reconstruction?

Im also not getting any of the warnings you are describing, only the object reference error.

EDIT: Ok i figured out the new stuff. I still don't understand the best way to save the nodes though. Do i have to create a list for each attribute in the Node class and add all the values of all the nodes in, then save and load those lists? I figure there is an easier way to do this but just can't figure it out.

So i for example create a list of ints 'ids' for the node IDs, then save and load that list and use that, in conjunction with the other lists to recreate the node? Surely there is a more efficient way to do this, no?

As an example:

Node class;

public int ID;
public Vector2 pos;
bool selectable = false;

I create a list for each attribute and store those lists. Then i can load those lists later to reconstruct each node?
User avatar
Joel
Moodkie Staff
Posts: 4826
Joined: Wed Nov 07, 2012 10:32 pm

Re: Saving a map state

Post by Joel »

Hi there,

Sorry I'm not sure I understand. Is ES3.Save<List<Node>> still isn't working even after using AddComponent rather than 'new'? Would you be able to describe the issues you're having currently?

It might be helpful to give an example of how loading Components works. When Easy Save loads a Component, it will check the reference manager for a Component with that ID. In your case there will not be a Component with that ID in the Manager because you're creating your Components at runtime, and there's no way for the reference manager to know that it exists (the case might be that they don't exist at this point).

When you use ES3.Load<List<MyComponent>> and it cannot find a reference for a Component in the List, it will create a new instance of that Component. However, a Component cannot exist without a GameObject to attach it to, so it will also create a new GameObject to attach it to.

But there's one thing we can do here: use ES3.LoadInto instead. We provide ES3.LoadInto<List<Component>> with a List of Components, and it will load the data directly into each of the Components in this List. I'm not sure what issues you're currently having, but ES3.LoadInto might be what you're after?

All the best,
Joel
Joel @ Moodkie Interactive
Purchase Easy Save | Contact | Guides | Docs | Getting started
Post Reply