Search Unity

Serialize a SO with a reference to another SO

Discussion in 'Scripting' started by gdbjohnson3, Feb 22, 2019.

  1. gdbjohnson3

    gdbjohnson3

    Joined:
    Mar 11, 2018
    Posts:
    13
    Hey there,

    I have a Scriptable Object that stores some game context data, such as player equipment.

    Like so:

    Code (CSharp):
    1. public class PlayerData : ScriptableObject {
    2.         public int CoinBalance = 0;
    3.         public int HitRequired = 1;
    4.         public int NumberOfLives = 3;
    5.         public LevelItemData CurrentLevel;
    6.         public WeaponModData CurrentWeapon;
    7.         public EngineModData CurrentEngine;
    8. }
    `WeaponModData`, `EngineModData`, and `LevelItemData` are Scriptable Objects.

    Changes to PlayerData are written to a flat file using the `JsonUtility`.

    My system for storing selected weapons etc using this method works most of the time. I think it is usually around a Build however, that my references in the JSON flat file get broken, and when I deserialize my JSON, Unity has a null for my Level, Weapon and Engine. It was my understanding that this method should be fine... these are references to SO data objects, and this system works most of the time in the editor.

    Here's my deserialize code:
    Code (CSharp):
    1. public static T LoadGameData<T>(T data, string fileName) where T : ScriptableObject {
    2.             string filePath = GetFilePath(fileName);
    3.             if (File.Exists(filePath)) {
    4.                 string json = File.ReadAllText(filePath);
    5.                 JsonUtility.FromJsonOverwrite(json, data);
    6.             }
    7.             return data;
    8.         }
    Here is the JSON:
    Code (CSharp):
    1. {"CoinBalance":179,"HitRequired":1,"NumberOfLives":3,"CurrentLevel":{"instanceID":11416},"CurrentWeapon":{"instanceID":11550},"CurrentEngine":{"instanceID":12418},"UnlockedLevels":[{"instanceID":11416}],"AvailableWeapons":[{"instanceID":11550}],"AvailableEngines":[{"instanceID":12418}]}
    All of the instanceID's seem to no longer point to anything, since when I deserialize, none of the references get populated. I get that Instances of prefabs won't work, but these are references to SO data files, which my understanding is totally valid for this method.

    What am I doing wrong? Is there a better way to store game state that includes references to SO data objects? I need these references to stay valid between builds. Incidentally, these SO data objects hold references to Prefabs... not sure if that matters.
     
  2. xVergilx

    xVergilx

    Joined:
    Dec 22, 2014
    Posts:
    3,296
    Do not serialize ScriptableObjects for game state.

    Wrap your required data into struct, and store that instead.
    To fetch another ScriptableObject on load - attach a unique identifier to your scriptable object.
    Then, create a lookup for all your scriptable objects of that type, and check against it.

    Something like Dictionary<string, SomeScriptableData> where string is your unique id deserialized from file.
    You can use Guid.NewGuid().ToString() to generate a unique id (or any other approach will work as well).
     
  3. gdbjohnson3

    gdbjohnson3

    Joined:
    Mar 11, 2018
    Posts:
    13
    Sigh. I was afraid you would say that. Seems to be the only thing that makes sense, I guess, given the behaviour. When I first did this approach, I expected identifiers to not change, but since they do, your approach is the only one that can work. Not a huge deal I guess... just another unity gotcha.

    thanks for the quick reply.
     
  4. gdbjohnson3

    gdbjohnson3

    Joined:
    Mar 11, 2018
    Posts:
    13
    Now that I'm getting into it, this seemingly simple requirement of persisting data tied to an SO is causing me to create an entirely redundant structure in code alongside what I have built as SO's. "Use them like Enums" they said... ya, just don't try and store them between sessions.
     
  5. Owen-Reynolds

    Owen-Reynolds

    Joined:
    Feb 15, 2012
    Posts:
    1,997
    It might help to think of ScriptableObjects as prefabs. They're really just dumbed down versions of those. Whatever a prefab can't do, they can't do.

    The problem is about linking to other scripts. Scene objects can only link to things in the same scene, or to Assets; and prefabs can only have links to other Assets. In the old days, if we wanted some weaponModData instances anyone could link to, we needed to put them in dummy prefabs - take an empty, add the script, set values, and make it a prefab. Now anyone can link to it and read it.

    ScriptableObjects are those same hacky prefabs, changed to make it obvious what we're doing. Can't be spawned, the icon is changed, and required to have a script and nothing else. But an SO is still just a limited type of prefab.
     
    rvinowise likes this.
  6. gdbjohnson3

    gdbjohnson3

    Joined:
    Mar 11, 2018
    Posts:
    13
    Ya. But if you watch some of Unity's tutorials about SO's, they promote its ability for data storage, and there is a lot of implementation in place for serializing them, such as instantiating an SO from JSON with their utility. All the infrastructure is in place.

    And, I'm not entirely sure why Unity has to change all the internal identifiers between builds. I was serializing references to SO's during my development of this feature, and everything worked until I compiled for iOS/Android, and then for some reason all the internal identifiers changed. If Unity could make it so that SO's never changed these internal id's, I think it would all work. This would be a powerful enhancement.