Search Unity

How to Serialize Scripable Object at Runtime?

Discussion in 'Scripting' started by DiligentGear, May 18, 2014.

  1. DiligentGear

    DiligentGear

    Joined:
    Dec 25, 2013
    Posts:
    28
    I recently read this thread: http://forum.unity3d.com/threads/155352-Serialization-Best-Practices-Megapost

    and I successfully serialized ScriptableObject in Editor.

    But the problem is, this doesn't seem to work with runtime serialization. I got this following error in my build:

    Code (csharp):
    1. SerializationException: Type UnityEngine.ScriptableObject in assembly UnityEngine, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null is not marked as serializable.
    Apparently BinaryFormatter is not happy about ScriptableObject. Is there any other way to serialize it during runtime?
     
  2. LightStriker

    LightStriker

    Joined:
    Aug 3, 2013
    Posts:
    2,717
    XML and JSon are known to not care much for the Serializable flag.
    Or you could write your own serializable wrapper around ScriptableObject.
     
  3. DiligentGear

    DiligentGear

    Joined:
    Dec 25, 2013
    Posts:
    28
    I'm trying to serialize in binary. Is there any hints on writing such wrapper?
     
  4. LightStriker

    LightStriker

    Joined:
    Aug 3, 2013
    Posts:
    2,717
    Usually, BinaryFormatter will nicely react to a class implementing ISerializable interface. With it, you can control what data is serialized and how.
    http://msdn.microsoft.com/en-us/library/system.runtime.serialization.iserializable.aspx

    A wrapper would look like;

    Code (csharp):
    1.  
    2.     [Serializable]
    3.     public class MyWrapper : ISerializable
    4.     {
    5.         public ScriptableObject scriptable;
    6.  
    7.         public MyWrapper()
    8.         {
    9.             // Empty constructor required to compile.
    10.         }
    11.  
    12.         // Implement this method to serialize data. The method is called on serialization.
    13.         public void GetObjectData(SerializationInfo info, StreamingContext context)
    14.         {
    15.             info.AddValue("ScriptableType", scriptable.GetType().AssemblyQualifiedName, typeof(string));
    16.             foreach(FieldInfo field in scriptable.GetType().GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy | BindingFlags.Instance))
    17.             {
    18.                 info.AddValue(field.Name, field.GetValue(scriptable), field.FieldType);
    19.             }
    20.         }
    21.  
    22.         // The special constructor is used to deserialize values.
    23.         // In this case, it recreate the original ScriptableObject.
    24.         public MyWrapper(SerializationInfo info, StreamingContext context)
    25.         {
    26.             Type type = Type.GetType((string)info.GetValue("ScriptableType", typeof(string)));
    27.             if (type == null)
    28.                 return;
    29.  
    30.             scriptable = ScriptableObject.CreateInstance(type);
    31.             if (scriptable == null)
    32.                 return;
    33.  
    34.             foreach (FieldInfo field in scriptable.GetType().GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy | BindingFlags.Instance))
    35.             {
    36.                 field.SetValue(scriptable, info.GetValue(field.Name, field.FieldType));
    37.             }
    38.         }
    39.     }
    Note that I wrote that quickly and haven't tested it. But it should work fine, or close.
     
    oAzuehT likes this.
  5. DiligentGear

    DiligentGear

    Joined:
    Dec 25, 2013
    Posts:
    28
    Your code works very well. I truly appropriate it!

    But there are still tons of headaches--A similar wrapper is needed for every Unity built-in type (I can't believe even value types like Vector3 are not serializable...) I included in my ScriptableObject. I'm not sure whether this will work on other types though.

    The worst part is that, XML won't solve this problem, either. I got this error when I tried to serialize an object that has a Transform field:
    I changed my design eventually, by only serializing primitive types, to avoid such headaches in the future. I hope Unity 5 would have some improvements about serialization. It really messed up all of my coding structure in order to make it work.
     
  6. LightStriker

    LightStriker

    Joined:
    Aug 3, 2013
    Posts:
    2,717
    Any reason why you would need such deep serialization at runtime? Sounds like overdesign to me... Unless you got a very good reason.
     
  7. DiligentGear

    DiligentGear

    Joined:
    Dec 25, 2013
    Posts:
    28
    I'm building a customization system where the player can customize their own character and weapon in game, and the player should be able to save their character and weapon in a database. The best case is to directly serialize/deserialize the character/weapon class.

    For example, changing skin color (where need to change material color), adding tattoos (where need to edit texture), attaching accessories (where need to know the attaching point, orientation, and a prefab), adding stabilizer adds-on to a sniping rifle (where the properties in the script need to be changed), and etc.

    What I'm doing right now is to encapsulate all possible customizable "parts" as prefabs, in Resources folder, so I can use Resources.Load to search for prototypes by their names. Therefore, the contents that need to be serialized become strings and other primitive types.

    Edit: I do feel like I'm running into some design flaws... Any advice will be appreciated.
     
    Last edited: May 19, 2014
  8. LightStriker

    LightStriker

    Joined:
    Aug 3, 2013
    Posts:
    2,717
    In a project I'm currently on, we have exactly this kind of character customization.

    We have all our character "parts" definition as ScriptableObject, each of them having a unique Guid. The definition contains a name, description, icon, price, and so on. It also has the name of the prefab to load if needed. We load all the ScriptableObject at start up to build a list of all existing parts.

    When the player choose a part, we serialize to JSon (string) a small intermediate class deriving from System.object. This class has no method and is only a container for JSon. We only save the Guid of the part and the colors the player chose. In the end, while the customization system is VERY deep, the serialization stay rather simple.

    JSon is useful to save using PlayerPref (string) and to send that data over network. Java and backends server are usually very friendly to JSon.

    All the NPC in our game are built the same way, using that customization system. So the designer don't place a full NPC in a level, but place a spawner with the proper NPC definition.
     
  9. DiligentGear

    DiligentGear

    Joined:
    Dec 25, 2013
    Posts:
    28
    I just realized what Guid does in AssetDatabase class. *facepalm* Time to start over.
    I was thinking about only serializing unique signatures for loading assets, but I just couldn't figure out what that this.

    Thank you for sharing your great codes and experiences!
     
  10. LightStriker

    LightStriker

    Joined:
    Aug 3, 2013
    Posts:
    2,717
    Well, in our case, we implemented our own Guid on top, because AssetDatabase is a UnityEditor class and not accessible at runtime.
    We even added Guid on GameObject to track them between scene load. Made a quest system that is external to all scene, but still able to target GameObject and resolve the reference to them when they are loaded. In other words, prefab targeting scene data. :p
     
  11. DiligentGear

    DiligentGear

    Joined:
    Dec 25, 2013
    Posts:
    28
    I just encountered the issue where the UnityEditor class is inaccessible during runtime. Is this custom Guid system like having all ScriptableObject have a Guid object, and retrieving them using reflection?
     
  12. LightStriker

    LightStriker

    Joined:
    Aug 3, 2013
    Posts:
    2,717
    No... We created a base class that derive from ScriptableObject. The Guid is a explicit field of that class.

    Any item we need to have runtime identification/serialization derive from that class.

    Since those never hold any heavy asset directly (mesh, texture, etc), we load them all at startup. So when we load a definition that requires a specific item, we just search in a dictionary for that Guid.
     
  13. DiligentGear

    DiligentGear

    Joined:
    Dec 25, 2013
    Posts:
    28
    Thanks for the clarification! So the loading process is like : Start the game -> Map all possible runtime serializable definitions in a dictionary by their pre-defined Guid -> Receive a list of requested items -> Load the concrete items targeted by the definitions.

    Everything makes more sense now. Again, thanks for your help. Time to work :-D
     
  14. LightStriker

    LightStriker

    Joined:
    Aug 3, 2013
    Posts:
    2,717
    Yup. We searched for a while for the best solution and settled for this one. Maybe there's another one that would work, but we haven't found it yet.

    It makes it quite easy to update the game with new item using AssetBundles, since in the mapping process, we load both from Resources and from all AssetBundles. That base class also has a runtime ref to the asset bundle it was loaded from, for when we need to load the asset it points too. In other word, we can add items to the game without re-releasing the game.

    Guid also has the advantage that they are fast to compare, is almost impossible to have two with the same ID, and can be easily converted back and forth to string.

    Little note, Unity is unable to serialize Guid properly. No idea why, even if the class if flagged Serializable. So, we save the item Guid as a byte array (byte[]).