Search Unity

SerializeHelper - Free save and load utility. (De)Serialize all objects in your scene.

Discussion in 'Assets and Asset Store' started by Cherno, Jul 3, 2015.

  1. Cherno

    Cherno

    Joined:
    Apr 7, 2013
    Posts:
    515
    To answer your question:
    First, if no OI script is present on the prefab, one is added. This is done because theoretically, a prefab has none but one was added during runtime, so if it is reconstructed, it needs one so parent and child relations can be reconstructed, too.

    Then, avariable that holds a reference to the newly added, or already present, OI script is declared to make the following code a bit shorter and easier to read ("idscript"). The id and idParent variables from this are filled with the corresponding information from the loaded data from the sceneObject instance, which was in turn part of the save game file.


    Here is a quick fix for the parenting issue. Just replace the ObjectIdentifier.cs script with the code below. There was a logic error that caused the system to not properly look for parents and children. It works now, even with deep hierarchies. Note that all children and parent still need ObjectIdentifier scripts!

    Code (CSharp):
    1. //Add an ObjectIdentifier component to each Prefab that might possibly be serialized and deserialized.
    2. //The name variable is not used by the serialization; it is just there so you can name your prefabs any way you want,
    3. //while the "in-game" name can be something different
    4. //for example, an item that the play can inspect might have the prefab name "sword_01_a",
    5. //but the name (not the GameObject name; that is the prefab name! We are talking about the variable "name" here!) can be "Short Sword",
    6. //which is what the player will see when inspecting it in his inventory, a shop, et cetera.
    7. //To clarify again: A GameObject's (and thus, prefab's) name should be the same as prefabName, while the variable "name" in this script can be anything you want (or nothing at all).
    8.  
    9. using UnityEngine;
    10. using System.Collections;
    11. using System.Collections.Generic;
    12.  
    13. public class ObjectIdentifier : MonoBehaviour {
    14.    
    15.     public string name;
    16.     public string prefabName;
    17.  
    18.     public string id;
    19.     public string idParent;
    20.     public bool dontSave = false;
    21.  
    22.     public void SetID() {
    23.  
    24.         if(string.IsNullOrEmpty(id)) {
    25.             id = System.Guid.NewGuid().ToString();
    26.         }
    27.  
    28.         CheckForParent();
    29.     }
    30.    
    31.     private void CheckForParent() {
    32.        
    33.         if(transform.parent == null) {
    34.             idParent = null;
    35.         }
    36.         else {
    37.             ObjectIdentifier oi_parent = transform.parent.GetComponent<ObjectIdentifier>();
    38.             if(oi_parent != null) {
    39.                 if(string.IsNullOrEmpty(oi_parent.id)) {
    40.                     oi_parent.SetID();
    41.                 }
    42.                 idParent = oi_parent.id;
    43.             }
    44.         }
    45.     }
    46. }
    47.  
    48.  
     
  2. Evasion4D

    Evasion4D

    Joined:
    Aug 8, 2013
    Posts:
    16
    Perfect Cherno !

    Be careful that "public string name;" hides inherited member 'UnityEngine.Object.name'
    I've replace it by "sName"
    (In SceneObject.cs and SaveloadMenu.cs too)

    Thanks for your free asset and your time

    Jean Michel
     
  3. Cherno

    Cherno

    Joined:
    Apr 7, 2013
    Posts:
    515
    Good call. I never experienced any trouble with it, by I will change the variable name for the next update.

    I spent a whole evening recently to get the automatic GameObject referencing to work, which it does now, but I'm still working on supporting ALL Types that inherit from component (Renderers and such). I also don't know yet how to go about keeping track of unity-specific variables that are members of sub classes inside a Monobehavior script. Need mroe research and lots of trial & error :)
     
  4. Glurth

    Glurth

    Joined:
    Dec 29, 2014
    Posts:
    109
    This looks really useful Cherno! Many thanks for putting it out there!
    (Alas, I fear it may be a while before I'm ready to start actually using it, and give you any feedback.)
     
  5. Evasion4D

    Evasion4D

    Joined:
    Aug 8, 2013
    Posts:
    16
    Hi Cherno,
    I come to news
    Have you succeeded to save all types ?
    Can I help you in any way?
     
  6. Cherno

    Cherno

    Joined:
    Apr 7, 2013
    Posts:
    515
    Thank you for your interest. In fact I have spent the last several days working on it. I am at a point where I can save every variable of a component, and also save references to GameObjects or components, even if they are in sub-classes. The new system doesn't need ISerializationSurrogates anymore. Currently, I am working on supporting collections like arrays and lists. This will probably be done later today. So, you can expect a major revamp very soon, along with a complete manual for how everything works.
     
  7. Evasion4D

    Evasion4D

    Joined:
    Aug 8, 2013
    Posts:
    16
    Thank you, I look forward to find out
     
  8. davidovitch82

    davidovitch82

    Joined:
    Sep 8, 2015
    Posts:
    13
    Hi Cherno, great to read you are still improving the serializehelper! Although I dread the moment when I might update to your latest version, as I already have been storing a lot of GameObject references via string ID's and presumably will have to delete all that stuff (as you mentioned this is now included).

    I did run into an error that I'm stuck on right now, perhaps you can help shed some light.
    When I try to save the game, this error pops up: SerializationException: Type UnityEngine.Sprite is not marked as Serializable.

    Now I had this error before, and as I recall it was solved by moving all my sprite assets into the Resources folder of my project. This time I don't know what is causing it :(
    I do have various prefabs with Sprite variables in their scripts, but those are all set at the prefab level in the inspector (I don't change those sprite variables at runtime). And they never caused problems so far.

    The full error:
    Code (CSharp):
    1. SerializationException: Type UnityEngine.Sprite is not marked as Serializable.
    2. System.Runtime.Serialization.Formatters.Binary.BinaryCommon.CheckSerializable (System.Type type, ISurrogateSelector selector, StreamingContext context) (at /Users/builduser/buildslave/mono/build/mcs/class/corlib/System.Runtime.Serialization.Formatters.Binary/BinaryCommon.cs:119)
    3. System.Runtime.Serialization.Formatters.Binary.ObjectWriter.GetObjectData (System.Object obj, System.Runtime.Serialization.Formatters.Binary.TypeMetadata& metadata, System.Object& data) (at /Users/builduser/buildslave/mono/build/mcs/class/corlib/System.Runtime.Serialization.Formatters.Binary/ObjectWriter.cs:386)
    4. System.Runtime.Serialization.Formatters.Binary.ObjectWriter.WriteObject (System.IO.BinaryWriter writer, Int64 id, System.Object obj) (at /Users/builduser/buildslave/mono/build/mcs/class/corlib/System.Runtime.Serialization.Formatters.Binary/ObjectWriter.cs:306)
    5. System.Runtime.Serialization.Formatters.Binary.ObjectWriter.WriteObjectInstance (System.IO.BinaryWriter writer, System.Object obj, Boolean isValueObject) (at /Users/builduser/buildslave/mono/build/mcs/class/corlib/System.Runtime.Serialization.Formatters.Binary/ObjectWriter.cs:293)
    6. System.Runtime.Serialization.Formatters.Binary.ObjectWriter.WriteQueuedObjects (System.IO.BinaryWriter writer) (at /Users/builduser/buildslave/mono/build/mcs/class/corlib/System.Runtime.Serialization.Formatters.Binary/ObjectWriter.cs:271)
    7. System.Runtime.Serialization.Formatters.Binary.ObjectWriter.WriteObjectGraph (System.IO.BinaryWriter writer, System.Object obj, System.Runtime.Remoting.Messaging.Header[] headers) (at /Users/builduser/buildslave/mono/build/mcs/class/corlib/System.Runtime.Serialization.Formatters.Binary/ObjectWriter.cs:256)
    8. System.Runtime.Serialization.Formatters.Binary.BinaryFormatter.Serialize (System.IO.Stream serializationStream, System.Object graph, System.Runtime.Remoting.Messaging.Header[] headers) (at /Users/builduser/buildslave/mono/build/mcs/class/corlib/System.Runtime.Serialization.Formatters.Binary/BinaryFormatter.cs:232)
    9. System.Runtime.Serialization.Formatters.Binary.BinaryFormatter.Serialize (System.IO.Stream serializationStream, System.Object graph) (at /Users/builduser/buildslave/mono/build/mcs/class/corlib/System.Runtime.Serialization.Formatters.Binary/BinaryFormatter.cs:211)
    10. SaveLoad.Save (.SaveGame saveGame) (at Assets/Scripts/Serialization/SaveLoad.cs:33)
    11. SaveLoadMenu.SaveGame (System.String saveGameName) (at Assets/Scripts/Serialization/SaveLoadMenu.cs:171)
    12. SaveLoadMenu.OnGUI () (at Assets/Scripts/Serialization/SaveLoadMenu.cs:66)
    13.  
     
  9. Cherno

    Cherno

    Joined:
    Apr 7, 2013
    Posts:
    515
    Hello davidovitch82!

    I assume the problem can be explained like this:

    Sprite is a Unity-specific Type so it's indeed not serializable without using an ISerializationSurrogate. If you have written one (likely leaving both methods empty, as with the ISS for the GameObject Type), then a new issue can occur: If the sprite variable is not located in the "first level" of a component, but rather as part of a class deeper down the hierarchy, then even the ISS will be of no use because the BinaryFormatter only sees the top-most members that are given to it.

    Code (CSharp):
    1. public class MyScript : MonoBehavior {
    2.      public Sprite sprite;//ok if an ISS for Type Sprite is used!
    3. }
    4.  
    5.  
    Code (CSharp):
    1. public class MyClass {
    2.      public Sprite sprite;
    3. }
    4.  
    5. public class MyScript : MonoBehavior {
    6.      public MyClass myClass;//not ok because now the Sprite variable lies deeper into the script's member hierarchy!
    7. }
    I hope you can follow my explaination. This issue was always critical and one of the major reasons I improved the whole system.



    Speaking of which, I'm happy to report that after weeks of frustrating trial & error and getting nowehere trying to include arrays and lists, earlier this week I finally got the breakthrough, the proverbial Gordian Knot split, so to speak. Now, my script goes through the whole hierarchy of a class instance (say, a component) until it reaches a member (field or property) that is from the System namespace (int, string etc.). That way, serialization errors can't occur anymore (I bet I will have to get back to that statement at some point!). The user also has a a lot more options to customize what will and will not be saved and loaded. So far, as mentioned above, arrays and Generic Lists are supported when it comes to collections. I will probably also include Dictionaries because it's trivial to to so, and the documentation will include an explaination how a developer can add support for other collection types if it is needed.

    It's still not ready for release, though, it needs some polishing and minor features are waiting to be included, but it's very close. The utility will have a new name because only the basic functionality is shared with SerializeHelper while number and size of the included features has been expanded by a factor of 10 or more (rough estimate).
     
    Last edited: Sep 29, 2016
  10. davidovitch82

    davidovitch82

    Joined:
    Sep 8, 2015
    Posts:
    13
    I think I understand, and I was able to fix the problem with the sprites. However I realized I also use in several places arrays of GameObjects, accompanied by arrays of strings to store their ID. I thought I could restore those GameObject arrays by doing OnDeserialize:

    Code (CSharp):
    1.  
    2. ObjectIdentifier[]objectsIdentifiers=FindObjectsOfType(typeof(ObjectIdentifier))asObjectIdentifier[];
    3.  
    4. for(inti=0;i<TEMPHEROES.Length;i++){
    5.  if(string.IsNullOrEmpty(TEMPHEROIDS[i])==false){
    6.   Debug.Log("tempheroIDstringexists:"+TEMPHEROIDS[i]);
    7.   foreach(ObjectIdentifier objectIdentifier inobjectsIdentifiers){
    8.    if(string.IsNullOrEmpty(objectIdentifier.id)==false){
    9.     if(objectIdentifier.id==TEMPHEROIDS[i]){
    10.     Debug.Log("heroIDmatches:"+TEMPHEROES[i]);
    11.     TEMPHEROES[i]=objectIdentifier.gameObject;
    12.     break;
    13.     }
    14.    }
    15.   }
    16.  }
    17. }
    18.  
    19.  
    but the second debug.log line never shows up so the ID's are not matched. This is due to the same reason you mentioned (the GameObjects inside the array are one step too 'deep')?

    Will this work in the new version you are putting out? Or should I create my own work-around for now?

    Thanks again!
     
  11. Cherno

    Cherno

    Joined:
    Apr 7, 2013
    Posts:
    515
    It should work. If no serialization error is thrown then the data is saved correctly since simple strings or string[] arrays pose no problem. I suggest checking if the id strings are saved correctly in the first place, by inserting Debug.Log lines in your OnSerialize function, and then again in OnDeserialize so you know the strings are all there in the array.

    What I don't understand about the loop you posted:

    At first, you treat TEMPHEROES like a string[] when you check if one of it's elements is null or empty, and again when you check it's value gainst the current objectIdentifier id. But later, before the break line, you treat it like a GameObject[] when you assign the objectIdentifier.gameObject to the current TEMPHEROES element.
     
  12. davidovitch82

    davidovitch82

    Joined:
    Sep 8, 2015
    Posts:
    13
    I'm sure the ID's are saved: after serializing I can see in the inspector that all the GameObjects (my 'temporary' heroes in this example) have an ID in their ObjectIdentifier component, and the string[] TEMPHEROIDS contains those same strings to identify the hero gameobjects with.
    The only thing that's not happening for some reason is using those string ID's to repopulate the gameobject array called TEMPHEROES after deserialization.

    Also, my code might look bad but I think the snippet I posted is correct. TEMPHEROIDS is the array of strings and TEMPHEROES is the array of gameobjects. They both have the same length which I set in the inspector (not at runtime) so that's why the first line checks the length of the TEMPHEROES array. It could just as well check the TEMPHEROIDS, the length would be the same.

    Actually what I just now realized is that while serializing (and deserializing) the gameobjects in the TEMPHEROES array are disabled. I did update the ObjectIdentifier to also add disabled objects to the list of stuff to serialize - using the code below. Disabled objects do also get deserialized properly (including their disabled state) and reappear in my scene but I still suspect the problem is in this area somewhere.

    Code (CSharp):
    1.  
    2. public void SetDisabledIds (List<GameObject> GOList) {
    3.      //this is only run on the pub object and ensures all the disabled children are also serialized
    4.      ObjectIdentifier[] childrenIds = GetComponentsInChildren<ObjectIdentifier>(true);
    5.      foreach(ObjectIdentifier idScript in childrenIds) {
    6.        if(idScript.transform.gameObject != gameObject && idScript.transform.gameObject.activeSelf == false) {
    7.          idScript.SetID();
    8.          GOList.Add (idScript.gameObject);
    9.          Debug.Log (idScript.gameObject + "Added to GOList as DISABLED by: " + gameObject);
    10.  
    11.          idScript.SetEnabledIds (GOList);
    12.        }
    13.      }
    14. }
    15.  
    16. public void SetEnabledIds (List<GameObject> GoList) {
    17.      //Disabled object with objectIdentifier must be checked for their ENABLED children to also be serialized
    18.      ObjectIdentifier[] childrenIds = GetComponentsInChildren<ObjectIdentifier>();
    19.      foreach(ObjectIdentifier idScript in childrenIds) {
    20.        if(idScript.transform.gameObject != gameObject) {
    21.          idScript.SetID();
    22.          GoList.Add (idScript.gameObject);
    23.          Debug.Log (idScript.gameObject + "Added to GOList as child of a DISABLED by: " + gameObject);
    24.        }
    25.      }
    26. }
    27.  
    28.  
     
  13. Cherno

    Cherno

    Joined:
    Apr 7, 2013
    Posts:
    515
    Oh, I see now that one array is called TEMPHEROES and one TEMPHEROIDS. My bad.

    Why are the gameObejct in the array disabled in the first place, did you do this or why is it happening?
     
  14. davidovitch82

    davidovitch82

    Joined:
    Sep 8, 2015
    Posts:
    13
    That is on purpose. I am basically serializing one object (called 'pub') that has my entire game world in it, including the heroes and equipment and such stuff. I am using both SetDisabledIds and SetEnabledIds from the code in my previous post to make sure all objects (such as heroes) that may be disabled at the time are also serialized. I had to do some extra work to get the parent-child relations to appear properly in the hierarchy after deserialization but so far I've managed.

    I think maybe I got the problem:

    In OnDeserialize() I just use:
    "ObjectIdentifier[] objectsIdentifiers = FindObjectsOfType (typeof(ObjectIdentifier)) as ObjectIdentifier[];"
    which will not include the disabled objects. Let me see if I can fix that!
     
  15. Cherno

    Cherno

    Joined:
    Apr 7, 2013
    Posts:
    515
    Note that disabled GameObjects can't be found in any way. If no reference to them is kept, they can never be accessed again via code. Disabled components are no problem (as long as the GO they sit on is acitve), but a disabled GameObject doesn't exist as far as Unity is concerned, so the components on it don't exist either. This means that FindObjectsOfType won't find those disabled GOs.

    I only realized this fact a few weeks ago and after reading pretty much everything I could find about it while trying to find a workaround, I came to the conclusion that it's simply not possible. The only solution is to keep a reference (like, adding a GO to a collection before it is disabled, and removing it from the list after enabling it again), or not disable it completely but rather just disable relevant components (renderers, colliders, rigidBodies, scripts).
     
  16. davidovitch82

    davidovitch82

    Joined:
    Sep 8, 2015
    Posts:
    13
    Yep changing that line to

    ObjectIdentifier[] objectsIdentifiers = GetComponentsInChildren<ObjectIdentifier>(true);

    makes it work. This should be OK as long as the GameObjects referenced in the array are always children of the object being deserialized.

    The whole process is rather slow, perhaps due to my setup of serializing so many objects. I also use OnSerialize and OnDeserialize on a lot of objects, and the ObjectIdentifier array gets created each time.

    Just thinking out loud here:
    I should probably create the ObjectIdentifier array only once, (and then wait for a few frames to make sure everything is deserialized?) and then use a reference to it a new method that I call on each component that has GameObject variables that need to be restored.

    Either way I probably will have to drop the 'auto-save' feature I planned to include that would save the game for every step the player takes :p
     
  17. Cherno

    Cherno

    Joined:
    Apr 7, 2013
    Posts:
    515
    This system was never meant to be used as a classic quicksave feature which saves and restores the whole gameplay, including animations etc. This would be another huge task altogether. I think of it more as a checkpoint-based solution.

    If you have lots of DebugLog lines printing to the console, those cause a lot of lag as well. Using lots of GetComponent and indeed the use of FindObjectsOfType is also very resource-intensive, so re-use it as often as possible, maybe even go so far as to always keep it updated when something is created or destroyed.

    The new utlity has a complex and flexible solution that allows the developer to control which and how fields, properties and types are saved and loaded.
     
  18. davidovitch82

    davidovitch82

    Joined:
    Sep 8, 2015
    Posts:
    13
    Also ran into a new issue. Although my GOList now includes disabled objects, this code:

    Code (CSharp):
    1.  
    2. foreach(GameObject go ingoList){
    3.   go.SendMessage("OnSerialize",SendMessageOptions.DontRequireReceiver);
    4. }
    5.  
    doesn't actually call OnSerialize for the disabled objects. Should be easy to fix but it's bedtime for now.

    Thanks for help again!
     
  19. Nogs

    Nogs

    Joined:
    Jul 12, 2016
    Posts:
    2
    How's the rewrite coming along Cherno? New version maybe put up on asset store soon?
     
  20. Cherno

    Cherno

    Joined:
    Apr 7, 2013
    Posts:
    515
    Rewrite is coming along nicely. I put up a more detailed description of the planned features and progress a few posts further up.I will probably release the new version here and spend a few more weeks testing everything before it submit it to the Asset Store.
     
  21. Cherno

    Cherno

    Joined:
    Apr 7, 2013
    Posts:
    515
    The last several days were filled with lots of tinkering with the code, including some nightmarish episodes where everything seemed to go wrong :D But in the end, I persevered and now have included all the features I wanted. My original plan to get rid of ISerializationSurrogates couldn't be realized because even though I can theoretically save all fields of, say, a Vector3 directly, it won't help much as these kind of classes won't get set properly; when their xyz fields are assigned again with the loaded values, the Vecto3 value as a whole won't reflect that as long as the Vector3 field itself is not assigned. Pretty technical stuff but I just wanted to say that ISSs will still be needed, but I will include a large selection and of course it's very easy to write your own should you ned to.
    Arrays, Lists and Dictionaries are now included, and the collection support, especially the dictionaries, was extremely difficult but now it works, even with references to GameObjects and Components.
    Next step is add some minor functions that allow the user to save and load single GameObjects, Components, and even variables to give a high degree of control over what you want to save/load, and how. This will make it possible to, say, only save the inventory of a character without having to save him or her as a whole.
    After that, there will be a testing period where I will try to save & load all kinds of variable Type combinations and hopefully not too many bugs will pop up then.
     
  22. Dexmes

    Dexmes

    Joined:
    Oct 1, 2015
    Posts:
    3

    AWESOME! Love this way of saving and you are doing a great job! Hope to see it soon with the components and what now working!! :)
     
  23. Cherno

    Cherno

    Joined:
    Apr 7, 2013
    Posts:
    515
    Thanks. I will release it very soon. One thing I couldn't get to work were nested collections (List<List<int>>, for example).
    Testing is largely complete, now I ahve to write documentation.
     
  24. Dexmes

    Dexmes

    Joined:
    Oct 1, 2015
    Posts:
    3
    Very nice! Awesome can't wait to see it!
     
  25. Cherno

    Cherno

    Joined:
    Apr 7, 2013
    Posts:
    515
    Unity Save Load Utility, the successor to SerializeHelper, has been uploaded. An new thread has been created on the Unity forums:

    Unity Save Load Utility
     
  26. Ginxx009

    Ginxx009

    Joined:
    Sep 11, 2016
    Posts:
    89
    @Cherno sir im using your save/load system and in my project i have a Counter gameobject with a self script for counting everytime i click in the canvas and added your ObjectIdentifier script into the Counter gameobject my problem is the everytime i save my counter gameobject and load it it successfully load the last value of my counter but when i click again the canvas for adding it doesn't add anymore . Help me please
     
  27. astrobot7

    astrobot7

    Joined:
    Feb 1, 2017
    Posts:
    2
    Can I store material of objects? I tried the test scene and its not working when m trying to change a material.
     
  28. FernandoDarci

    FernandoDarci

    Joined:
    Jan 25, 2018
    Posts:
    19
    ISerializationSurrogates for a Unity project is a waste of time. Besides the scripts are in C#, and you understand that can do anything that you can do for other kinds of C# projects, must remember Unity and C# are ways to do things. Unity have the advantage of MonoBehaviour, where you can attach to multiple GameObjects. Simplier way to serialize an GameObject is make a MonoBehaviour with all info that you need to preserve for this GameObject, and a way to save and restore it in some way. After this, a separated MonoBehaviour simply storing an UnityEvent to save and other to Load each MonoBehaviour attached in a GameObject. Triggering the UnityEvent does the trick nicely.
     
  29. person90111

    person90111

    Joined:
    May 18, 2022
    Posts:
    20
    how do I make the buttons bigger?