Preserving current inventory between scenes?

Hi there

I’m looking for a way to save a player’s current inventory between scenes. Using a tutorial, I created a basci inventory system that is used to log which items a player picks up, display an Icon of that item and keep track of the quantity of said item (for example, a red berry bush may offer a yield of 3 or 5 berries).

This all works well, apart from when I go to the next scene and the inventory dissapears.

Currently, the only data that transfers is the current quantities of each of the inventory items (which I save using playerprefs). But could someone offer me guidance on what may be the best way to preserve the inventory data and repopulate the ‘backpack’ with item icons and values?

I’ve traied various methods (arrays and playerprefs), but I keep getting myself into knots and would really appreciate if anyone has any thoughts?

Thanks in advance (and apologies if my coding is inefficient or sloppy - still learning here so be gentle),
Miss McGeek

Item data script

public class Item : MonoBehaviour {

    public int ID;
    public int quantity;
    public string type;
    public string description;

    public Sprite icon;
    public bool pickedUp;

    // Use this for initialization
    void Start () {
       
    }
   
    // Update is called once per frame
    void Update () {
       
    }
}

Slot data script

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

public class Slot : MonoBehaviour {

    public GameObject item;
    public int ID;
    public string type;
    public string description;
    public Text text;
    public bool empty;
    public Sprite icon;

    private LevelManager theLevelManager;

    // Use this for initialization
    void Start()
    {
        theLevelManager = FindObjectOfType<LevelManager>();
    }

    // Update is called once per frame
    void Update () {
       
    }

    public void UpdateSlot()
    {
        this.GetComponent<Image>().sprite = icon;
        this.GetComponentInChildren<Text>().text = "" + theLevelManager.currentCount;

    }

}

Inventory management script

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

public class Inventory : MonoBehaviour
{

    public bool inventoryEnabled;
    public GameObject inventory;

    private int allSlots;
    private int enabledSlots;
    private GameObject[] slot;
    public GameObject slotHolder;

    public GameObject itemPickedUp;

    private PlayerController thePlayer;
    private LevelManager theLevelManager;

    void Start()
    {

        thePlayer = FindObjectOfType<PlayerController>();
        theLevelManager = FindObjectOfType<LevelManager>();
        allSlots = 16;
        slot = new GameObject[allSlots];

        for (int i = 0; i < allSlots; i++)
        {
            slot[i] = slotHolder.transform.GetChild(i).gameObject;
            if (slot[i].GetComponent<Slot>().item == null)
            {
                slot[i].GetComponent<Slot>().empty = true;
            }
        }
    }

    void Update()
    {
        if (Input.GetKeyDown(KeyCode.B))
        {
            inventoryEnabled = !inventoryEnabled;
        }

        if (inventoryEnabled == true)
        {
            inventory.SetActive(true);
            Time.timeScale = 0;
            //    thePlayer.canMove = false;
        }
        else
        {
            inventory.SetActive(false);
            Time.timeScale = 1f;
            //   thePlayer.canMove = true;
        }
    }

    private void OnTriggerEnter2D(Collider2D other)
    {
        if (other.tag == "Item")
        {
            itemPickedUp = other.gameObject;
            Item item = itemPickedUp.GetComponent<Item>();
            theLevelManager.IncreaseItemCount(itemPickedUp.GetComponent<Item>().ID, itemPickedUp.GetComponent<Item>().quantity);
            AddItem(itemPickedUp, item.ID, item.type, item.description, item.icon, theLevelManager.currentCount);
        }

    }

    void AddItem(GameObject itemObject, int itemID, string itemType, string itemDescription, Sprite itemIcon, int currentCount)
    {
        for (int i = 0; i < allSlots; i++)
        {
            if ((slot[i].GetComponent<Slot>().empty) || (itemPickedUp.GetComponent<Item>().ID == slot[i].GetComponent<Slot>().ID))
            {
                itemObject.GetComponent<Item>().pickedUp = true;
                slot[i].GetComponent<Slot>().item = itemObject;
                slot[i].GetComponent<Slot>().icon = itemIcon;
                slot[i].GetComponent<Slot>().type = itemType;
                slot[i].GetComponent<Slot>().ID = itemID;
                slot[i].GetComponent<Slot>().description = itemDescription;

                itemObject.transform.parent = slot[i].transform;
                itemObject.SetActive(false);

                slot[i].GetComponent<Slot>().UpdateSlot();
                slot[i].GetComponent<Slot>().empty = false;

                return;
            }

        }
    }
}

Level manager script

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

public class LevelManager : MonoBehaviour
{
   
    public PlayerController thePlayer;
    public int currentCount;
   

    public int redBerryCount;
    public int blueberryCount;
    public int cottonCount;

    // Use this for initialization
    void Start()
    {
      
        thePlayer = FindObjectOfType<PlayerController>();

       if (PlayerPrefs.HasKey("RedBerryCount"))
       {
           redBerryCount = PlayerPrefs.GetInt("RedBerryCount");
        }
        if (PlayerPrefs.HasKey("BlueberryCount"))
        {
            blueberryCount = PlayerPrefs.GetInt("BlueberryCount");
        }
        if (PlayerPrefs.HasKey("CottonCount"))
        {
            cottonCount = PlayerPrefs.GetInt("CottonCount");
        }
       
    }

    void Update()
    {
     
    }

        public void IncreaseItemCount(int ID, int quantity)
    {
      
        if (ID == 1)
        {
            redBerryCount = redBerryCount + quantity;
            currentCount = redBerryCount;
            PlayerPrefs.SetInt("RedBerryCount", redBerryCount);

        }
        if (ID == 2)
        {
            blueberryCount = blueberryCount + quantity;
            currentCount = blueberryCount;
            PlayerPrefs.SetInt("BlueberryCount", blueberryCount);
        }
        if (ID == 3)
        {
            cottonCount = cottonCount + quantity;
            currentCount = cottonCount;
            PlayerPrefs.SetInt("CottonCount", cottonCount);
        }


    }
}

Hi @MissMcGeek

If you are going to use MonoBehaviours as your items, these have to be added to GameObjects, so you’ll have to keep/transfer the GameObjects to next scene too, otherwise you’ll lose your inventory items on scene load, when old scene GameObjects disappear.

One way is to create a manager object, that stays alive across scene load, using DontDestroyOnLoad on that object. You’d have to parent the items to this GameObject, when item is added to inventory.

Other way is to use data objects only, which can be put into inventory, that is also in DontDestroyOnLoad object. When you add item from world or drop item to world, you have to manage this, as you would be using data only, plain C# classes, which must be added (as a field) to MonoBehaviour script attached to some pickup container object.

Look into serialization for saving items. You don’t need this always, if you just want to keep items from previous scene. If you want to save game (items and player state for example) and load it later, use serialization.

There are tutorials by Unity and many good YouTube tutorials on all these topics. Unity Answers also has many good answers on all of these topics.

1 Like

Hi all,

Slight update. I tried the dontdestronyonload method, but because the inventory script is attached to the Player, when the next scene loads, the Player is unable to locate the inventory gameobject variables it needs (slotHolder and Inventory).

Rather than having these two items as Public GameObjects, I tried to code the script so that it would search the scene for a GameObject with the tag “Inventory” and set that object instead (GameObject myInventory = GameObject.FindObjectWithTag (“Inventory”). But I continued to get variable loading issues.

I then tried to unpick the code so that the Inventory script was attached to the DontDestroyOnLoad Inventory object (to protect the links). And just have the item pick up part attached to the Player.

This sort of worked, but then I couldn’t get the inventory to display in the second scene (even though I could see that the GameObject and all its data has transferred just fine).

@eses I’ve been researching your other suggestions all week as well. However, I think that my level of coding is so basic, that I’m struggling to implement anything useful.

So I think I may now go back to square one and try to find a different tutorial, but one that has data saving and transfer covered too.

Thanks for your help though @eses . Even though I couldn’t get it to work, I still learned a lot from investigating your suggestions.

MIss McGeek

@MissMcGeek

“…because the inventory script is attached to the Player, when the next scene loads, the Player is unable to locate the inventory gameobject variables it needs (slotHolder and Inventory).”

You don’t have to put your inventory into your player. Player is after all avatar / representation of your player on screen, inventory can just exist in scene.

“So I think I may now go back to square one and try to find a different tutorial, but one that has data saving and transfer covered too.”

I recommend searching Unity Answers, it has many good answers. Try keywords like: Inventory, item system, pickup, item container.

One way to access the inventory is to make it a singleton (or more like static field you can access):

```csharp
*public class Inventory : MonoBehaviour
{
// Singleton, call from any script
public static Inventory instance;

// fill in inspector
public List<Item> items;

// Setup this very simple singleton
private void Awake()
{
    if (instance == null)   
    {
        instance = this;
    }
    else
    {
        Destroy(this.gameObject);
    }
}

}*
```

You can then get the inventory in your player like this:

var inventory = Inventory.instance;

Then you can call your methods in inventory (imaginary examples, didn’t read your code):

inventory.AddItem(sword);

// Or

var item = inventory.GetItemByName("sword");

Also notice you can keep the item as Item (which is just C# data class), which could be:

// Item data
[System.Serializable]
public class Item
{
    public string name;
    public int value;
}

You could then place it in scene like this:

// Item container in world object
public class ItemContainer : MonoBehaviour
{
    [SerializeField] private Item item;

    // Call from elsewhere
    // Take data and put it into inventory
    public Item Data
    {
        get
        {
            return item;
        }
        set
        {
            this.item = value;
        }
    }
}

This way world items are contained ItemContainer MonoBehaviours on game objects, and the Items themselves are just data then stored either in Inventory, in a List or Lists if different types exist, or they reside in container, which you can get by collision, raycast or by whatever means necessary.

You can work out the serialization then as separate issue. Lists you can serialize pretty much as is, if you put them in C# “data” class. Unity has many tutorials on this topic, like this one: Recorded Video Session: Quiz Game 2 - Unity Learn.

Or keep the inventory first from scene to scene, as your inventory is in DontDestroyOnLoad object. That is pretty much all you need.

When picking up items, get the ItemContainer, then get it’s item and add it to your inventory:

var picked = someWorldGameObject.GetComponent<ItemContainer>();

inventory.AddItem(picked.Data);

When you need to add item to world, you need to take the item, put it to instantiated container, then drop it into game world.

1 Like

@eses thank you so much for all of this and taking the time to explain it. I really appreciate your help.

I think part of my problem has been trying to stick closely to the tutorial I followed (which attached the inventory script to the player). I think I may have just solved this tonight (shortly before reading your reply). I uncoupled the inventory script from the player (as you suggested) and attached it to the Inventory Object itself (which has the DontDestroyOnLoad script attached too).

I then moved the Item collision detection portion of the script into the PlayerController script, adjusting slightly so that it called into the inventiry script when necessary:

 theInventory.AddItem(itemPickedUp, item.ID, item.type, item.description, item.icon, theLevelManager.currentCount);

All seems to work perfectly now. Ive also added code to clear the inventory of items and delete all item count prefabs:

for (int i = 0; i < allSlots; i++)
                {
                    if (slot[i] != null)
                    {
                        slot[i].GetComponent<Slot>().item = null;
                        slot[i].GetComponent<Slot>().icon = null;
                        slot[i].GetComponent<Slot>().type = null;
                        slot[i].GetComponent<Slot>().ID = 0;
                        slot[i].GetComponent<Slot>().description = null;
                        slot[i].GetComponent<Slot>().UpdateSlot2();
                        slot[i].GetComponent<Slot>().empty = true;

                      
                    }

.
.
.
public void UpdateSlot2()
    {
        this.GetComponent<Image>().sprite = null;
        this.GetComponentInChildren<Text>().text = "";

    }

My script is probably quite long-winded. But again, for a complete novice, I’m pretty pleased. Only took 6 evenings and a lot of kind advice! This forum and the Unity tutorials have both been incredibly helpful.

Miss McGeek

1 Like