David Dimalanta
David Dimalanta

Reputation: 548

Way to deserialized or load last save state scene with GameObject and other classes instead of storing normal fields via binary format?

I humbly ask for help in order to solve this problem. I successfully take a quick guide to learn more about save and load state of the game. Basically first, I know how to use PlayerPrefsto store basic string, int, and float.

Now, looking for a more effective way to store saved files via serialization. At first, after my first few researches online, I watch some video tutorial and only provides storing basic fields (int, string, bool, float, etc.) and saved in a created file. I attempt to try it on classes but didn't worked unless I marked as [Serializable].

Next, trying to save Gameobject created, prefabs or not, didn't work and it requires to serialize that class itself that is "GameObject". I took first attempt using this guide from StackOverflow with the accepted answer, I do understand and saving a GameObject or other custom classes require to store it and converted into .xml file.

Here's my two main problem need to resolve. the first one is that the runtime returned NullPointerException after I ensured all of the necessary objects are created as new. The error stopped at this line (ask for more code source if you need):

            DataContractSerializer ds = new DataContractSerializer (data2.GetType ()); // --> Serialize to .xml file.
            MemoryStream stream = new MemoryStream();

            ds.WriteObject (stream, data2); // --> The error stops here.
            stream.Seek (0, SeekOrigin.Begin);
            file.Write (stream.GetBuffer (), 0, stream.GetBuffer ().Length);
            file.Close();

As you can see, this is the part of the code where you can save and stored in a file created on a persistent file directory for the stored classes such as GameObject, List, and/or other custom classes.

The second problem will be tackled after the first problem is resolved. The second problem involves loading last state saved. For the normal fields such as integer or string, using BinaryFormatter and FileStream works well to load stored values form a file created last time. I tried that on stored custom classes such as GameObject but it required a different method like this one but it is a bit hard to understand how to translate it in Unity and still observe some ways to work it out, the best way to load stored classes from a file.

Here is the class I'm trying to deserialize a class that contains the following fields inside.

[DataContract]
public class TreeData2 {

    // - - - Spouse - - -
    [DataMember] private List<GameObject> _masters;

    public List<GameObject> masters {

        get { return _masters; }
        set { _masters = value; }

    }

    [DataMember] private List<GameObject> _targets;

    public List<GameObject> targets {

        get { return _targets; }
        set { _targets = value; }

    }

    [DataMember] private List<FamilyDatabase> _familyGroup;

    public List<FamilyDatabase> familyGroup {

        get { return _familyGroup; }
        set { _familyGroup = value; }

    }

    [DataMember] private GameObject _node;

    public GameObject node {

        get { return _node; }
        set { _node = value; }

    }

    [DataMember] private List<string> _mothers;

    public List<string> mothers {

        get { return _mothers; }
        set { _mothers = value; }

    }

    [DataMember] private List<string> _fathers;

    public List<string> fathers {

        get { return _fathers; }
        set { _fathers = value; }

    }

    [DataMember] private List<GenerationDatabase> _genDb;

    public List<GenerationDatabase> genDb {

        get { return _genDb; }
        set { _genDb = value; }

    }

    // - - - Root Action - - -
    [DataMember] private List<GameObject> _child;

    public List<GameObject> child {

        get { return _child; }
        set { _child = value; }

    }

    // Gen Database (Main)
    [DataMember] private List<string> _mothersDB;

    public List<string> mothersDB {

        get { return _mothersDB; }
        set { _mothersDB = value; }

    }

    [DataMember] private List<string> _fathersDB;

    public List<string> fathersDB {

        get { return _fathersDB; }
        set { _fathersDB = value; }

    }

    [DataMember] private List<GameObject> _mastersDB;

    public List<GameObject> mastersDB {

        get { return _mastersDB; }
        set { _mastersDB = value; }

    }

    [DataMember] private List<GameObject> _targetsDB;

    public List<GameObject> targetsDB {

        get { return _targetsDB; }
        set { _targetsDB = value; }

    }

    [DataMember] private List<string> _mothersT;

    public List<string> mothersT {

        get { return _mothersT; }
        set { _mothersT = value; }

    }

    [DataMember] private List<string> _fathersT;

    public List<string> fathersT {

        get { return _fathersT; }
        set { _fathersT = value; }

    }

    [DataMember] private List<GameObject> _mastersT;

    public List<GameObject> mastersT {

        get { return _mastersT; }
        set { _mastersT = value; }

    }

    [DataMember] private List<GameObject> _targetsT;

    public List<GameObject> targetsT {

        get { return _targetsT; }
        set { _targetsT = value; }

    }

}

data2 is the variable name of the TreeData2 class and yes I'm making a family tree like structure via Unity for the game that shows progress of unlocking and storing lists of the branches. Here's the recap with the mentioned variable name while serializing GameObject and List classes.

            FileStream file = File.Create (Application.persistentDataPath + "/check/treeData2.dat");
            TreeData2 data2 = new TreeData2();

            . . .

            DataContractSerializer ds = new DataContractSerializer (data2.GetType ());
            MemoryStream stream = new MemoryStream();

            ds.WriteObject (stream, data2); // --> Error stops here. Returns NullPointerException due to failed in parsing in .xml file in storing GameObject classes and List<T>.
            stream.Seek (0, SeekOrigin.Begin);
            file.Write (stream.GetBuffer (), 0, stream.GetBuffer ().Length);
            file.Close();

            string result = XElement.Parse(Encoding.ASCII.GetString(stream.GetBuffer()).Replace("\0", "")).ToString();

            print ("SAVE TREE COMPLETE");
            print ("Result: " + result);

Redirect from this original question from Game Development.

Upvotes: 0

Views: 692

Answers (2)

David Dimalanta
David Dimalanta

Reputation: 548

Unfortunately, after several experiments to find the easy way to store GameObjects effectively without hassle, there is no shortcut in preserving files + game objects during save and load state. I decided to give up for banging my head too hard to go crazy in making expectations on saving scene in one go. There are many steps to understand it.

Right now, storing info on each game object's specified sections/components require complex serializable classes, a meticulous way for sharing and retrieving each part of data. Explanation on this link and for more effective way in preserving data (but still not on game object) via formatting into JSON file.

It is a wise choice to make a better roadmap for tracking last state from scratch if you want to make your own save/load state for both common variables, serializable classes, and game object.

However, there is another way to save and load GameObject using this solution found on this Unity Q&A section. It discusses about "SerializerHelper" that lets you serialize not only serialized classes but also game objects, scenes and other non-serializable classes. Check this forum as well in order to understand how it works. You can try this Unity package to try out saving/loading GO here to download. (Requires to register Dropbox account if needed.)

Upvotes: 0

Farhan
Farhan

Reputation: 1268

Let me start with clearing a confusion I find so often. 'Serializable' will serialize a class for display in inspector only. You can say that it will serialize your class into a format that can be represented by inspector. Often, serialize means convert into byte arrays, it doesn't have to be a byte array, it can be any other format, e.g. strings.

Unity does not allow you to save your gameobjects directly, however, it doesn't stop you from recreating them to reflect some previous state. A gameobject may have so many components, e.g. MeshRenderer, Camera, etc, and it could get rough serializing all that down, imagine some big hierarchy!.

Generally, you want to separate your behaviors from data, i.e. your MonoBehavior from model/data (not 3d model), its a common OOP practice. You can come up with how you can save and retrieve some data from disk. I used Json.Net to serialize my data models into json. That gave me flexibility to save it locally as well as send it over network, but its just an example.

For an example, if I had to serialize a user's inventory, I would make a class like this:

[Serializable]
public class Inventory
{
    public List<InventoryItem> _items;
    // Some serializable and transient variables
}

[Serializable]
public class InventoryItem
{
    // Some serializable and transient variables
}

Then my Monobehaviour that would show inventory would look like this:

public class InventoryView : MonoBehaviour
{
    public Inventory _inventory; //Now the inventory items show in the inspector also, because it is serialized.

    void createViewFromInventory()
    { //... }
}

And now, anywhere I have reference to an Inventory object, I can serialize it however I wish to do so. With Json.Net, all I had to do looked something like this:

Inventory inv = getInventoryRef(); // get inventory here
string serializedInv = JsonConvert.Serialize(inv); // It converts it into json.
PlayerPrefs.Save("inventory", serializedInv); //

And in order to retrieve the saved inventory, I would do something like this:

void loadInventory()
{
    string invStr = PreferPrefs.GetString("inventory");
    Inventory inv = JsonConvert.Deserialize<Inventory>(invStr);
}

This way, monobehavior view classes need only ACT upon inventory state, but not save other components on the gameobject. Hope it helps.

Upvotes: 1

Related Questions