Search Unity

Serializing ScriptableObject references with JsonUtility

Discussion in 'Scripting' started by codegasm, Nov 17, 2017.

  1. codegasm

    codegasm

    Joined:
    May 3, 2015
    Posts:
    38
    In my game save system I am using plain C# classes to hold the data. I am also using ScriptableObject classes saved as .asset files in my project to define data that doesn't change. In some places I need to refer to these .asset files in my save data.

    Like so many others I have now found that JsonUtility.ToJson doesn't handle references to ScriptableObjects in a way that can be used for game saves. It saves InstanceIDs which are ephemeral and change each time you restart Unity. This makes my game saves useless.

    My intention was for it to serialize a reference to the .asset file in question instead of to the ScriptableObject instance at runtime. Is there some way of changing the way JsonUtility handles references or should I be using some other form of serialization in order to achieve this?

    I know this question has been asked before, but I have yet to find a useful answer. From what I've read the EditorJsonUtility class uses assetGUID/fileID since Unity 5.5.0 but I obviously can't use that class since it only works in the editor.
     
    IvanIvanovP3 and SudoCat like this.
  2. TonyLi

    TonyLi

    Joined:
    Apr 10, 2012
    Posts:
    12,692
    This has probably already occurred to you, so I'm just putting it out there as another voice that this is probably the way to go: Create another ScriptableObject assets that holds a list of references to your ScriptableObject assets. In your save data, record the list index. You can implement ISerializationCallbackReceiver and get/set the index in the callbacks.
     
    IvanIvanovP3 likes this.
  3. codegasm

    codegasm

    Joined:
    May 3, 2015
    Posts:
    38
    Thanks, that helped me get my thinking unstuck, I can probably blame Friday afternoon brain for not thinking of that myself. I would have preferred something a bit prettier but this sounds like it can work. I just have to figure out a good way to maintain an array of assets.
     
  4. SudoCat

    SudoCat

    Joined:
    Feb 19, 2013
    Posts:
    64
    I'm stuck in a similar boat right now. I tried going down this route, using a ScriptableObject asset containing a list of references, and using the Serialization callbacks to get the reference, but I can't find a reliable way to handle the OnAfterDeserialise. I'm storing a reference to the name during OnBeforeSerialize, and then attemping to reset the _reference from that name during OnAfterSerialize:

    Code (CSharp):
    1.         public void OnBeforeSerialize()
    2.         {
    3.             _name = null;
    4.             if (_reference)
    5.             {
    6.                 _name = _reference.name;              
    7.             }
    8.         }
    9.  
    10.         public void OnAfterDeserialize()
    11.         {
    12.             if (!_reference)
    13.             {
    14.                 _reference = Database.Instance.GetRow<T>(_name);
    15.             }
    16.         }
    Unfortuantely, Unity disallows use of Resources.Load and other similar methods during serialisation, so I can't access the current Database instance.

    Any suggestions? Right now I've resorted to using a property to handle the deserialization on request, but that doesn't feel like a reliable option.
     
  5. codegasm

    codegasm

    Joined:
    May 3, 2015
    Posts:
    38
    I ended up using a tiny MonoBehaviour which holds a reference to the ScriptableObject with the lookup table. The MonoBehaviour lives in my scene with [ExecuteInEditMode] and a static instance which is set in OnEnable. In the OnAfterDeserialize method I check that the instance is set and if so I do the lookup. This works fine for me since I only need the lookup to work at runtime and only at certain times in edit mode.
     
  6. SudoCat

    SudoCat

    Joined:
    Feb 19, 2013
    Posts:
    64
    Thanks, I was really hoping I could avoid using a scene object for it just in case someone forgets to add it, but that's probably the best option. Looks like it's all working now, I'll run some proper tests over the weekend.

    Thanks again for the help!
     
    codegasm likes this.