Need help with saving inventory

I’ve been trying to create a save system that can save the items in my inventory and also save the position of where items are since the items can be dropped. With the code below the saving will keep the items in my inventory, but on the first load after saving, if I drop an item from my inventory with the “usebananaX”, it just removes the item from the inventory instead of placing it down.

sorry for the messy code, I have hit it with a hammer about 42 times trying different things

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

//every object that can be interacted with needs to be a prefab even if its a clone of another object

public class Inventory : MonoBehaviour, IDataPersistence
{
    // Array "inventory" with a size of 4 (modifiable)
    public GameObject[] inventory = new GameObject[4];
    public Button[] InventoryButtons = new Button[4];

    public GameObject Player;
    public Transform itemSpawnLocation;
    public GameObject itemToSpawn;

    public TMP_Text howManySticks;
    public int stickCount;
    public int stoneCount;
    public int leafCount;
    public int ironCount;
    public int glassCount;

    public Text invItemTXT;
    public bool canYouUseIt0;   //if true then there is something in "[0]" inventory slot
    public bool canYouUseIt1;
    public bool canYouUseIt2;
    public bool canYouUseIt3;




    public void LoadData(GameData data)
    {
        this.stickCount = data.stickCount;
        this.stoneCount = data.stoneCount;
        this.leafCount = data.leafCount;
        this.ironCount = data.ironCount;
        this.glassCount = data.glassCount;

        // Clear the inventory and UI
        for (int i = 0; i < inventory.Length; i++)
        {
            inventory[i] = null;
            InventoryButtons[i].image.overrideSprite = null;
        }

        // Load saved items into the inventory
        for (int i = 0; i < data.inventoryItems.Count; i++)
        {
            InventoryItemData itemData = data.inventoryItems[i];

            // Load the original prefab from Resources
            GameObject prefab = Resources.Load<GameObject>("Prefabs/" + itemData.prefabPath);
            if (prefab != null)
            {
                GameObject item = prefab;

                // Assign item properties
                InteractionObject interactionObject = item.GetComponent<InteractionObject>();
                interactionObject.itemType = itemData.itemType;

                // Add item to inventory
                inventory[i] = item;
                InventoryButtons[i].image.overrideSprite = item.GetComponent<SpriteRenderer>().sprite;

                // Deactivate item in the scene?
                //item.SetActive(false);
            }
            else
            {
                Debug.LogWarning("Prefab not found for item: " + itemData.prefabPath);
            }
        }
    }

    public void SaveData(ref GameData data)
    {
        data.stickCount = this.stickCount;
        data.stoneCount = this.stoneCount;
        data.leafCount = this.leafCount;
        data.ironCount = this.ironCount;
        data.glassCount = this.glassCount;

        data.inventoryItems.Clear();

        for (int i = 0; i < inventory.Length; i++)
        {
            if (inventory[i] != null)
            {
                InteractionObject interactionObject = inventory[i].GetComponent<InteractionObject>();

                InventoryItemData itemData = new InventoryItemData
                {                    
                    itemID = interactionObject.id,
                    itemName = inventory[i].name,
                    itemType = inventory[i].GetComponent<InteractionObject>().itemType,
                    prefabPath = inventory[i].name, // Use prefab name for lookup
                    position = inventory[i].transform.position,
                    rotation = inventory[i].transform.rotation,
                    quantity = 1
                };

                data.inventoryItems.Add(itemData);
            }
        }
    }


    public void AddItem(GameObject item)
    {
        bool itemAdded = false;
        

        // Finding the first empty slot in inventory
        for (int i = 0; i < inventory.Length; i++)
        {
            if (inventory[i] == null)
            {
                inventory[i] = item;
                Debug.Log(item.name + " was added");
                itemAdded = true;

                //update ui to show item in slot
                InventoryButtons[i].image.overrideSprite = item.GetComponent<SpriteRenderer>().sprite;

                // Do something with the object "DoInteraction" calls to other script with the same function name
                item.SendMessage("DoInteraction", SendMessageOptions.DontRequireReceiver);

                string itemType = item.GetComponent<InteractionObject>().itemType;

                if (itemType == "Stick")
                {
                    Debug.Log("you picked up a stick");
                    stickCount = stickCount + 1;
                }

                else if (itemType == "Stone")
                {
                    stoneCount = stoneCount + 1;
                }

                else if (itemType == "Leaf")
                {
                    leafCount = leafCount + 1;
                }

                else if (itemType == "Glass")
                {
                    glassCount = glassCount + 1;
                }

                else if (itemType == "Iron")
                {
                    ironCount = ironCount + 1;
                }

                item.SetActive(false);

                break;
            }
        }


        // If inventory is full
        if (!itemAdded)
        {
            Debug.Log("Inventory full - item not added!!");
        }
    }

    public bool FindItem(GameObject item)
    {
        for (int i = 0; i < inventory.Length; i++)
        {
            if (inventory[i] == item)
            {
                // We found the item
                return true;
            }
        }
        // We didn't find it
        return false;
    }

    public GameObject FindItemByType(string itemType)
    {
        for (int i = 0; i < inventory.Length; i++)
        {
            if (inventory[i] != null)
            {
                if (inventory[i].GetComponent<InteractionObject>().itemType == itemType)
                {
                    // We found it
                    return inventory[i];
                }
            }
        }
        // We didn't find it
        return null;
    }

    public void RemoveItem(GameObject item)
    {

        for (int i = 0; i < inventory.Length; i++)
        {
            if (inventory[i] == item)
            {
                // If found, remove it
                inventory[i] = null;
                Debug.Log(item.name + " was removed from inventory");

                //remove item from ui
                InventoryButtons[i].image.overrideSprite = null;

                string itemType = item.GetComponent<InteractionObject>().itemType;

                if (itemType == "Stick") 
                {
                    stickCount = stickCount - 1;
                }
                if (itemType == "Stone")
                {
                    stoneCount = stoneCount - 1;
                }
                if (itemType == "Leaf")
                {
                    leafCount = leafCount - 1;
                }
                if (itemType == "Iron")
                {
                    ironCount = ironCount - 1;
                }
                if (itemType == "Glass")
                {
                    glassCount = glassCount - 1;
                }

                

                break;
            }
        }
    }

    public void pickedUpStick()
    {
        Debug.Log("picked up a stick");
        stickCount = stickCount + 1;
    }

    
    public void Update()
    {
        howManySticks.text = stickCount.ToString();
        
        GameObject stick = FindItemByType("Stick");
        GameObject stone = FindItemByType("Stone");
        GameObject leaf = FindItemByType("Leaf");
        GameObject iron = FindItemByType("Iron");
        GameObject glass = FindItemByType("Glass");

        //if material is less than 0 return to 0
        if (stickCount < 0)
        {
            stickCount = 0;
        }

        if (stoneCount < 0)
        {
            stoneCount = 0;
        }

        if (leafCount < 0)
        {
            leafCount = 0;
        }

        if (ironCount < 0)
        {
            ironCount = 0;
        }

        if (glassCount < 0)
        {
            glassCount = 0;
        }




        //checks to see if an item is in a certain slot
        if (inventory[0] != null)   //[i] is the specific slot in this case the first one on the left
        {
            canYouUseIt0 = true;     //sets this value true to use in button ui "useBanana" function
            
        }

        if (inventory[1] != null)   //[i] is the specific slot in this case the first one on the left
        {
            canYouUseIt1 = true;     //sets this value true to use in button ui "useBanana" function
        }

        if (inventory[2] != null)   //[i] is the specific slot in this case the first one on the left
        {
            canYouUseIt2 = true;     //sets this value true to use in button ui "useBanana" function
        }

        if (inventory[3] != null)   //[i] is the specific slot in this case the first one on the left
        {
            canYouUseIt3 = true;     //sets this value true to use in button ui "useBanana" function
        }
    }


    public void UseBanana0()  //if "canYouUseIt" is true then this will run otherwise it won't
    {
        if (canYouUseIt0 && inventory[0] != null)
        {
            GameObject itemToDrop = inventory[0];

            // Remove the item from the inventory
            RemoveItem(itemToDrop);

            // Reactivate and position the original item
            Vector3 position = itemSpawnLocation != null ? itemSpawnLocation.position : transform.position;
            itemToDrop.transform.position = position;
            itemToDrop.SetActive(true);

            Debug.Log(itemToDrop.name + " was dropped");
        }
    }


    public void UseBanana1()  //if "canYouUseIt" is true then this will run otherwise it won't
    {
        if (canYouUseIt1 && inventory[1] != null)
        {
            GameObject itemToDrop = inventory[1];

            // Remove the item from the inventory
            RemoveItem(itemToDrop);

            // Reactivate and position the original item
            Vector3 position = itemSpawnLocation != null ? itemSpawnLocation.position : transform.position;
            itemToDrop.transform.position = position;
            itemToDrop.SetActive(true);

            Debug.Log(itemToDrop.name + " was dropped");
        }
    }

    public void UseBanana2()  //if "canYouUseIt" is true then this will run otherwise it won't
    {
        if (canYouUseIt2 && inventory[2] != null)
        {
            GameObject itemToDrop = inventory[2];

            // Remove the item from the inventory
            RemoveItem(itemToDrop);

            // Reactivate and position the original item
            Vector3 position = itemSpawnLocation != null ? itemSpawnLocation.position : transform.position;
            itemToDrop.transform.position = position;
            itemToDrop.SetActive(true);

            Debug.Log(itemToDrop.name + " was dropped");
        }
    }

    public void UseBanana3()  //if "canYouUseIt" is true then this will run otherwise it won't
    {
        if (canYouUseIt3 && inventory[3] != null)
        {
            GameObject itemToDrop = inventory[3];

            // Remove the item from the inventory
            RemoveItem(itemToDrop);

            // Reactivate and position the original item
            Vector3 position = itemSpawnLocation != null ? itemSpawnLocation.position : transform.position;
            itemToDrop.transform.position = position;
            itemToDrop.SetActive(true);

            Debug.Log(itemToDrop.name + " was dropped");
        }
    }

    private void Start()
    {
        if(DataPersistenceManager.instance == null)
        {
            Debug.LogError("DataPersistenceManager instance is null. Ensure it is initialized");
            return;
        }

        DataPersistenceManager.instance.LoadGame();
    }

    private void OnApplicationQuit()
    {
       
        DataPersistenceManager.instance.SaveGame();
    }


}

DataPersistenceManager script

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Linq;

public class DataPersistenceManager : MonoBehaviour
{
    [Header("File Storage Config")]

    [SerializeField] private string fileName;

    [SerializeField] private bool useEncryption;

    private GameData gameData;

    private List<IDataPersistence> dataPersistenceObjects;

    private FileDataHandler dataHandler;

    public static DataPersistenceManager instance {  get; private set; }

    private void Awake()
    {
        if (instance == null)
        {
            instance = this;
            DontDestroyOnLoad(gameObject);
        }
        else
        {
            Debug.LogError("Found more than one data persistence manager in the scene");
            Destroy(gameObject);
        }
    }

    private void Start()
    {
        this.dataHandler = new FileDataHandler(Application.persistentDataPath, fileName, useEncryption);
        this.dataPersistenceObjects = FindAllDataPersistenceObjects();

        // Try to load game data
        this.gameData = dataHandler.Load();

        // If no data is found, initialize default data
        if (this.gameData == null)
        {
            Debug.Log("No data was found. Initializing to default data.");
            NewGame();
        }

        // Ensure gameData is not null before loading the game
        if (this.gameData == null)
        {
            Debug.LogError("Failed to initialize gameData in Start().");
            return;
        }

        // Now safely load game data into persistence objects
        LoadGame();
    }




    public void NewGame()
    {
        this.gameData = new GameData();
        if (this.gameData == null)
        {
            Debug.LogError("Failed to initialize a new GameData object.");
        }
    }


    public void LoadGame()
    {
        if (gameData == null)
        {
            Debug.LogError("GameData is null in LoadGame(). This should not happen.");
            return;
        }

        foreach (IDataPersistence dataPersistenceObj in dataPersistenceObjects)
        {
            dataPersistenceObj.LoadData(gameData);
        }

        Debug.Log("LOADED sticks = " + gameData.stickCount);
    }



    public void SaveGame()
    {
        if (gameData == null)
        {
            Debug.LogError("Cannot save game because gameData is null.");
            return;
        }

        foreach (IDataPersistence dataPersistenceObj in dataPersistenceObjects)
        {
            dataPersistenceObj.SaveData(ref gameData);
        }

        Debug.Log("Saved sticks = " + gameData.stickCount);
        dataHandler.Save(gameData);
    }


    private void OnApplicationQuit()
    {
        SaveGame();
    }

    private List<IDataPersistence> FindAllDataPersistenceObjects()
    {
        IEnumerable<IDataPersistence> dataPersistenceObjects = FindObjectsOfType<MonoBehaviour>().OfType<IDataPersistence>();

        return new List<IDataPersistence>(dataPersistenceObjects);
    }

}

Inventory Item Data script

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

[System.Serializable]
public class InventoryItemData
{
    public string itemName;
    public string itemType; // e.g., "Stick", "Stone"
    public string prefabPath;
    public string spritePath; // Path to the item's sprite
    public int quantity; // Optional: For stackable items

    

    public string itemID;

    
    public Vector3 position;
    public Quaternion rotation;
}


file data handler script

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
using System.IO;

public class FileDataHandler
{
    private string dataDirPath = "";

    private string dataFileName = "";

    private bool useEncryption = false;
    private readonly string encryptionCodeWord = "BlueJaysLoveCardinals";

    public FileDataHandler(string dataDirPath, string dataFileName, bool useEncryption)
    {
        this.dataDirPath = dataDirPath;
        this.dataFileName = dataFileName;
        this.useEncryption = useEncryption;
    }


    public void Save(GameData data)
    {
        string fullPath = Path.Combine(dataDirPath, dataFileName);
        try
        {
            Directory.CreateDirectory(Path.GetDirectoryName(fullPath));

            string dataToStore = JsonUtility.ToJson(data, true);

            if (useEncryption)
            {
                dataToStore = EncryptDecrypt(dataToStore);
            }

            using (FileStream stream = new FileStream(fullPath, FileMode.Create))
            {
                using (StreamWriter writer = new StreamWriter(stream))
                {
                    writer.Write(dataToStore);
                }
            }
        }
        catch (Exception e)
        {
            Debug.LogError("Error occured when trying to save data to file: " + fullPath + "\n" + e);
        }
    }


    public GameData Load()
    {
        string fullPath = Path.Combine(dataDirPath, dataFileName);
        GameData loadedData = null;

        if(File.Exists(fullPath))
        {
            try
            {
                string dataToLoad = "";

                if (useEncryption)
                {
                    dataToLoad = EncryptDecrypt(dataToLoad);
                }

                using (FileStream stream = new FileStream(fullPath, FileMode.Open))
                {
                    using (StreamReader reader = new StreamReader(stream))
                    {
                        dataToLoad = reader.ReadToEnd();
                    }
                }

                loadedData = JsonUtility.FromJson<GameData>(dataToLoad);

            }
            catch(Exception e)
            {
                Debug.LogError("Error occured when trying to load data from file: " + fullPath + "\n" + e);
            }
        }
        return loadedData;
    }


    private string EncryptDecrypt(string data)
    {
        string modifiedData = "";

        for (int i = 0; i < data.Length; i++)
        {
            modifiedData += (char)(data[i] ^ encryptionCodeWord[i % encryptionCodeWord.Length]);
        }
        return modifiedData;
    }


}

Load/Save steps:

An excellent discussion of loading/saving in Unity3D by Xarbrough:

And another excellent set of notes and considerations by karliss_coldwild:

Loading/Saving ScriptableObjects by a proxy identifier such as name:

When loading, you can never re-create a MonoBehaviour or ScriptableObject instance directly from JSON. Save data needs to be all entirely plain C# data with no Unity objects in it.

The reason is they are hybrid C# and native engine objects, and when the JSON package calls new to make one, it cannot make the native engine portion of the object, so you end up with a defective “dead” Unity object.

Instead you must first create the MonoBehaviour using AddComponent() on a GameObject instance, or use ScriptableObject.CreateInstance() to make your SO, then use the appropriate JSON “populate object” call to fill in its public fields.

If you want to use PlayerPrefs to save your game, it’s always better to use a JSON-based wrapper such as this one I forked from a fellow named Brett M Johnson on github:

Do not use the binary formatter/serializer: it is insecure, it cannot be made secure, and it makes debugging very difficult, plus it actually will NOT prevent people from modifying your save data on their computers.

A good summary / survey of the problem space:

1 Like

In SaveData you have this:

Which means it’s only when you are saving you are creating an instance of a data item. Change your logic to use that data item all throughout because it is the representation of your data, not the GUI!