How to serialize & save inventory items

I’m totally new to Unity and please help me figure out how to save & load items that are currently held in the inventory. Basically, I’m trying to serialize & save my game state to a binaryformat file. I know how to save/load variables like int, string, etc., but the inventory has an array w/ images and, after spending a couple of days on this issue, I’m still not quite sure how I should go about this, in fact I’m totally lost. Here are my scripts & any advice would be greatly appreciated. Thanks!

Item script:

using UnityEngine;

[CreateAssetMenu]
public class Item : ScriptableObject
{
    public int itemID;
    public string itemName;
    public Sprite itemSprite;
}

Inventory script to add & remove items to/from the inventory:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class Inventory : MonoBehaviour {

    public static Inventory inventoryS;

    public const int numSlots = 7; 
    public Image[] itemImages = new Image[numSlots];
    public Text[] itemNames = new Text[numSlots];
    public Item[] items = new Item[numSlots];

    public void AddItem(Item itemToAdd){
       
            for (int i = 0; i < items.Length; i++) {

                if (items[i] == null) {
               
                    items[i] = itemToAdd;
                    itemImages[i].sprite = itemToAdd.itemSprite;
                    itemNames[i].text = itemToAdd.itemName;
                    itemImages[i].enabled = true;
                    return;
                }
            }
        }

    public void RemoveItem(Item itemToRemove){

            for (int i = 0; i < items.Length; i++) {

                if (items[i] == itemToRemove) {

                    items[i] = null;
                    itemImages[i].sprite = null;
                    itemNames [i].text = null;
                    itemImages[i].enabled = false;
                    return;
                }
            }
        }   
    }

GameManager script to save & load the game state (persistent throughout the game):

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System; 
using System.Runtime.Serialization.Formatters.Binary;
using System.IO;
using UnityEngine.SceneManagement;

public class GameControl : MonoBehaviour {

    public static GameControl control;

    public float health;

    void Awake(){

        if (control == null) {

            DontDestroyOnLoad (gameObject);
            control = this;

        } else if (control != this) {

            Destroy (gameObject);
        }
    }

    public void Save(){

        BinaryFormatter bf = new BinaryFormatter();
        FileStream file = File.Create (Application.persistentDataPath + "/playerInfo.dat");
        PlayerData data = new PlayerData ();

        data.health = health;

        bf.Serialize (file, data);
        file.Close ();
    }

    public void Load(){

        if (File.Exists (Application.persistentDataPath + "/playerInfo.dat")) {

            BinaryFormatter bf = new BinaryFormatter ();
            FileStream file = File.Open (Application.persistentDataPath + "/playerInfo.dat", FileMode.Open);
            PlayerData data = (PlayerData)bf.Deserialize (file);
            file.Close ();

            health = data.health;
        }
    }

[Serializable] 
class PlayerData{
 public float health; 
}
3 Likes

I hope I am not glossing over anything, but if it were me, in that situation, I would simply save the itemID and the item’s index in my inventory. I can make an educated guess that these are not dynamic items, and retrieving them by their itemID would be pretty easy. Next, just put them back into the inventory at their index.
Just for this situation :slight_smile:

1 Like

Thank you guys!

methos5k, thanks for the input and not at all, I believe you are right on. The items in this case are not dynamic, meaning they are created as scriptableobjects and never change, and they are just put in & out of the inventory slots. I tried placing [System.Serializable] as Ironmax kindly suggested, but then I get an error "serializationException: Type UnityEngine.UI.Image is not marked as Serializable.

When you say save the itemID and item’s index in the inventory, you mean I create itemID int & item index int in the Inventory script and set them in the AddItem function? Sorry about such a basic question–it’s just a bit too high a hurdle for me and please bear with me. Thanks.

Well, I saw that the “Item” class already had an itemID. And figure your inventory must have slot (numbers).
I also saw that you weren’t looking for how to save ‘ints’ - you said you know how… So, I thought this solution would work out. heh. Where you do it is up to you :slight_smile: (Sorry, I’m sleepy)

1 Like

(You don’t need the [System.Serializable] attribute above your MonoBehaviour definition. Unity always serializes MonoBehaviours.)

When Unity serializes a ScriptableObject reference such as:

public Item item;

Or, in your case an array of them:

public Item[] items = new Item[numSlots];

It only records the GUID that points to the ScriptableObject. (Side note: Prior to Unity 5.5, Unity recorded the ephemeral InstanceID, not the GUID.) It doesn’t record the entire contents of the ScriptableObject. So you’re going to have to do a little extra work to save and load your inventory.

You have a bunch of loose Item assets in your project. Your Inventory script is going to need a way to find them at runtime.

Create another ScriptableObject class named ItemDatabase that collects all of these Item assets into an array:

using UnityEngine;
[CreateAssetMenu]
public class ItemDatabase : ScriptableObject
{
    public Item[] items;
}

Create an ItemDatabase asset, and assign all your items to it. (You could write a custom inspector that searches your project and automatically adds all Item assets. That’s probably a topic for a separate thread, though.)

Now, when your Inventory script needs to find an Item, it can search the ItemDatabase. In fact, you could add a helper method named GetItem() to ItemDatabase:

using UnityEngine;
[CreateAssetMenu]
public class ItemDatabase : ScriptableObject
{
    public Item[] items;

    public Item GetItem(int itemID)
    {
        foreach (var item in items)
        {
            if (item != null && item.itemID == itemID) return item;
        }
        return null;
    }
}

Your Inventory script will need to do a little custom serialization to save the itemIDs in its items[ ] array:

public class Inventory : MonoBehaviour, ISerializationCallbackReceiver {

    public static Inventory inventoryS;
    public const int numSlots = 7;
    public Image[] itemImages = new Image[numSlots];
    public Text[] itemNames = new Text[numSlots];
    public Item[] items = new Item[numSlots];

    // Add these:
    [HideInInspector] public int[] itemIDs = new int[numSlots];
    public ItemDatabase itemDatabase; // Assign your ItemDatabase asset.

    public void OnBeforeSerialize() // Copy item IDs from items[] to itemIDs[].
    {
        for (int i = 0; i < numSlots; i++)
        {
            var itemID = (items[i] != null) ? items[i].itemID : -1;
            itemIDs[i] = itemID;
        }
    }

    public void OnAfterDeserialize() // Fill items[] from itemIDs[].
    {
        for (int i = 0; i < numSlots; i++)
        {
            var item = itemDatabase.GetItem(itemIDs[i]);
            if (item == null)
            {
                items[i] = null;
                itemImages[i].sprite = null;
                itemNames[i].text = string.Empty;
                itemImages[i].enabled = false;
            }
            else
            {
                items[i] = item;
                itemImages[i].sprite = item.itemSprite;
                itemNames[i].text = item.itemName;
                itemImages[i].enabled = true;
            }
        }
    }

For brevity, I included a reference to ItemDatabase directly in your Inventory script. It would probably be better instead to have a “manager” script that holds a reference to ItemDatabase that other scripts could use, too.

4 Likes

Thanks guys, again.

Tony, I will definitely try that. Thanks for the detailed explanation. I might be able to work it out, and I understand Unity better now, thanks to you.

If i remove the serializing from my inventory script, i lose some List generic population… So yes you need it… Because maybe not all your scripts are mono. Example i have class called Item that is not mono, but part of a Inventory collection that does use mono

You’re welcome, man.

And IronMax, that makes sense, if you’re using non-MonoBehaviours, for sure :slight_smile:

1 Like

you’re right Ironmax, Item class is not mono and referenced in the inventory. Makes sense, Thanks!

Just another side note: Unity automatically serializes ScriptableObjects as well as MonoBehaviours. But, as Ironmax mentioned, classes that aren’t derived from UnityEngine.Object will need the [System.Serializable] attribute to serialize them.

Great thanks Tony. I’ll keep that in mind for sure.