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:
    513
    [UPDATE]

    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

    [/UPDATE]

    SerializeHelper Download Link


    So, saving & loading player progress is something that most games need in some form or another, and often, the PlayerPrefs way of doing it is not sufficient. The late and great UnitySerializer is no longer supported and was always something of a monster in terms of complexity, so I made my own system for saving and loading objects in the scene.

    So, what is it, and what can it do?

    Currently, it is a small collection of simple scripts and classes that allow you to save and load GameObjects by serializeing and writing them to a savegame file via BinaryFormatter. So far, only a GO's transform, active state, name, and any MonoBehavior scripts on it are saved, along with their variable values, of course. That means that you will have to create your own workarounds for saving and loading things like Renderers and other components. I did not include them because their importance varies from project from project.

    How do you use it?
    Simple: Open the provided sample scene, and either hit Escape or F5 and F9 on your keyboard for the menu or QuickLoad and QuickSave, respectively. Note that a save game file and possible directory structure will be created on your hard disk. Take a look at the "SaveLoadMenu" script to see where files are saved.

    How it works, in simple words:

    At the core lies the SaveGame class. an instance of this class is created when we want to save. This class holds a list of instances of the class SceneObject.
    SceneObject in turn represents a GameObject in a form that can be serialized. It holds a GO's name, id, parent's id (if any), Transform values (rot/pos/scale), active state, and a list of instances of the class ObjectComponent.
    ObjectComponent represents one MonoBehavior (currently) component ("script"). It holds the component's name as a string, and a Dictionary<string,object> "fields". This dictionary holds a field's name as key and a field's value as value, stored as an object.

    Each GameObject that should be able to be saved and loaded needs two things:
    1. A component "ObjectIdentifier"
    2. A Prefab of it somewhere in the Resources folder, which of course also needs an ObjectIdentifier component.

    ObjectIdentifier is a MonoBehavior component that holds a GameObject's prefab name and id and parent's id values.

    If we want to save the GameObject in our scene, the following happens:
    (0.) At runtime start, the prefabDictionary is filled. This collection holds references to all GameObjects in the Resources folder which have an ObjectIdentifier component. This dictionary is later used to reconstruct a GameObject.
    1. A new instance of SaveGame is created
    2. All occurences of the ObjectIdentifier is collected, thereby creating a collection of GameObject that should be saved.
    3. For each GO, a new instance of SceneObject is created. This gets "filled" by "packing" the GameObject's components into serializable form, and copying the ids, Transform, and other misc. data.
    4. Each SceneObject instance created for a GO is added to the SaveGame's list of SceneObjects.
    5. The SaveGame is serialized by the static function SaveLoad/Save method and written to a file in the hard disk. ISerializationSurrogates are used to serialize Unity-specific types like Vector3 (a few examples are provided)

    If we want to load a saved game, the following happens:
    1. All Objects which are not tagged as "DontDestroy" (persistent objects, like managers etc.) are destroyed, thereby clearing the scene and creating a blank slate.
    2. The SaveGame instance is deserialized from file
    3. Each SceneObject in the SaveGame instance's list of SceneObjects is "unpacked"; A SceneObject's prefabName value is used to pull a prefab from a the prefabDictionary and instantiate a copy of it.
    4. The newly instantiated GO's components and misc. values are reconstructed from the data stored in the SceneObject instance.

    I want to stress the fact that while it has been tested, it represents a bare-bones aproach and should be considered a stepping stone on the way to implement your own save/load feature in your project. This package has been kept simple on purpose, both because it should be easy to understand and also because each project is different in it's needs.

    It's also worth noting that serialization is, at least by me, considered an advanced topic which requires copious amounts of research and trial and error in order to understand it. I recommend the Microsoft Developer Network (MSDN) and StackOverflow.com for further information.

    Before using the assets, I strongly recommend at least cursory reading of all the scripts. I included numerous comments and some readme files. You absolutely HAVE to get a basic understanding of what happens :)

    SerializeHelper Download Link
    (Alternate download from my own webspace: SerializeHelper Download Link)
    (Project folder from my own webspace, in case the asset package doesn't work: SerializeHelper Project Folder)
     

    Attached Files:

    Last edited: Oct 9, 2016
  2. Cherno

    Cherno

    Joined:
    Apr 7, 2013
    Posts:
    513
    Comments & Critique are welcome. I am no pro programmer by any means, I just wanted to share what my own efforts in the field of serialization have brought me: A solid framework for saving and loading player progress.

    Things that would make reasonable additions:
    Solutions for serializing other Unity-spcific types like Mesh, Renderer, Material, ...
    Handling GameObject and Transform references directly on the field operating level
    Saving and loading single GameObjects for selective saving / loading (it's pretty much already there with Pack/Unpack GameObject, but not implemented)
     
  3. eydamson_pugeh

    eydamson_pugeh

    Joined:
    Feb 16, 2015
    Posts:
    2
    hey thanks ill try this.. :)
    ive been looking for systems that can serialize "scenes" (most of then are paid or not supported any more)
    i hope this could be my answer thanks any way God bless :)
     
  4. Cherno

    Cherno

    Joined:
    Apr 7, 2013
    Posts:
    513
    Considering that
    Considering that a scene is nothing more than the GameObjects in it as well as some misc information like skybox etc., then it's just a matter of saving the GameObjects and the misc data. I originally created this asset to save maps made for a voxel-type game, but of course it is equally suited for normal save games.
     
  5. RavenLiquid

    RavenLiquid

    Joined:
    May 5, 2015
    Posts:
    38
    So I'm looking into this solution my self and was hoping to find here what I was looking for.
    Unfortunatly I found your Texture2D surrogate to look a lot like mine at the moment, empty :).

    I see you do have a color surrogate, so my question is if this is the best way to go: serialize the pixels, via a Get/SetPixels32?
     
  6. Cherno

    Cherno

    Joined:
    Apr 7, 2013
    Posts:
    513
    Well as explained in the project, this particular ISS is just there to prevent non-serializable classes from throwing an error if they are a member of a custom class that is to be serialized. To actually serialize a Texture2D, yeah, Get/SetPixels looks like the way to go. I would only do this if the texture is actually created or changed at runtime, otherwise it's far easier to just save the name of the texture as a string and use that with Resources.Load to assign it to the Texture2D variable again after loading.
     
  7. RavenLiquid

    RavenLiquid

    Joined:
    May 5, 2015
    Posts:
    38
    Yes, thats my issue. I'm loading a massive amount of textures at runtime and I want to store them as one item to ease disk IO when I load them again later. Also I want to compress them after loading and do this only once.
     
  8. RavenLiquid

    RavenLiquid

    Joined:
    May 5, 2015
    Posts:
    38
    After some testing I found it is not the way to go. As I feared before (and is actually quite obvious afterwards) it is very inefficient on both the performance side and the storage side.

    Taking a 720P texture, and serializing it will take up at least (1280*720)*4 bytes of space.
    But Unity has to generate those pixels first, and this takes up a lot of time.

    5 textures generated a file of almost 60 megabytes and it took a long time doing it (did not time it but almost a minute I think).

    So I kept looking an came accros GetRawTextureData for the millionth time in code completion, but this time I also noticed the UpdateRawTextureData. I had not noticed it because I kept looking at the Set methods and thought you could only get the raw data.

    Well this is exactly what you want. I got a 3Mb file for 5 and a 900 Mb file out of 1500 720P textures, and it takes less then 10 seconds to store on my Surface Pro 3, and about the same amount of time to load.

    To improve this I'm going to see if compressing the stream before writing would be a good space/performance trade off.
     
  9. Cherno

    Cherno

    Joined:
    Apr 7, 2013
    Posts:
    513
    Good to see you are making progress!
     
  10. Brainswitch

    Brainswitch

    Joined:
    Apr 24, 2013
    Posts:
    270
    Uncompressed images take up quite a lot of space, I suggest looking into either PNG compression or LZMA (which is generic, not specific to images, so you can reuse it for other stuff - but it's still very good compression for images, it might even be better than PNG. I believe LZMA is used by Unity for quite a few things.). Then again, this depends on your target device(s), PNG is probably faster to compress&load than LZMA on lower-end devices. Oh, and don't use Unity's built in EncodeToPNG - it will only run on the Unity main thread. If you target devices has more than 1 core, you will get much better performance if compress and decompress multithreaded.

    Since you are using RawTextureData byte arrays, I would suggest trying with LZMA first, these
    might help:
    http://www.nullskull.com/a/768/7zip-lzma-inmemory-compression-with-c.aspx
    https://github.com/episage/LZMA-compression
     
    Last edited: Aug 26, 2015
  11. RavenLiquid

    RavenLiquid

    Joined:
    May 5, 2015
    Posts:
    38
    I was thinking about something like that, PNG is no good, as I want to serialize the whole lot in to one steam and then compress the stream before it is written.

    The LZMA example looks like what I was thinking and I'm going to try it out!
     
  12. Cherno

    Cherno

    Joined:
    Apr 7, 2013
    Posts:
    513
    After a lot of experimentation today, I managed to reconstruct GameObject references in scripts without the user having to add seperate variables in his scripts that hold the relevant ids. This has been a major obstacle since the very start and I'm really happy that I finally tackled it. The same procedure can be used for similar references, of course (Transform, for example). I will do some more testing and upload the modified version of SerializeHelper.
     
  13. Trung PV

    Trung PV

    Joined:
    Dec 17, 2015
    Posts:
    1
    Thank you sooooo much Cherno - my life savior !!!!!!!!!!!!!!!!!!!!!! It works like a charm !!! :))))
     
    Last edited: Jan 5, 2016
  14. luukvanoijen

    luukvanoijen

    Joined:
    Sep 15, 2015
    Posts:
    32
    You did a really good job Cherno, although I do have a question. Can this save an object with it's children, if yes, how? The children have things like rigidbody's, colliders and springs attached to them too.
     
  15. Cherno

    Cherno

    Joined:
    Apr 7, 2013
    Posts:
    513
    Thanks to both of you.

    @ Lucky: Yes, it can save and load an object with it's complete hierarchy intact. Let me explain.

    The most basic case is an object where the hierarchy doesn't change during play; No children are added or existing ones deleted or parented to other objects. In this case, nothing else needs to be done since when loading a saved game, the a clone of the prefab of the object just gets instantiated.

    If the hierarchy does change during play, then all children which may change need to have ObjectIdentifier components attached, and of course they also need Prefabs. Everything else is done by the script: It checks all children of an object for OIs and if one is present, saves that object's parent ID so it can be re-parented after loading.
     
    Last edited: Jan 5, 2016
  16. luukvanoijen

    luukvanoijen

    Joined:
    Sep 15, 2015
    Posts:
    32
    @Cherno Thanks for the quick answer. I don't add, delete or parent existing objects as children runtime, so I'm fine just having an ObjectIdentifier on the parent. But I do have another question, how can I trigger a save or load function with another script? I have an object with a trigger, so if a player is in the trigger, he can press the button and the game will save. I already have everything setup, but I just need to be able to trigger a save or load function, like SaveLoadScript.Save();
     
  17. luukvanoijen

    luukvanoijen

    Joined:
    Sep 15, 2015
    Posts:
    32
    @Cherno Okay so I found out, that SaveLoad.Save(); is working, but, how do I define which game to load? Load() takes a string, but I don't get what SaveGame saveGame means.
     
  18. luukvanoijen

    luukvanoijen

    Joined:
    Sep 15, 2015
    Posts:
    32
    @Cherno I think I figured it out! I just had to make a public SaveGame BouncersSave, then in the editor, I could choose the name of the Savegame.
     
  19. luukvanoijen

    luukvanoijen

    Joined:
    Sep 15, 2015
    Posts:
    32
    @Cherno I really need help on this one, I just can't get my game to save. What I have is a script that calls SaveLoad.Save(); This doesn't give any errors, but it won't save either. How would I save and load the game? I am looking through the SaveLoadMenu script, but I just can't really figure this one out :D
     
  20. Cherno

    Cherno

    Joined:
    Apr 7, 2013
    Posts:
    513
    You need to call the SaveGame function from the SaveLoadMenu script, since only that function will actually create a new instance of SaveGame and fill it with packed GameObjects (as SceneObjects).
    Take a look at the SaveGame() function in SaveLoadMenu: It takes no parameter so it's assumed that it should use the QuickSave slot, which it does by calling the SaveGame function which does take a paramter (string saveGameName), and uses "QuickSave" in this case.
    If you want to save and load different save games, then you just have to pass the right name as a string to the SaveGame and LoadGame functions. You could do this by keeping a public string variable in the SaveLoadMenu script which holds the SaveGame name, which is then used with the corresponding functions.

    Example:

    (insert the public string nameOfSaveGame; line and repalce the OnGUI function)

    Code (CSharp):
    1. public class SaveLoadMenu : MonoBehaviour {
    2.  
    3.     public bool showMenu;
    4.     public bool usePersistentDataPath = true;
    5.     public string savePath;
    6.     public Dictionary<string,GameObject> prefabDictionary;
    7.         //new line:
    8.         public string nameOfSaveGame;
    9. //...
    10.  
    11. void OnGUI() {
    12.      
    13.         if(showMenu == true) {
    14.             GUILayout.BeginHorizontal();
    15.             GUILayout.FlexibleSpace();
    16.             GUILayout.BeginVertical();
    17.             GUILayout.FlexibleSpace();
    18.          
    19.             if(GUILayout.Button("Exit to Windows")) {
    20.                 Application.Quit();
    21.                 return;
    22.             }
    23.  
    24.             nameOfSaveGame = GUILayout.TextField(nameOfSaveGame, 25);
    25.          
    26.           if(GUILayout.Button("QuickSave")) {
    27.                 SaveGame();
    28.                 return;
    29.             }
    30.          
    31.             if(GUILayout.Button("QuickLoad")) {
    32.                 LoadGame();
    33.                 return;
    34.             }
    35.        
    36.             if(GUILayout.Button("Save Game: " + nameOfSaveGame)) {
    37.                 SaveGame(nameOfSaveGame);
    38.                 return;
    39.             }
    40.          
    41.             if(GUILayout.Button("Load Game: " + nameOfSaveGame")) {
    42.                LoadGame(nameOfSaveGame);
    43.                return;
    44.            }
    45.  
    46.            GUILayout.FlexibleSpace();
    47.            GUILayout.EndVertical();
    48.            GUILayout.FlexibleSpace();
    49.            GUILayout.EndHorizontal();
    50.        }
    51.    }
    52. //...
    Normally, when opening the menu, you would check the save files on your hard disk and display them as a list of buttons so the user can just click on one and the game gets loaded / saved. The method above just provides a quick solution for testing purposes.
     
    Last edited: Jan 6, 2016
  21. luukvanoijen

    luukvanoijen

    Joined:
    Sep 15, 2015
    Posts:
    32
    @Cherno I really appreciate your support, thank you! I'll try to use SaveGame("BouncersSave") instead of Save(), as you said. Again, I really appreciate your support :D
     
  22. luukvanoijen

    luukvanoijen

    Joined:
    Sep 15, 2015
    Posts:
    32
    @Cherno I discovered a bug! Everytime I load a game without a save, it just creates an empty scene, which isn't bad, but everytime I create a new game, which works, I save it, but when I try to load it, the program just stops. No crash or anything, it just shows the button pressed, but I can't click anything and it doesn't load anything :p

    EDIT:
    If I do it in the editor, it says requires fullscreen camera. When I check the DebugLog I can see it loaded everything, it even says Loaded BouncersSave. In the hierarchy, I can only see the menu object, because that is on Don'tDestroy, because if it gets destroyed, I don't think it'll continue loading right? At least it doesn't freeze, it just doesn't load correctly.

    EDIT (again):
    I set the savepath to "c:/Saved Games/", but after saving, I can't find the folder Saved Games anywhere. If I now look in the debug log, it says that it can't find the save file.
     
    Last edited: Jan 7, 2016
  23. luukvanoijen

    luukvanoijen

    Joined:
    Sep 15, 2015
    Posts:
    32
    @Cherno Sorry, the "bug" was just my mistake, I managed to fix it (don't know how :p) but now I get this error:

    SerializationException: Type UnityEngine.AnimationCurve is not marked as Serializable.
    System.Runtime.Serialization.Formatters.Binary.BinaryCommon.CheckSerializable (System.Type type, ISurrogateSelector selector, StreamingContext context) (at /Users/builduser/buildslave/mono-runtime-and-classlibs/build/mcs/class/corlib/System.Runtime.Serialization.Formatters.Binary/BinaryCommon.cs:119)
    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-runtime-and-classlibs/build/mcs/class/corlib/System.Runtime.Serialization.Formatters.Binary/ObjectWriter.cs:386)
    System.Runtime.Serialization.Formatters.Binary.ObjectWriter.WriteObject (System.IO.BinaryWriter writer, Int64 id, System.Object obj) (at /Users/builduser/buildslave/mono-runtime-and-classlibs/build/mcs/class/corlib/System.Runtime.Serialization.Formatters.Binary/ObjectWriter.cs:306)
    System.Runtime.Serialization.Formatters.Binary.ObjectWriter.WriteObjectInstance (System.IO.BinaryWriter writer, System.Object obj, Boolean isValueObject) (at /Users/builduser/buildslave/mono-runtime-and-classlibs/build/mcs/class/corlib/System.Runtime.Serialization.Formatters.Binary/ObjectWriter.cs:293)
    System.Runtime.Serialization.Formatters.Binary.ObjectWriter.WriteQueuedObjects (System.IO.BinaryWriter writer) (at /Users/builduser/buildslave/mono-runtime-and-classlibs/build/mcs/class/corlib/System.Runtime.Serialization.Formatters.Binary/ObjectWriter.cs:271)
    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-runtime-and-classlibs/build/mcs/class/corlib/System.Runtime.Serialization.Formatters.Binary/ObjectWriter.cs:256)
    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-runtime-and-classlibs/build/mcs/class/corlib/System.Runtime.Serialization.Formatters.Binary/BinaryFormatter.cs:232)
    System.Runtime.Serialization.Formatters.Binary.BinaryFormatter.Serialize (System.IO.Stream serializationStream, System.Object graph) (at /Users/builduser/buildslave/mono-runtime-and-classlibs/build/mcs/class/corlib/System.Runtime.Serialization.Formatters.Binary/BinaryFormatter.cs:211)
    SaveLoad.Save (.SaveGame saveGame) (at Assets/Scripts/Serialization/SaveLoad.cs:33)
    SaveLoadMenu.SaveGame (System.String saveGameName) (at Assets/Scripts/Serialization/SaveLoadMenu.cs:134)
    SaveLoadMenu.Update () (at Assets/Scripts/Serialization/SaveLoadMenu.cs:49)

    But also this error:

    IOException: Sharing violation on path C:\Bouncers\Saved Games\BouncersSave.sav
    System.IO.FileStream..ctor (System.String path, FileMode mode, FileAccess access, FileShare share, Int32 bufferSize, Boolean anonymous, FileOptions options) (at /Users/builduser/buildslave/mono-runtime-and-classlibs/build/mcs/class/corlib/System.IO/FileStream.cs:320)
    System.IO.FileStream..ctor (System.String path, FileMode mode, FileAccess access, FileShare share, Int32 bufferSize)
    (wrapper remoting-invoke-with-check) System.IO.FileStream:.ctor (string,System.IO.FileMode,System.IO.FileAccess,System.IO.FileShare,int)
    System.IO.File.Create (System.String path, Int32 bufferSize) (at /Users/builduser/buildslave/mono-runtime-and-classlibs/build/mcs/class/corlib/System.IO/File.cs:135)
    System.IO.File.Create (System.String path) (at /Users/builduser/buildslave/mono-runtime-and-classlibs/build/mcs/class/corlib/System.IO/File.cs:130)
    SaveLoad.Save (.SaveGame saveGame) (at Assets/Scripts/Serialization/SaveLoad.cs:32)
    SaveLoadMenu.SaveGame (System.String saveGameName) (at Assets/Scripts/Serialization/SaveLoadMenu.cs:134)
    SaveLoadMenu.Update () (at Assets/Scripts/Serialization/SaveLoadMenu.cs:49)

    I don't think the first error is a big problem (pun unintended), but the second error stops it from saving.
     
  24. Cherno

    Cherno

    Joined:
    Apr 7, 2013
    Posts:
    513
    1. If you try to serialize Types that are not serializable and you don't use a ISerializationSurrogate for those Types, then you will get serialization errors.

    About the sharing violation, I never encountered it. You will have to search the internet for this type of error to see why it pops up. Make sure a folder named Saved Games is present.

    If all else fails, take a look at the sample scene provided and read all scripts and readme files carefully to get an understaning on what's happening. As I wrote in the first post, this is a fairly complex topic and a million things can go wrong, and no solution will work for every project, so in the end you will jsut have to figure a lot of things out for yourself :)
     
  25. luukvanoijen

    luukvanoijen

    Joined:
    Sep 15, 2015
    Posts:
    32
    @Cherno So what types couldn't be serialized? I think it could be canvas or something. For the second problem, I googled it, for everyone the solution was to close the file, but I looked through the save and load functions in SaveLoad.cs and it seems like it properly closes it. I do see the save file in the folder I wanted it to save too, but when I try to load it, it fails. I'll check tomorrow, because I'm on my phone right now haha.

    --Lucky
     
  26. Cherno

    Cherno

    Joined:
    Apr 7, 2013
    Posts:
    513
    Types that are not marked as serializable are, for example:
    Everything Unity-specific, like Mesh, Transform, GameObject, possibly Rect as well.
    Canvas inherits from MonoBehavior so it should not be a problem, but maybe the Canvas class itself contains problematic variables.

    Try inserting Debug.Log lines at various places to see what happens when.
     
  27. luukvanoijen

    luukvanoijen

    Joined:
    Sep 15, 2015
    Posts:
    32
    @Cherno I have a special jelly cube setup, with skinned mesh renderer. It uses springs to make it jelly, so could that be a problem? I have 1 hour of school left, then I have a break of about 3 hours :p. Then I'll get some Debug.Logs in place. Like I said, I can see that the file is in place, so it did save, but when Unity loads it, the hierarchy is completely empty. I think that it doesn't complete the saving process somehow, like it saves, but forgets to close the file. But then again, with the save file in place, it can't load the save file. I need to check what the error was again, it was something like "failed to load savefile", but it could also be "couldn't find savefile" or something :p. My ADD isn't helping on that part :D.

    --Lucky
     
    Last edited: Jan 8, 2016
  28. luukvanoijen

    luukvanoijen

    Joined:
    Sep 15, 2015
    Posts:
    32
    @Cherno I found another error! If I try to load the savefile, it gives me the following error:

    EndOfStreamException: Failed to read past end of stream.
    System.IO.BinaryReader.ReadByte () (at /Users/builduser/buildslave/mono-runtime-and-classlibs/build/mcs/class/corlib/System.IO/BinaryReader.cs:293)
    System.Runtime.Serialization.Formatters.Binary.ObjectReader.ReadNextObject (System.IO.BinaryReader reader) (at /Users/builduser/buildslave/mono-runtime-and-classlibs/build/mcs/class/corlib/System.Runtime.Serialization.Formatters.Binary/ObjectReader.cs:142)
    System.Runtime.Serialization.Formatters.Binary.ObjectReader.ReadObjectGraph (BinaryElement elem, System.IO.BinaryReader reader, Boolean readHeaders, System.Object& result, System.Runtime.Remoting.Messaging.Header[]& headers) (at /Users/builduser/buildslave/mono-runtime-and-classlibs/build/mcs/class/corlib/System.Runtime.Serialization.Formatters.Binary/ObjectReader.cs:110)
    System.Runtime.Serialization.Formatters.Binary.BinaryFormatter.NoCheckDeserialize (System.IO.Stream serializationStream, System.Runtime.Remoting.Messaging.HeaderHandler handler) (at /Users/builduser/buildslave/mono-runtime-and-classlibs/build/mcs/class/corlib/System.Runtime.Serialization.Formatters.Binary/BinaryFormatter.cs:179)
    System.Runtime.Serialization.Formatters.Binary.BinaryFormatter.Deserialize (System.IO.Stream serializationStream) (at /Users/builduser/buildslave/mono-runtime-and-classlibs/build/mcs/class/corlib/System.Runtime.Serialization.Formatters.Binary/BinaryFormatter.cs:136)
    SaveLoad.Load (System.String gameToLoad) (at Assets/Scripts/Serialization/SaveLoad.cs:51)
    SaveLoadMenu.LoadGame (System.String saveGameName) (at Assets/Scripts/Serialization/SaveLoadMenu.cs:149)
    SaveLoadMenu.OnGUI () (at Assets/Scripts/Serialization/SaveLoadMenu.cs:64)
     
  29. luukvanoijen

    luukvanoijen

    Joined:
    Sep 15, 2015
    Posts:
    32
    @Cherno I have googled a lot, and I actually found out something very usefull. The saving process is easier if you create the file with Streamwriter. You are using File.Create and it works for you. But using Streamwriter you can create the file if it doesn't exist, but if it exists, it automatically writes to it! I think I am going to try to implement Streamwriter myself, but I don't know how. It should get rid of the problem.

    --Lucky

    EDIT:
    The problem with saving was, that I was trying to serialize an Animation Curve. So I deleted all the animation curves and now it saves. But when I try to load the save file, it just gives me this error:
    SerializationException: Field "SlopeCurveModifier" not found in class UnityStandardAssets.Characters.FirstPerson.RigidbodyFirstPersonController+MovementSettings
    System.Runtime.Serialization.Formatters.Binary.ObjectReader.ReadTypeMetadata (System.IO.BinaryReader reader, Boolean isRuntimeObject, Boolean hasTypeInfo) (at /Users/builduser/buildslave/mono-runtime-and-classlibs/build/mcs/class/corlib/System.Runtime.Serialization.Formatters.Binary/ObjectReader.cs:691)
    System.Runtime.Serialization.Formatters.Binary.ObjectReader.ReadObjectInstance (System.IO.BinaryReader reader, Boolean isRuntimeObject, Boolean hasTypeInfo, System.Int64& objectId, System.Object& value, System.Runtime.Serialization.SerializationInfo& info) (at /Users/builduser/buildslave/mono-runtime-and-classlibs/build/mcs/class/corlib/System.Runtime.Serialization.Formatters.Binary/ObjectReader.cs:269)
    System.Runtime.Serialization.Formatters.Binary.ObjectReader.ReadObject (BinaryElement element, System.IO.BinaryReader reader, System.Int64& objectId, System.Object& value, System.Runtime.Serialization.SerializationInfo& info) (at /Users/builduser/buildslave/mono-runtime-and-classlibs/build/mcs/class/corlib/System.Runtime.Serialization.Formatters.Binary/ObjectReader.cs:195)
    System.Runtime.Serialization.Formatters.Binary.ObjectReader.ReadObject (BinaryElement element, System.IO.BinaryReader reader, System.Int64& objectId, System.Object& value, System.Runtime.Serialization.SerializationInfo& info) (at /Users/builduser/buildslave/mono-runtime-and-classlibs/build/mcs/class/corlib/System.Runtime.Serialization.Formatters.Binary/ObjectReader.cs:223)
    System.Runtime.Serialization.Formatters.Binary.ObjectReader.ReadNextObject (System.IO.BinaryReader reader) (at /Users/builduser/buildslave/mono-runtime-and-classlibs/build/mcs/class/corlib/System.Runtime.Serialization.Formatters.Binary/ObjectReader.cs:154)
    System.Runtime.Serialization.Formatters.Binary.ObjectReader.ReadObjectGraph (BinaryElement elem, System.IO.BinaryReader reader, Boolean readHeaders, System.Object& result, System.Runtime.Remoting.Messaging.Header[]& headers) (at /Users/builduser/buildslave/mono-runtime-and-classlibs/build/mcs/class/corlib/System.Runtime.Serialization.Formatters.Binary/ObjectReader.cs:110)
    System.Runtime.Serialization.Formatters.Binary.BinaryFormatter.NoCheckDeserialize (System.IO.Stream serializationStream, System.Runtime.Remoting.Messaging.HeaderHandler handler) (at /Users/builduser/buildslave/mono-runtime-and-classlibs/build/mcs/class/corlib/System.Runtime.Serialization.Formatters.Binary/BinaryFormatter.cs:179)
    System.Runtime.Serialization.Formatters.Binary.BinaryFormatter.Deserialize (System.IO.Stream serializationStream) (at /Users/builduser/buildslave/mono-runtime-and-classlibs/build/mcs/class/corlib/System.Runtime.Serialization.Formatters.Binary/BinaryFormatter.cs:136)
    SaveLoad.Load (System.String gameToLoad) (at Assets/Scripts/Serialization/SaveLoad.cs:59)
    SaveLoadMenu.LoadGame (System.String saveGameName) (at Assets/Scripts/Serialization/SaveLoadMenu.cs:149)
    SaveLoadMenu.OnGUI () (at Assets/Scripts/Serialization/SaveLoadMenu.cs:64)
    I tried File.Flush() before closing, but that didn't. To be expected though.
     
    Last edited: Jan 8, 2016
  30. Cherno

    Cherno

    Joined:
    Apr 7, 2013
    Posts:
    513
    If you serialize a class instance, you also have to deserialize into that exact Type of class, that means there may not be any differences between the fields of the saved and loaded instance. It is possible to tell the serializer exactly how to serialize and deserialize a file, however, which is commonly used when a program is updated (version changes) and classes have changed.
     
  31. luukvanoijen

    luukvanoijen

    Joined:
    Sep 15, 2015
    Posts:
    32
    @Cherno Things are going horribly. I deleted the prefab of the player, deleted the player from the scene and made my own player prefab. I also gave it a different name. So I deleted StandardAssets/Characters, because I no longer needed it. Now it says "TypeLoadException: Could not load type 'UnitySandardAssets.Charachters.FirstPerson.RigidbodyFirstPersonController+MovementSettings'." How could I make sure that it doesn't try to load the asset?

    --Lucky
     
  32. Cherno

    Cherno

    Joined:
    Apr 7, 2013
    Posts:
    513
    If you start to modify things, make sure you know what it will cause. That's all I can say :)
    Normally, it shouldn't matter if you delete something, as long as it's not a prefab that is needed by a savegame to recreate a saved GO.
     
  33. davidovitch82

    davidovitch82

    Joined:
    Sep 8, 2015
    Posts:
    13
    @Cherno Hey, thanks for sharing this piece of technology. I was dreading having to write my own serializing system and (re-)designing my entire project around it until I found SerializeHelper. It seems to do exactly what I need and nothing more, which is great because it doesn't add anything unnecessary to my project.

    However while I can import the package into an empty project and mess around with it just fine, when I import it into my existing project I get a couple of errors that seem to be caused by the fact that I have an existing script called "Type.cs"

    This might seem like a silly question but what would be the recommended way to resolve this issue? I can rename the existing script and the 'public class Type: MonoBehavior' in it, but many of my other scripts check for values in the Type script. Anybody have any pro tips on how to deal with this situation?

    Thanks in advance!

    David
     
  34. Cherno

    Cherno

    Joined:
    Apr 7, 2013
    Posts:
    513
    I have to say that the problem lies more in your existing project than the imported SerializeHelper asset :)
    The Type class is not something I wrote for the SH but rather is native to NET (via import System) so I'm really using something that is already there but not always needed. I generally take care to not use any class names that may cause conflicts with existing ones.

    So... All I can suggest is to do the same and not use any Type names that already exist in NET ;)
     
  35. davidovitch82

    davidovitch82

    Joined:
    Sep 8, 2015
    Posts:
    13
    Thanks for the quick reply. I don't have an education in programming so I'm learning as I go ;)

    So do I understand this correctly: I didn't encounter any problems with my existing Type class because I didn't add "using System;" and then use the NET Type class in any of my scripts. Soon as a script is added that does (in this case by importing the SerializationHelper package) a conflict arises ..?

    And as a follow-up question: How can I avoid such conflicts without knowing - to put it dramatically - the names of all classes in something like NET that might be added to my project at any time. I suppose using a name like 'Type' for a public class was a silly idea, but who knows what other names I should not use to avoid future conflicts? Is this part of a learning curve?

    Thanks again for your help :)
     
  36. Cherno

    Cherno

    Joined:
    Apr 7, 2013
    Posts:
    513
    Yes.

    Not sure how to answer that one... I never had much of a problem with this. It helps to avoid extremely generic class names like "Object" and "Collection". When in doubt, there's always MSDN where you can check if something already exists.
     
  37. Vedrit

    Vedrit

    Joined:
    Feb 8, 2013
    Posts:
    505
    If you're using Visual Studios, it'll generally underline in green any classes, variables or functions that conflict.
     
  38. davidovitch82

    davidovitch82

    Joined:
    Sep 8, 2015
    Posts:
    13
    Ok I'm sure I might figure this out myself at some point, but what does it take for GameObjects to get added to the prefabDictionary?

    I'm using the SaveLoadMenu script for now to test things, but only the TestObject 1 and TestObject 2 are added to the prefabDictionary. I've added ObjectIdentifier scripts to several other prefabs as well as to some gameobjects in my scene, but they don't get added?
     
  39. Cherno

    Cherno

    Joined:
    Apr 7, 2013
    Posts:
    513
    Take a look at the code:

    Code (CSharp):
    1. prefabDictionary = new Dictionary<string, GameObject>();
    2.         GameObject[] prefabs = Resources.LoadAll<GameObject>("");
    3.         foreach(GameObject loadedPrefab in prefabs) {
    4.             if(loadedPrefab.GetComponent<ObjectIdentifier>()) {
    5.                 prefabDictionary.Add (loadedPrefab.name,loadedPrefab);
    6.                 Debug.Log("Added GameObject to prefabDictionary: " + loadedPrefab.name);
    7.             }
    8.         }
    All GameObjects somewhere in the Resources folder that have an ObjectIdentifier component are added. So if your prefabs are only in the Assets folder but not in the Resources folder, they won't be added.
     
  40. davidovitch82

    davidovitch82

    Joined:
    Sep 8, 2015
    Posts:
    13
    Yeah that was a very silly question, sorry :p

    Currently I'm trying to work out if I can serialize 2d arrays of gameobjects in a script component with SerializeHelper. I'm setting a separate 2d array of strings to store and read the objectID's on serialize and deserialize but so far it does't work (the 2d array is null after deserializing).
     
  41. Cherno

    Cherno

    Joined:
    Apr 7, 2013
    Posts:
    513
    Multi-dimensional arrays should serialize without any problems if their type is serializable. Still, you could try it with a flat array first and see if that works as intended. Zou could also try flattening the m/d array (and unflattening it after deserializeing).
     
  42. davidovitch82

    davidovitch82

    Joined:
    Sep 8, 2015
    Posts:
    13
    While my 2d array contains GameObjects, which do get deserialized: they appear in the scene after loading. But the relationship to the parent (all objects in the 2d array are parented to the same GameObject) is gone after loading.

    In OnSerialize I do:

    Code (Csharp):
    1.  
    2. eventsIds[x,y]=events[x,y].GetComponent<ObjectIdentifier>().id;
    3. Debug.Log("SettingobjectIDforEVENT:"+events[x,y]);
    4.  
    which seems to work, the debug line tells me it set the ID
    In OnDeserialize I do:

    Code (Csharp):
    1.  
    2. for(intx=0;x<events.GetLength(0);x++){
    3.  
    which throws an error "object reference not set to an instance of an object". So that means the array called 'event' is null? It starts out as such but before saving has been populated with a couple of gameobjects.

    Anyway maybe I'll try turning it into a 1d array before serialize and then back to normal afterwards.
     
  43. Cherno

    Cherno

    Joined:
    Apr 7, 2013
    Posts:
    513
    Well, from what I understand, you are not serializing the events array, only the eventsIds array (which makes sense since one is of Type GameObject and one is of Type string). However, you try to access the events array which didn't get serialized, or deserialized, for that matter, which explains why it throws an error, since it might not be initialized. So I guess you would have to use

    Code (CSharp):
    1. for(intx=0;x<eventsIds.GetLength(0);x++){
    instead, and/or initialize the events array first (and fll it).
     
  44. Astrydax

    Astrydax

    Joined:
    Mar 2, 2016
    Posts:
    14
    Why not put this on the asset store?
     
  45. Cherno

    Cherno

    Joined:
    Apr 7, 2013
    Posts:
    513
    Good question. I asked myself that question :D I think thte reason is that it would really like to add a few important pieces of functionality (in particular, automatic reference handling for GameObject and similar variables). I'm currently not working on any projects that use a savegame feature so I didn't work on it yet. It's coming! :)
     
  46. Evasion4D

    Evasion4D

    Joined:
    Aug 8, 2013
    Posts:
    16
    Very good job Cherno,

    I have some questions...

    1/ The Hierarchy is not respected after loading
    Save :
    Group 1
    .....TestObject 1
    Group 2
    .....Group 2 a
    ..........TestObject 1 2 a
    .....Group 2 b
    ..........TestObject 1 2 b

    Load :
    Group 2 b
    .....TestObject 1 2 b
    Group 2 a
    .....TestObject 1 2 a
    Group 2
    Group 1
    TestObject 1

    So if the object has only one parent but no grandparent, it becomes root

    2/ I want to recover the materials of an object (as I change in runtime)
    So I added:
    List<string> componentTypesToAdd = new List<string>() {
    "UnityEngine.MonoBehaviour","UnityEngine.Renderer"
    };

    But then I get an error at 360 var tp = obj.GetType();
    NullReferenceException: Object reference not set to an instance of an object
    SaveLoadMenu.UnpackComponents (UnityEngine.GameObject& go, .SceneObject sceneObject) (at Assets/Scripts/Serialization/SaveLoadMenu.cs:360)

    Have an idea ?

    Thanks a lot
     
    Last edited: Aug 29, 2016
  47. Cherno

    Cherno

    Joined:
    Apr 7, 2013
    Posts:
    513
    Good work catching that parenting issue. I think I never tested it with complex hierarchies, but it should be easy to correct. It's gonna take a few weeks, though. Time is short at the moment.

    For keeping track of material, I think it's more reliable to load all material into a dictionary at game start (akin to the prefab iedentifier dictionary), and then use the On(De)Serialize functions to assign the current material name to a dedicated variable on thart script, and use the dictionary with the recovered material name to reassign it to the renderer after loading.

    If you want to work out your own way: It's always a good idea to insert Debug.Log lines, especialy if there is a null reference exception, so you can check what exactly goes wrong and when/where.
     
  48. Evasion4D

    Evasion4D

    Joined:
    Aug 8, 2013
    Posts:
    16
    Thanks Cherno
    I focus on materials.
    I had already started with a dictionary but I wanted to know if you had a different approach
    If you have time to correct the hierarchy ... it would be welcome
     
  49. Cherno

    Cherno

    Joined:
    Apr 7, 2013
    Posts:
    513
    Are you sure that all GameObjects in eht hierarchy have the PrefabIdentifier component, and are prefabs that are loaded into the Prefab Dictionary at game start? From the top of my head, I can't think of any reason why the hierarchy couldn't be reconstructed.
     
  50. Evasion4D

    Evasion4D

    Joined:
    Aug 8, 2013
    Posts:
    16
    Group 1 and Group 2 are from Empty object Prefab (called "TestEmpty") with your "ObjectIdentifier" script
    There are in the Resources/Prefabs folder, like TestObject 1

    Console In runtime :
    Added GameObject to prefabDictionary: TestEmpty
    Added GameObject to prefabDictionary: TestObject 1

    Can you explain me the role of oi and idScript in SaveLoadMenu.cs ?

    325
    if(go.GetComponent<ObjectIdentifier>() == false) {
    ObjectIdentifier oi = go.AddComponent<ObjectIdentifier>();
    }

    ObjectIdentifier idScript = go.GetComponent<ObjectIdentifier>();
    idScript.id = sceneObject.id;
    idScript.idParent = sceneObject.idParent;

    Thanks for your time
     
    Last edited: Sep 3, 2016