Search Unity

  1. Unity 2019.2 is now released.
    Dismiss Notice

Referencing JSON Item Definitions In Scene

Discussion in 'Scripting' started by DapperDino, Sep 30, 2019.

  1. DapperDino

    DapperDino

    Joined:
    Feb 18, 2018
    Posts:
    18
    Hi, I'm really struggling to decide the best way for me to store data for the items in my single player RPG.

    Ever since I started using Scriptable Objects I thought that they would be perfect for the job as it's much easier to set up their data inside the inspector than externally with XML or JSON files. They were working great for me until I realised that I needed run-time data on my items, for example, durability and enchantments. My next idea was to store the default item data in JSON files and to use JsonUtility to parse the data into a normal Item class where I can freely change durability/enchantments and then serialize to file for saving and loading.

    My only problem with this is that when designing, for example, quests that give item rewards or chests that have loot, it's not very easy to assign the items I want without just using Ids compared to the simplicity of selecting a ScriptableObject to use. Any guidance will be greatly appreciated.

    Thanks in advance,
    Nathan
     
  2. DapperDino

    DapperDino

    Joined:
    Feb 18, 2018
    Posts:
    18
    Maybe I would need to make editor extensions to design the items anyway because as far as I am aware, JsonUtility doesn't work with abstract types or interfaces that I am using for item components (such as durability and enchantments)
     
  3. karl_jones

    karl_jones

    Unity Technologies

    Joined:
    May 5, 2015
    Posts:
    3,552
  4. DapperDino

    DapperDino

    Joined:
    Feb 18, 2018
    Posts:
    18
    Thanks Karl, I'll have a go with that. But how would you recommend I go about formatting the data in the JSON file? My Item class is currently looking like this:

    Code (CSharp):
    1. [Serializable]
    2.     public class Item : IItem, IDatabaseItem
    3.     {
    4.         [SerializeField] private int id = -1;
    5.         [SerializeField] private string name = "New Item Name";
    6.         [SerializeField] private Sprite icon = null;
    7.         [SerializeField] private List<IItemComponent> components = new List<IItemComponent>();
    8.  
    9.         public int Id => id;
    10.         public string Name => name;
    11.         public Sprite Icon => icon;
    12.         public List<IItemComponent> Components => components;
    13.     }
    IItemComponent being the interface for durability, enchantment, consumable, etc... so I can stay away from inheritance and make the most of composition to allow modular items. As far as I'm aware JsonUtility doesn't work with Interfaces or Abstract classes?
     
  5. karl_jones

    karl_jones

    Unity Technologies

    Joined:
    May 5, 2015
    Posts:
    3,552
    No JSONUtility does not work with interfaces or abstracts, it has the same limitations as our serialization system.
    in 2019.3 we added SerializeReference which does support interfaces. Maybe take a look at that. It should in theory work with JSONUtility although I have not tested it. If it does not then its worth reproting a bug.
    https://forum.unity.com/threads/serializereference-attribute.678868/
     
  6. DapperDino

    DapperDino

    Joined:
    Feb 18, 2018
    Posts:
    18
    Code (CSharp):
    1. {
    2.   "id": -1,
    3.   "name": "New Item Name",
    4.   "icon": {
    5.     "instanceID": 0
    6.   },
    7.   "components": [
    8.     {
    9.       "id": 0
    10.     }
    11.   ],
    12.   "references": {
    13.     "version": 1,
    14.     "00000000": {
    15.       "type": {
    16.         "class": "Durability",
    17.         "ns": "Hel.Items",
    18.         "asm": "GameLogic"
    19.       },
    20.       "data": {
    21.         "maxDurability": 1,
    22.         "currentDurability": 1
    23.       }
    24.     },
    25.     "00000001": {
    26.       "type": {
    27.         "class": "Terminus",
    28.         "ns": "UnityEngine.DMAT",
    29.         "asm": "FAKE_ASM"
    30.       },
    31.       "data": {
    32.       }
    33.     }
    34.   }
    35. }
    36.  
    I tried using JsonUtility with the item class with a test Durability class as an IItemComponent for testing purposes and then logged the json output to the console. Does this mean that all my item files would have to follow this format to be able to be loaded back from file to Unity?
     
  7. karl_jones

    karl_jones

    Unity Technologies

    Joined:
    May 5, 2015
    Posts:
    3,552
    Yes if you want to be able to use the SerializeReference feature.
    We give each instance an id so they can be shared, we then store the instance details with the id.

    The `00000000` and `00000001` is the id. As long as its unique per file then it will be fine
     
  8. DapperDino

    DapperDino

    Joined:
    Feb 18, 2018
    Posts:
    18
    Thanks again for all the help :) Just wondering what the id `00000001` is all about and whether it is necessary (I assume it is)
     
  9. karl_jones

    karl_jones

    Unity Technologies

    Joined:
    May 5, 2015
    Posts:
    3,552
    Yes it wont work without the ids.
    They are mapped across to where the instance is actually used.

    e.g
    Code (csharp):
    1.   "components": [
    2. {
    3.       "id": 0
    4. }
    is mapped to `00000000`.
    All SerializedReference instances are added under a `references` section, each with a unique id. We then map the instances to wherever they are used using those ids. This means that you can now have a single instance referenced by multiple fields and they wont all become seperate instances when its serialized.

    Heres an example of something I have
    Code (csharp):
    1.  
    2.     m_Sources:
    3.     - id: 2
    4.     - id: 3
    5.     - id: 4
    6.     - id: 5
    7.     - id: 6
    8.     m_Formatters:
    9.     - id: 2
    10.     - id: 7
    11.     - id: 8
    12.     - id: 9
    13.     - id: 10
    14.     - id: 11
    15.     - id: 12
    16.     - id: 13
    17.  
    You can see that id 2 is used in 2 seperate lists now.

    And id 2 refers to an instance that also has a reference to id 0.
    Code (csharp):
    1.  
    2.     00000002:
    3.       type: {class: ListFormatter, ns: UnityEngine.Localization.SmartFormat.Extensions,
    4.         asm: Unity.Localization}
    5.       data:
    6.         m_SmartSettings:
    7.           id: 0
    :)
     
  10. DapperDino

    DapperDino

    Joined:
    Feb 18, 2018
    Posts:
    18
    I definitely can see this being a solution to my problem that I might go ahead with but before I dedicate too much time and effort into this I wonder if I could make use of Scriptable Objects.

    Essentially all I want is to easily be able to set up Item data like name, description, price, rarity (rarity being a reference to a separate Scriptable Object storing rarity name, colour, etc...) as well as data that defines how an item works using a list of IItemComponent. That could be consumable, armour, ammunition and so on which would allow me to have multiple item components so I could have a Gingerbread Sword for example that can be used to attack and can also be consumed to restore health.

    I would be fine using scriptable objects right now if my data was completely static but of course some of that data needs to change at runtime. Is there any way at all to clone data from a Scriptable Object to be used at runtime but without the attachment to the Scriptable Object so that when my sword loses durability, the item template SO doesn't lose durability.

    Thanks again in advance
     
  11. karl_jones

    karl_jones

    Unity Technologies

    Joined:
    May 5, 2015
    Posts:
    3,552
    Im not entirely sure I understand. You want to read JSON and spawn ScriptableObject instances?
    If you have a list of IItemComponent that are all different ScriptableObjects then you need a way to know the types when you load the JSON. That does not exist in the JSON as ScriptableObjects are stored as references in the JSON, the asset is itself not stored in it. You could rig something custom to reference other JSON files which contains the ScriptableObject instance data.
    e.g
    Code (csharp):
    1.  
    2. "components" [
    3. {
    4.     "id": "MyScriptableObjectClass:mydata.json"
    5. }]
    Then you could provide a custom load and save
    Code (csharp):
    1.  
    2. class MyReference, ISerializationCallbackReceiver
    3. {
    4.        public string[] components;
    5.    
    6.        Object[] m_Components;
    7.        
    8.  
    9.         public void OnBeforeSerialize()
    10.         {
    11.            foreach(var c in m_Components)
    12.            {
    13.                // Store the component data to json
    14.                // Store the class name and a reference to the json file in `components`
    15.            }
    16.         }
    17.  
    18.         public void OnAfterDeserialize()
    19.         {
    20.             foreach(var c in components)
    21.            {
    22.                // Parse the string, extract the class name and file location.
    23.                var so = ScriptableObject.CreateInstance(className);
    24.                // read the json file and use overrite on so
    25.                
    26.                // Add it to the list of m_Components          
    27.            }
    28.         }
    29. }
    Its not the simplest approach but in theory you could then work with normal ScriptableObject references.
     
  12. DapperDino

    DapperDino

    Joined:
    Feb 18, 2018
    Posts:
    18
    Sorry I could have explained it better. Reading from JSON is just one of my ideas for how I can possibly solve the problem but if I can just Instantiate a scriptable object to use at runtime without altering the asset itself then I think that solves all my problems. Since I am using Odin Inspector (https://odininspector.com/) I can set up the item components directly on the SO without having to mess with JSON.

    I'll have a go at implementing it and I'll get back to you with my results. I'll add screenshots to explain what I mean better.

    Thanks for everything, you've been a huge help!
     
  13. ThermalFusion

    ThermalFusion

    Joined:
    May 1, 2011
    Posts:
    837
    As far as I know you can also instantiate the ScriptableObject in runtime and modify the instance to your liking, without modifying the asset. Not sure if this is something you're looking for.

    Code (CSharp):
    1. [CreateAssetMenu(fileName = "TestSO", menuName = "TestSO", order = 0)]
    2. public class TestSO : ScriptableObject
    3. {
    4.     public float m_SomeFloat = 5f;
    5.     public float m_RuntimeFloat = 0f;
    6.     public float m_RuntimeInt = 1;
    7. }
    Code (CSharp):
    1. public class TestSOUser : MonoBehaviour
    2. {
    3.     public TestSO objectSource;
    4.     public TestSO objectInstance;
    5.  
    6.     void Start()
    7.     {
    8.         objectInstance = Instantiate<TestSO>(objectSource);
    9.         objectInstance.m_RuntimeFloat = objectSource.m_SomeFloat;
    10.     }
    11.  
    12.     void Update()
    13.     {
    14.         if (objectInstance)
    15.         {
    16.             objectInstance.m_RuntimeInt = Mathf.FloorToInt(Time.time);
    17.         }
    18.     }
    19. }
     
  14. DapperDino

    DapperDino

    Joined:
    Feb 18, 2018
    Posts:
    18
    I think that's exactly what I need and I'm in the process of implementing it right now :) Whenever I need to add an item using the SO asset I'll just instantiate it then there shouldn't be any problems (as far as I'm aware haha)
     
    ThermalFusion likes this.
  15. karl_jones

    karl_jones

    Unity Technologies

    Joined:
    May 5, 2015
    Posts:
    3,552
    I was overthinking the problem, I didnt realize you just wanted Instantiate ;)