Search Unity

Saving and loading multiple objects

Discussion in 'Scripting' started by LeRan, Jan 7, 2016.

  1. LeRan

    LeRan

    Joined:
    Nov 24, 2015
    Posts:
    118
    Hello forum,

    I am trying to make a 2D-RPGish game, with some interactive map features (resources to pick up etc.). That means that each map contains several objects, whose sprites (empty/full etc.) and eventually position or other properties vary according to player actions.

    For the save/load function, I put all the map-resource gameObjects inside a container object, and I was trying to simply serialize that container object and write it to file, in the hope that loading said game object would load every child object with their right sprites and properties. Unfortunately, Unity keeps telling me: "SerializationException: Type UnityEngine.GameObject is not marked as Serializable." (the class that contains them is, though, and everything works fine again as soon as I remove the gameObjects, to leave only strings or integers) (but the problem reappears even if I put in this class mere empty gameObjects from script)

    I read that there exists some scripts to serialize gameObjects including all their children (like the one Whydoidoit created), but wouldn't it be overkill? Am I missing something simple to save and load my game properly?
     
  2. Cherno

    Cherno

    Joined:
    Apr 7, 2013
    Posts:
    515
    The Type GameObject is not serializable, and nor are the other Unity-specific Types like Mesh and Transform.
    That means you have to find a workaround. For example, you could give each GameObject a script which holds and generated a random unique ID, and save that instead of the GameObject itself. When loading, you go through all the GameObject in the scene until you find the one with the right ID, and reassign it.

    Be aware that this becomes very complex very quickly, and you WILL have to to a large amount of trial and error as well as reading up on serializing.

    I can point you to my own free asset, called SerializeHelper, which aims to privide a basic understanding and functionality for saving and loading data.

    http://forum.unity3d.com/threads/se...e-serialize-all-objects-in-your-scene.338148/
     
  3. LeRan

    LeRan

    Joined:
    Nov 24, 2015
    Posts:
    118
    Thanks Cherno, your solution is very intersting, and I will look into it as soon as I can gather enough peace of mind to do some proper coding.

    By the way, in the meantime I found the Unity Serializer NG for Unity 5 (https://github.com/TheSniperFan/unityserializer-ng). Do you think it's something worth learning for my needs? Coding time is my limiting factor here, and I try not to overkill anything...
     
  4. Cherno

    Cherno

    Joined:
    Apr 7, 2013
    Posts:
    515
    If it works for you sure... I think I might have tried it out myself at one point. The reason I started my own serializer was because US eventually threw errors or just didn't work right when the scene got more complex, and since it's extremely complex, I couldn't figure out how to make it work, so I learned it myself from the ground up and made a simpler version while still taking inspiration from it for the parts that I COULD understand.
     
  5. LeRan

    LeRan

    Joined:
    Nov 24, 2015
    Posts:
    118
    Thanks, I totally see your point :)
     
  6. RylanG

    RylanG

    Joined:
    Jan 6, 2016
    Posts:
    3
    I am assuming you have only looked at binary serialization. If so, that is limited to system fields such as integers and strings, but I believe XML can store generics, lists, and similar. This would mean you could store gameobject information as a generic then cast it as you load the file so Unity knows what it has. I haven't tried it myself, but I do believe it would work. Might want to read up on XML serialization to see if it's a better alternative.

    If you want to stick with binary serialization, it will be complicated no matter how you do it. Serialized files can store integers and floats, which means you can store a list of vectors for your objects within the save file (as float arrays). Then you simply need a way for the loading script to know which objects go where. An ID system or similar would be fine for that purpose, or store different types of objects in different matrices. Basic Ex:

    Code (CSharp):
    1.  
    2. using System;
    3. using System.Runtime.Serialization.Formatters.Binary;
    4.  
    5. [Serializable]
    6. public class SaveFile
    7. {
    8.     public int planets; //Number of stars
    9.     public int stars; //Number of planets
    10.     public float[] planetLocations; //Matrix for vector locations of planets
    11.     public float[] starLocations; //Matrix for vector locations of stars
    12. }
    Somewhere else in code as you create the save file for use:

    Code (CSharp):
    1. SaveFile save = new SaveFile();
    2. planetLocations = new float[3, x]; //Where x equals the number of planets
    3. starLocations = new float[3, y]; //Where y equals the number of stars
    I'm assuming that the numbers of these things would vary based on your scene. This is a super simple example, but should give you the idea. It stores all the basic position information, and you can simply separate the object types into different arrays. There are tons of ways to do saves, this is just one way, and it really depends on how much information you need to pass in.
     
  7. LeRan

    LeRan

    Joined:
    Nov 24, 2015
    Posts:
    118
    Rylan, you're officially my new hero. I was tearing my hair out trying to figure out why I couldn't even serialize scriptable objects, but I was not aware that it was due to my using binary formatters! I tried with XML serializer and now it works like a charm. Well, not with gameObjects, but at least it does with scriptable objects, which should be sufficient for what I'm trying to do.

    Meanwhile, I learnt that it's evil to store data in game objects, and that it is preferable to use a separate list of classes and scriptable objects for that purpose, and it's true because I read it on the internet :
    http://www.archmagerises.com/news/2015/9/22/tips-on-game-world-state-data-serialization-in-unity-c
    (plus, it really seems to make sense)

    Next step will be to find a way to make the savegame less easyly readable for the end user - but for now I'm very content that I can check it myself. Thanks!
     
  8. LeRan

    LeRan

    Joined:
    Nov 24, 2015
    Posts:
    118
    Hmm, unforeseen difficulty: when trying to load scriptable objects from my save file, it keeps telling me : "InventairePerso must be instantiated using the ScriptableObject.CreateInstance method instead of new InventairePerso ."

    My code is pretty straightforward : here the load function :
    Code (CSharp):
    1. public static void Charge_Jeu() {
    2.       if(File.Exists(cheminDonnéesSauvegarde+"jeuxSauvés.sav")) {
    3.             XmlSerializer leSerialXML = new XmlSerializer(typeof(Jeu));
    4.             FileStream file = File.Open(cheminDonnéesSauvegarde+"jeuxSauvés.sav", FileMode.Open);
    5.             Jeu.JeuCourant=(Jeu)leSerialXML.Deserialize(file);
    6.             file.Close();
    7.       }
    8. }
    And here the "game" class, with a "current game" static instance
    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3.  
    4. [System.Serializable]
    5. public class InventairePerso : ScriptableObject
    6. {
    7.     public int or;
    8.     public string test;
    9.     //that's just a bogus personal inventory to test the code
    10. }
    11.  
    12. [System.Serializable]
    13. public class Jeu
    14. {
    15.     public static Jeu JeuCourant = new Jeu();
    16.     public string nomDuJeu;
    17.     public InventairePerso inventairePerso = ScriptableObject.CreateInstance<InventairePerso>();
    18.  
    19.     public Jeu() {
    20.         inventairePerso.name = "case inventaire défaut";
    21.         inventairePerso.or = 0;
    22.         inventairePerso.test = "inventaire vide";
    23.         nomDuJeu = "nom du jeu vide";
    24.     }
    25. }

    I have no idea what I'm doing wrong? Whether I instantiate the scriptable object in the class declaration of Jeu like above, or in the constructor, provides the same result.
     
  9. LeRan

    LeRan

    Joined:
    Nov 24, 2015
    Posts:
    118
    Note that despite that warning I get, everything seems to work smoothly, my fields and objects seem to be loaded correctly. Should I ignore that warning ? That sounds somewhat dangerous...