Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Voting for the Unity Awards are OPEN! We’re looking to celebrate creators across games, industry, film, and many more categories. Cast your vote now for all categories
    Dismiss Notice
  3. Dismiss Notice

Saving unique inventory items in a JSON file

Discussion in 'Scripting' started by Deeblock, May 24, 2018.

  1. Deeblock

    Deeblock

    Joined:
    Feb 14, 2018
    Posts:
    49
    I'm trying to save and load the items a player has in his inventory to and from a JSON file. These items contain individual attributes, such as durability, item count etc. Each item is an instance of a scriptable object (Instantiated as a copy). How can I go about saving these unique item instances? Thanks.
     
  2. jschieck

    jschieck

    Joined:
    Nov 10, 2010
    Posts:
    429
    You can use the JsonUtility on a Serializable class.

    There's lot of way's to do it. But let's look at a simple example I whipped up.

    Essentially you have an Inventory class, that stores a list of items. In this scenario we can have swords and axes. When a new item is created, we give it a unique Guid id. This can then be used to identify this item anywhere else in our code. But for simplicity sake, we just use the first item of that type when we use our item, decreasing it's durability.

    The comments speak to the rest of the example.
    Code (CSharp):
    1.  
    2. using System.Collections.Generic;
    3. using System.IO;
    4. using UnityEngine;
    5.  
    6. [System.Serializable]
    7. public class Item
    8. {
    9.     public enum Type { Sword, Axe }
    10.     // unique id of the item
    11.     // you could assign this to player "slots"
    12.     public System.Guid id;
    13.     // type of item
    14.     public Type type;
    15.     // current durability
    16.     public int durability;
    17.     // item count
    18.     public int count;
    19. }
    20.  
    21. [System.Serializable]
    22. public class Inventory
    23. {
    24.     // where should the inventory be saved?
    25.     private string savePath { get { return Path.Combine(Application.persistentDataPath, "inventory.json"); } }
    26.     // items currently in the inventory
    27.     public List<Item> items;
    28.     public Inventory()
    29.     {
    30.         items = new List<Item>();
    31.     }
    32.     // save the inventory to disk
    33.     public void Save()
    34.     {
    35.         File.WriteAllText(savePath, JsonUtility.ToJson(this));
    36.     }
    37.     // load the inventory from disk
    38.     public void Load()
    39.     {
    40.         if (File.Exists(savePath))
    41.             JsonUtility.FromJsonOverwrite(File.ReadAllText(savePath), this);
    42.     }
    43. }
    44.  
    45. public class Test : MonoBehaviour
    46. {
    47.     Inventory inventory;
    48.  
    49.     void Start()
    50.     {
    51.         // load up our inventory
    52.         inventory = new Inventory();
    53.         inventory.Load();
    54.         // log for debugging
    55.         Debug.LogFormat("Loaded inventory {0}", JsonUtility.ToJson(inventory));
    56.         // add test items if we don't have any
    57.         var sword = inventory.items.Find(q => q.type == Item.Type.Sword);
    58.         if (sword == null)
    59.             PickupItem(Item.Type.Sword);
    60.         var axe = inventory.items.Find(q => q.type == Item.Type.Axe);
    61.         if (axe == null)
    62.             PickupItem(Item.Type.Axe);
    63.         // log for debugging
    64.         Debug.LogFormat("Current inventory {0}", JsonUtility.ToJson(inventory));
    65.     }
    66.     void OnDestroy()
    67.     {
    68.         // save our inventory to disk
    69.         inventory.Save();
    70.     }
    71.     void Update()
    72.     {
    73.         // when we press 1, use a sword
    74.         if (Input.GetKeyDown(KeyCode.Alpha1))
    75.         {
    76.             UseItem(Item.Type.Sword);
    77.         }
    78.         // when we press 2, use an axe
    79.         if (Input.GetKeyDown(KeyCode.Alpha2))
    80.         {
    81.             UseItem(Item.Type.Axe);
    82.         }
    83.     }
    84.     void PickupItem(Item.Type type)
    85.     {
    86.         // create a new item
    87.         Item item = new Item()
    88.         {
    89.             id = System.Guid.NewGuid(),
    90.             type = type,
    91.             durability = 10,
    92.             count = 1,
    93.         };
    94.         // add it to the inventory
    95.         inventory.items.Add(item);
    96.     }
    97.     void UseItem(Item.Type type)
    98.     {
    99.         // find the item of that type in the inventory
    100.         Item item = inventory.items.Find(q => q.type == type);
    101.         if (item == null)
    102.         {
    103.             Debug.LogFormat("Item {0} not in inventory", type);
    104.             return;
    105.         }
    106.         // decrease it durability
    107.         item.durability--;
    108.         if (item.durability <= 0)
    109.         {
    110.             // remove the "broken" item
    111.             inventory.items.Remove(item);
    112.             Debug.LogFormat("Removed from inventory. {0} has broke", type);
    113.         }
    114.         else
    115.         {
    116.             Debug.LogFormat("{0}.durability = {1}", type, item.durability);
    117.         }
    118.     }
    119. }
     
    Lethn likes this.
  3. jschieck

    jschieck

    Joined:
    Nov 10, 2010
    Posts:
    429
    I'm an idiot I didn't read that you said it's a scriptable object. The approach of using JsonUtility still works though, you just can't use Guid's

    Code (CSharp):
    1. using UnityEngine;
    2.  
    3. public class Test : MonoBehaviour
    4. {
    5.     public class ItemTest : ScriptableObject
    6.     {
    7.         [HideInInspector] public long uniqueId;
    8.         public int durability;
    9.     }
    10.  
    11.     void Start()
    12.     {
    13.         // when the item is created
    14.         var originalItem = ScriptableObject.CreateInstance<ItemTest>();
    15.         originalItem.uniqueId = System.DateTime.UtcNow.ToBinary();
    16.         originalItem.durability = 23;
    17.         Debug.Log(originalItem.uniqueId);
    18.         Debug.Log(originalItem.durability);
    19.         // serialize it
    20.         string json = JsonUtility.ToJson(originalItem);
    21.  
    22.         // load it back into another item
    23.         var loadedItem = ScriptableObject.CreateInstance<ItemTest>();
    24.         JsonUtility.FromJsonOverwrite(json, loadedItem);
    25.         Debug.Log(loadedItem.uniqueId);
    26.         Debug.Log(loadedItem.durability);
    27.     }
    28. }
     
    Lethn likes this.
  4. Deeblock

    Deeblock

    Joined:
    Feb 14, 2018
    Posts:
    49
    Hmm each Item is a created asset using CreateAssetMenu. From your example, it appears that each item instance is given a uniqueID? How can I load the item data back though? When I use JSONUtility on a scriptable object instance, it saves them as InstanceIDs (which from my understanding changes), and when I load them back I get a ton of errors.
     
    Last edited: May 25, 2018
  5. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,378
    JsonUtility just uses the built in unity serializer.

    The problem here is that unity will try to save anything that inherits from UnityEngine.Object as a reference rather than as their value. This is why you get these InstanceIDs in your json.

    You get errors when you load it because it can't find said objects anymore.

    This can be super annoying.

    ...

    So. A few things I'm going to cover here on how I personally serialize stuff.

    1) Tokenize anything you're saving
    Personally I don't serialize the object itself directly, instead I create a token of the object to serialize. A token should be a simple object that stores just the state information of the object. This is important because you'll be often re-establishing the state of Unity objects which can't be created in the traditional manner and instead rely on Unity to be created/referenced. So this state token allows us to save state independent of the unity objects.

    How I go about this is that I create a 'ITokenizable' interface that anything that can have a token made of it. It looks something like this:
    Code (csharp):
    1.  
    2.     /// <summary>
    3.     /// Represents an object that handles its own creation of a token of its state.
    4.     ///
    5.     /// A token should be serializable.
    6.     ///
    7.     /// This contract will be respected by com.spacepuppy.Dynamics when trying to receive a StateToken of an object.
    8.     /// If the object implements this, the interface will be used, otherwise a <see cref="com.spacepuppy.Dynamic.StateToken"/> will be used.
    9.     /// </summary>
    10.     public interface ITokenizable
    11.     {
    12.  
    13.         object CreateStateToken();
    14.         void RestoreFromStateToken(object token);
    15.  
    16.     }
    17.  
    https://github.com/lordofduct/space...cepuppyUnityFramework/Dynamic/ITokenizable.cs

    Here's an example of it in action:
    Code (csharp):
    1.  
    2. using UnityEngine;
    3.  
    4. using com.spacepuppy;
    5. using com.spacepuppy.Dynamic;
    6. using com.spacepuppy.Utils;
    7.  
    8. namespace com.mansion.Entities.Inventory
    9. {
    10.  
    11.     [CreateAssetMenu(fileName = "InventoryItem", menuName = "Inventory/FuseInventoryItem")]
    12.     public class FuseInventoryItem : InventoryItem, ITokenizable
    13.     {
    14.  
    15.         #region Fields
    16.      
    17.         #endregion
    18.  
    19.         #region Properties
    20.  
    21.         public bool Blown
    22.         {
    23.             get;
    24.             set;
    25.         }
    26.  
    27.         #endregion
    28.  
    29.         #region Methods
    30.  
    31.         object ITokenizable.CreateStateToken()
    32.         {
    33.             return this.Blown;
    34.         }
    35.  
    36.         void ITokenizable.RestoreFromStateToken(object token)
    37.         {
    38.             this.Blown = ConvertUtil.ToBool(token);
    39.         }
    40.  
    41.         #endregion
    42.  
    43.     }
    44.  
    45. }
    46.  
    Note how this object's state really is only a boolean. It technically has other state information, but it's not of consequence. All I want to maintain is this 'Blown' state. So that's what I returns as my token.

    Note here's an ever more complex one:
    Code (csharp):
    1.  
    2. using UnityEngine;
    3. using System.Collections.Generic;
    4. using System.Linq;
    5.  
    6. using com.spacepuppy;
    7. using com.spacepuppy.Dynamic;
    8. using com.spacepuppy.Project;
    9. using com.spacepuppy.Scenario;
    10. using com.spacepuppy.Utils;
    11.  
    12. namespace com.mansion.Entities.Inventory
    13. {
    14.  
    15.     public class InventoryPouch : SPComponent, ITokenizable
    16.     {
    17.      
    18.         #region Fields
    19.  
    20.         [SerializeField]
    21.         [ReorderableArray]
    22.         [DisableOnPlay]
    23.         private List<InventoryItem> _items = new List<InventoryItem>();
    24.         [System.NonSerialized]
    25.         private ItemCollection _itemColl;
    26.  
    27.         [SerializeField]
    28.         private Trigger _onInventoryChanged;
    29.  
    30.         [System.NonSerialized]
    31.         private IEntity _entity;
    32.      
    33.         #endregion
    34.  
    35.         #region CONSTRUCTOR
    36.  
    37.         protected override void Awake()
    38.         {
    39.             base.Awake();
    40.  
    41.             _entity = IEntity.Pool.GetFromSource<IEntity>(this);
    42.         }
    43.  
    44.         protected override void Start()
    45.         {
    46.             base.Start();
    47.  
    48.             if (!this.IsInitialized) this.InitItems();
    49.         }
    50.  
    51.         protected virtual void InitItems()
    52.         {
    53.             this.IsInitialized = true;
    54.  
    55.             for (int i = 0; i < _items.Count; i++)
    56.             {
    57.                 var item = _items[i];
    58.                 if (item == null)
    59.                 {
    60.                     _items.RemoveAt(i);
    61.                     i--;
    62.                 }
    63.                 else
    64.                 {
    65.                     this.OnItemAdded(item);
    66.                 }
    67.             }
    68.         }
    69.  
    70.         #endregion
    71.  
    72.         #region Properties
    73.  
    74.         public IEntity Entity
    75.         {
    76.             get { return _entity; }
    77.         }
    78.  
    79.         public bool IsInitialized
    80.         {
    81.             get;
    82.             private set;
    83.         }
    84.  
    85.         public ItemCollection Items
    86.         {
    87.             get
    88.             {
    89.                 if (_itemColl == null) _itemColl = new ItemCollection(this);
    90.                 return _itemColl;
    91.             }
    92.         }
    93.  
    94.         public Trigger OnInventoryChanged
    95.         {
    96.             get { return _onInventoryChanged; }
    97.         }
    98.      
    99.         #endregion
    100.  
    101.         #region Methods
    102.  
    103.         protected virtual void OnItemAdded(InventoryItem item)
    104.         {
    105.             if (!this.IsInitialized) this.InitItems();
    106.  
    107.             item.OnItemAddedToInventory(this);
    108.  
    109.             if (_onInventoryChanged.Count > 0) _onInventoryChanged.ActivateTrigger(this, this);
    110.         }
    111.  
    112.         protected virtual void OnItemRemoved(InventoryItem item)
    113.         {
    114.             item.OnItemRemovedFromInventory(this);
    115.  
    116.             if (_onInventoryChanged.Count > 0) _onInventoryChanged.ActivateTrigger(this, this);
    117.         }
    118.  
    119.         protected virtual void OnClearingItems()
    120.         {
    121.             foreach(var item in _items)
    122.             {
    123.                 if (!object.ReferenceEquals(item, null)) item.OnItemRemovedFromInventory(this);
    124.             }
    125.  
    126.             if (_onInventoryChanged.Count > 0) _onInventoryChanged.ActivateTrigger(this, this);
    127.         }
    128.  
    129.         #endregion
    130.  
    131.         #region ITokenizable Interface
    132.  
    133.         public object CreateStateToken()
    134.         {
    135.             //return (from i in _items select i.name).ToArray();
    136.  
    137.             return (from i in _items
    138.                     let tk = (i is ITokenizable) ? (i as ITokenizable).CreateStateToken() : null
    139.                     select (tk != null) ? (object)(new Token(i.name, tk)) : (object)i.name).ToArray<object>();
    140.         }
    141.  
    142.         public void RestoreFromStateToken(object token)
    143.         {
    144.             this.Items.Clear();
    145.  
    146.             var arr = token as object[];
    147.             if (arr == null || arr.Length == 0) return;
    148.  
    149.             var items = Game.Settings.InventorySet;
    150.             if (object.ReferenceEquals(items, null)) return;
    151.          
    152.             foreach(var o in arr)
    153.             {
    154.                 InventoryItem item = null;
    155.                 if (o is string)
    156.                     item = items.GetAsset(o as string) as InventoryItem;
    157.                 else if(o is Token)
    158.                 {
    159.                     var tk = (Token)o;
    160.                     item = items.GetAsset(tk.name) as InventoryItem;
    161.                     if (item is ITokenizable)
    162.                         (item as ITokenizable).RestoreFromStateToken(tk.token);
    163.                 }
    164.  
    165.                 if (!object.ReferenceEquals(item, null))
    166.                     this.Items.Add(item);
    167.             }
    168.         }
    169.      
    170.         #endregion
    171.  
    172.         #region Special Types
    173.  
    174.         public class ItemCollection : IList<InventoryItem>
    175.         {
    176. /** snipped for brevity **/
    177.         }
    178.      
    179.         [System.Serializable]
    180.         private struct Token
    181.         {
    182.             public string name;
    183.             public object token;
    184.  
    185.             public Token(string nm, object tk)
    186.             {
    187.                 this.name = nm;
    188.                 this.token = tk;
    189.             }
    190.         }
    191.  
    192.         #endregion
    193.  
    194.     }
    195.  
    196. }
    197.  
    Note how this one actually contains InventoryItem's like the fuse. So it's effectively nesting tokens.

    2) Don't use the unity serializer, including JsonUtility

    Yep... that thing is garbo IMO.

    My problem with the unity serializer is that it infers the type structure of your serialized objects from the field's of the type you're deserializing to.

    This is why unity doesn't support serializing polymorphic fields... basically it doesn't support inheritance.

    If you have a class called ClassA, and another ClassB which inherits from ClassA. And you serialize a field typed as ClassA, even though it contains a ClassB, it'll serialize/deserialize it as a ClassA losing all information about it being a ClassB. This is also why you can't serialize by interface type.

    Super god damn annoying!

    Thing is there are plenty of json libraries out there that do support it. Like Json.Net and the sort.

    Personally I use mine found here:
    https://github.com/lordofduct/space...ree/master/SPSerialization/Serialization/Json

    With this we can now serialize things typed as 'object' (which is what my ITokenizable interface returns tokens as).

    3) Dealing with unity objects

    Now you may have noticed in my InventoryPouch class that when restoring from a token it accesses a collection called 'InventorySet':

    Code (csharp):
    1.  
    2.         public object CreateStateToken()
    3.         {
    4.             //return (from i in _items select i.name).ToArray();
    5.  
    6.             return (from i in _items
    7.                     let tk = (i is ITokenizable) ? (i as ITokenizable).CreateStateToken() : null
    8.                     select (tk != null) ? (object)(new Token(i.name, tk)) : (object)i.name).ToArray<object>();
    9.         }
    10.  
    11.         public void RestoreFromStateToken(object token)
    12.         {
    13.             this.Items.Clear();
    14.  
    15.             var arr = token as object[];
    16.             if (arr == null || arr.Length == 0) return;
    17.  
    18.             var items = Game.Settings.InventorySet;
    19.             if (object.ReferenceEquals(items, null)) return;
    20.          
    21.             foreach(var o in arr)
    22.             {
    23.                 InventoryItem item = null;
    24.                 if (o is string)
    25.                     item = items.GetAsset(o as string) as InventoryItem;
    26.                 else if(o is Token)
    27.                 {
    28.                     var tk = (Token)o;
    29.                     item = items.GetAsset(tk.name) as InventoryItem;
    30.                     if (item is ITokenizable)
    31.                         (item as ITokenizable).RestoreFromStateToken(tk.token);
    32.                 }
    33.  
    34.                 if (!object.ReferenceEquals(item, null))
    35.                     this.Items.Add(item);
    36.             }
    37.         }
    38.  
    So what I'm actually doing in this thing is serializing the name of my inventory item, and any extra information that it might want to (if it implements ITokenizable).

    When I load the thing, I look up the inventory item by that name.

    This means I need a pool from which to lookup the inventory from. I do this with my QueryableAssetSet:
    Code (csharp):
    1.  
    2. #pragma warning disable 0649 // variable declared but not used.
    3. using UnityEngine;
    4. using System.Collections.Generic;
    5. using System.Linq;
    6.  
    7. using com.spacepuppy.Utils;
    8.  
    9. namespace com.spacepuppy.Project
    10. {
    11.  
    12.     [CreateAssetMenu(fileName = "AssetSet", menuName = "Spacepuppy/Asset Set")]
    13.     public class QueryableAssetSet : ScriptableObject, IAssetBundle
    14.     {
    15.  
    16.         #region Fields
    17.  
    18.         [SerializeField]
    19.         [ReorderableArray()]
    20.         private UnityEngine.Object[] _assets;
    21.  
    22.         private Dictionary<string, UnityEngine.Object> _table;
    23.  
    24.         #endregion
    25.      
    26.         #region Methods
    27.  
    28.         private void SetupTable()
    29.         {
    30.             if (_table == null)
    31.                 _table = new Dictionary<string, Object>();
    32.             else
    33.                 _table.Clear();
    34.  
    35.             for (int i = 0; i < _assets.Length; i++)
    36.             {
    37.                 _table[_assets[i].name] = _assets[i];
    38.             }
    39.         }
    40.  
    41.         public UnityEngine.Object GetAsset(string name)
    42.         {
    43.             if (_table == null) this.SetupTable();
    44.  
    45.             UnityEngine.Object obj;
    46.             if (_table.TryGetValue(name, out obj))
    47.                 return obj;
    48.             else
    49.                 return null;
    50.         }
    51.      
    52.         #endregion
    53.  
    54.         #region IAssetBundle Interface
    55.  
    56.         public bool Contains(string name)
    57.         {
    58.             if (_table == null) this.SetupTable();
    59.  
    60.             return _table.ContainsKey(name);
    61.         }
    62.  
    63.         public bool Contains(UnityEngine.Object asset)
    64.         {
    65.             return System.Array.IndexOf(_assets, asset) >= 0;
    66.         }
    67.  
    68.         public IEnumerable<string> GetAllAssetNames()
    69.         {
    70.             if (_table == null) this.SetupTable();
    71.  
    72.             return _table.Keys;
    73.         }
    74.  
    75.         UnityEngine.Object IAssetBundle.LoadAsset(string name)
    76.         {
    77.             return this.GetAsset(name);
    78.         }
    79.  
    80.         UnityEngine.Object IAssetBundle.LoadAsset(string name, System.Type tp)
    81.         {
    82.             var obj = this.GetAsset(name);
    83.             if (object.ReferenceEquals(obj, null)) return null;
    84.  
    85.             if (TypeUtil.IsType(obj.GetType(), tp)) return obj;
    86.             else return null;
    87.         }
    88.  
    89.         T IAssetBundle.LoadAsset<T>(string name)
    90.         {
    91.             return this.GetAsset(name) as T;
    92.         }
    93.  
    94.         public void UnloadAllAssets()
    95.         {
    96.             if (_table != null) _table.Clear();
    97.             for(int i = 0; i < _assets.Length; i++)
    98.             {
    99.                 Resources.UnloadAsset(_assets[i]);
    100.             }
    101.         }
    102.  
    103.         public void UnloadAsset(UnityEngine.Object asset)
    104.         {
    105.             if(this.Contains(asset))
    106.             {
    107.                 Resources.UnloadAsset(asset);
    108.             }
    109.         }
    110.  
    111.  
    112.  
    113.         public void Dispose()
    114.         {
    115.             Resources.UnloadAsset(this);
    116.         }
    117.  
    118.         #endregion
    119.  
    120.     }
    121. }
    122.  
    https://github.com/lordofduct/space...master/SPProject/Project/QueryableAssetSet.cs

    Which you can see my inventory set in my assets here:
    InventorySet.png
    My GameSettings have a global reference to this for easy access and that's how I look up my inventory items.

    4) Dealing with actually saving

    So I'm not going to go into the code for how I actually save because well... it's a little scattered around since I save so many things.

    But effectively when I save I send out a message to all objects that I'm about to save. That message's event args include a reference to a special container in which they're allowed to stick the values generated by their ITokenizable token's into and include some sort of uid for them for future look up.

    You can see this message dispatch here:
    Code (csharp):
    1.  
    2.         public override void SaveScenario(SaveReason reason, bool saveToDisk)
    3.         {
    4.             var data = Services.Get<ScenarioPersistentData>();
    5.             if (data != null)
    6.             {
    7.                 this.OnSavePersistentData(data);
    8.                 Messaging.Broadcast<IPersistentDataGlobalHandler>((o) => o.OnSave(data, reason));
    9.                 _stateToken = data.CreateStateToken();
    10.             }
    11.  
    12.             if(saveToDisk)
    13.             {
    14.                 var token = new SaveGameToken()
    15.                 {
    16.                     Data = this.SaveToken,
    17.                     Checkpoint = this.Checkpoint
    18.                 };
    19.  
    20.                 token.Save(System.IO.Path.Combine(Application.persistentDataPath, this.GetDefaultSaveFileName()));
    21.             }
    22.         }
    23.  
    This container is then saved to disk.

    And it looks something like this when saved:
    Code (csharp):
    1.  
    2. {
    3.     "@type" : "com.mansion.SaveGameToken",
    4.     "Data" : [
    5.         "com.mansion.ScenarioPersistentData+ValuePair[]",
    6.         {
    7.             "@type" : "com.mansion.ScenarioPersistentData+ValuePair",
    8.             "Id" : "*Ep1*GameState*",
    9.             "Value" : {
    10.                 "@type" : "com.mansion.Scenarios.Episode1.Episode1Controller+Token",
    11.                 "ObjectivesToken" : [
    12.                     "com.mansion.Objective[]"
    13.                 ],
    14.                 "MobsKilled" : 0,
    15.                 "MobsSpawned" : 0,
    16.                 "DocumentsScore" : 0,
    17.                 "Deaths" : 0,
    18.                 "RoundsFired" : 0,
    19.                 "TimesHealed" : 0,
    20.                 "TakeDamage" : 0,
    21.                 "Secrets" : 0,
    22.                 "CurrentTime" : {
    23.                     "@type" : "System.TimeSpan",
    24.                     "_ticks" : 154980000
    25.                 },
    26.                 "Difficulty" : 2,
    27.                 "Inventory" : null
    28.             }
    29.         },
    30.         {
    31.             "@type" : "com.mansion.ScenarioPersistentData+ValuePair",
    32.             "Id" : "08D5A48D1313C2D2",
    33.             "Value" : {
    34.                 "@type" : "com.mansion.Scenarios.Episode1.Ep1_PlayerScenarioPersistentStateController+PlayerStateToken",
    35.                 "Health" : 100,
    36.                 "MaxHealth" : 100,
    37.                 "Inventory" : [
    38.                     "System.Object[]",
    39.                     "KnifeInventoryItem",
    40.                     "FlashlightInventoryItem",
    41.                     "PistolInventoryItem",
    42.                     "ShotgunInventoryItem",
    43.                     "SledgehammerInventoryItem",
    44.                     {
    45.                         "@type" : "com.mansion.Entities.Inventory.InventoryPouch+Token",
    46.                         "name" : "Untracked - FuseGarden",
    47.                         "token" : false
    48.                     },
    49.                     {
    50.                         "@type" : "com.mansion.Entities.Inventory.InventoryPouch+Token",
    51.                         "name" : "Untracked - FuseGreenhouse",
    52.                         "token" : false
    53.                     }
    54.                 ],
    55.                 "AmmoToken" : [
    56.                     "com.mansion.Entities.Weapons.AmmoPouch+ValuePair[]",
    57.                     {
    58.                         "@type" : "com.mansion.Entities.Weapons.AmmoPouch+ValuePair",
    59.                         "Type" : 1,
    60.                         "Quantity" : 100
    61.                     }
    62.                 ],
    63.                 "GunAmmoInClip" : 0,
    64.                 "ImmuneToGrapple" : false,
    65.                 "GunTier" : 0,
    66.                 "GunDamage" : 0,
    67.                 "GunCritPercent" : 0,
    68.                 "Stat_DocumentsRead" : 0,
    69.                 "Stat_Secrets" : 0,
    70.                 "Stat_StepTracker" : false
    71.             }
    72.         }
    73.     ],
    74.     "Checkpoint" : {
    75.         "@type" : "com.mansion.CheckpointState",
    76.         "Scene" : "WR_CodeTest",
    77.         "Position" : {
    78.             "@type" : "UnityEngine.Vector3",
    79.             "x" : -0.607535123825073,
    80.             "y" : -0.0100000500679016,
    81.             "z" : -2.43769550323486
    82.         },
    83.         "Rotation" : {
    84.             "@type" : "UnityEngine.Quaternion",
    85.             "x" : 0,
    86.             "y" : 0.982165813446045,
    87.             "z" : 0,
    88.             "w" : 0.188016876578331
    89.         }
    90.     }
    91. }
    92.  
    You can see the inventory saved here:
    Code (csharp):
    1.  
    2.                 "Inventory" : [
    3.                     "System.Object[]",
    4.                     "KnifeInventoryItem",
    5.                     "FlashlightInventoryItem",
    6.                     "PistolInventoryItem",
    7.                     "ShotgunInventoryItem",
    8.                     "SledgehammerInventoryItem",
    9.                     {
    10.                         "@type" : "com.mansion.Entities.Inventory.InventoryPouch+Token",
    11.                         "name" : "Untracked - FuseGarden",
    12.                         "token" : false
    13.                     },
    14.                     {
    15.                         "@type" : "com.mansion.Entities.Inventory.InventoryPouch+Token",
    16.                         "name" : "Untracked - FuseGreenhouse",
    17.                         "token" : false
    18.                     }
    19.                 ],
    20.  
    Note how it it's just the inventory names except for 2 of them which are specifically those fuses which store some extra data.
     
    NateReese77, eses and Ryiah like this.