Lists and shared references

If I add an item to my inventory list twice and try to change a variable in one of them it gets reflected in the other. How can I add these as seperate items so that they don’t reflect each other? As the below code shows I am trying to change equipID to increment when a duplicate item is added to my inventory, but all of the equipIDs for all the duplicates of the item being added also change.

public void AddItemToInventory (int id)
    {
        Item itemToAdd = itemDB.GetItemByID (id);
        if (itemToAdd is Equipment && equipment.Count < equipmentInvSize)
        {
            Equipment e = (Equipment)itemToAdd;
            equipment.Add (e);
            //Set equipID
            int tempID = 0;
            foreach (Equipment instance in equipment)
            {
                if (instance.itemID == e.itemID)
                {
                    tempID++;
                }
            }
            e.equipID = tempID - 1;
            SaveEquipment ();
}

What is it you’re exactly trying to do?

The code you show is… honestly… not the best code I’ve seen. There’s tons of issues that I could foresee resulting from this code.

Nevermind that I don’t know what you mean by “reflected in the other”.

So instead of describing what your literal lines of code are doing. What is the actual task at hand? Can you show us the bulk of primary code that handles that task?

Clone your instance or use structs

Structs are valuetypes and will be copied.
Classes are reference types… no copy they point to the instance in the memory

If you want to know more about it, read a book. Or feel free to ask me.

Kind regards
Spy-Shifty

re: @lordofduct :
I’m going out on a small limb here, based on a previous thread in which I tried to assist.
I believe he is trying to make unique instances of his ‘database of items’. 10 swords could have different xp values earned on them, or something to that effect (that may not be the only example).
… of course, probably better to hear from him to be sure :slight_smile: :slight_smile:

I appreciate the input, and @methos5k is correct in what he says. It’s a similar issue to something I posted prior to this. I’m still new-ish to coding as you can tell. My inventory is functional (the list side of things that is) but I cannot for the life of me figure out how to add the same item more than once, and access them individually. For example, if I buy two of the same weapon, how can I access their variables separately?

I tried to add equipID to counter this problem (as above shows) but you say the code isn’t the best. How would you suggest I improve? I know my inventory system will need a major rework once I figure out how it should be done but could you maybe push me in the right direction? Because another problem stemming from this is linking my inventory to the UI… How does an item in my UI know what item it is? If that makes sense…

I can post code, but there is a lot of it

Show the inventory code, probably be best to see what we’re working with.

Oh no, here goes… Slightly embarrassed to show this mess. It’s become slightly spaghetii’d with recent failed attempts at achieving what I require. If you would like to see any associated scripts please ask.

using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using UnityEngine;
using UnityEngine.UI;
using Newtonsoft.Json;

[System.Serializable]
public class Inventory : MonoBehaviour
{
    public GameObject slot;
    public GameObject slotImage;
    public GameObject temp_destroy_parent;

    public List<Equipment> equipment;
    public List<Resource> resources;
    public List<Consumable> consumables;

    private ItemDatabase itemDB;
    private LoadoutController loadCon;
    private Loadout loadout;
    private EquipmentLevelManager levelManager;
    private GameObject invPanel;
    //Equipment
    private int equipmentInvSize;
    private GameObject equipmentPanel;
    private Transform equipSlotContainer;
    //Resources
    private int resourceInvSize;
    private GameObject resourcePanel;
    private Transform resourceSlotContainer;
    //Consumables
    private int consumableInvSize;
    private GameObject consumablePanel;
    private Transform consumableSlotContainer;
    private GameObject loadoutContainer;
    [HideInInspector]
    public GameObject activeLoadout;

    private void Awake ()
    {
        equipment = new List<Equipment> ();
        resources = new List<Resource> ();
        consumables = new List<Consumable> ();

        itemDB = gameObject.GetComponent<ItemDatabase> ();
        loadCon = gameObject.GetComponent<LoadoutController> ();
        levelManager = gameObject.GetComponent<EquipmentLevelManager> ();
        invPanel = GameObject.Find ("Inventory");
        equipmentInvSize = 20;
        equipmentPanel = GameObject.Find ("EquipmentPanel");
        equipSlotContainer = GameObject.Find ("EquipmentSlots").transform;
        resourceInvSize = 20;
        resourcePanel = GameObject.Find ("ResourcePanel");
        resourceSlotContainer = GameObject.Find ("ResourceSlots").transform;
        consumableInvSize = 20;
        consumablePanel = GameObject.Find ("ConsumablePanel");
        consumableSlotContainer = GameObject.Find ("ConsumableSlots").transform;
        loadoutContainer = GameObject.Find ("LoadoutContainer");

        SyncLoadoutList ();

        var dat = Application.persistentDataPath + "/dat";
        if (!Directory.Exists (dat))
        {
            Directory.CreateDirectory (dat);
        }
        if (File.Exists (Application.persistentDataPath + "/dat/Equipment.json"))
        {
            LoadEquipment ();
        }
        if (File.Exists (Application.persistentDataPath + "/dat/Resources.json"))
        {
            LoadResources ();
        }
        if (File.Exists (Application.persistentDataPath + "/dat/Consumables.json"))
        {
            LoadConsumables ();
        }
    }

    private void Start ()
    {
        if (invPanel != null)
        {
            if (invPanel.activeSelf == true)
            {
                invPanel.SetActive (false);
            }
        }

        CreateSlots ();

        equipmentPanel.SetActive (true);
        resourcePanel.SetActive (false);
        consumablePanel.SetActive (false);

        levelManager.SyncCurrentGear ();
    }

    public void SyncLoadoutList ()
    {
        for (int i = 0; i < loadoutContainer.transform.childCount; i++)
        {
            if (loadoutContainer.transform.GetChild (i).gameObject.activeSelf == true)
            {
                activeLoadout = loadoutContainer.transform.GetChild (i).gameObject;
                loadout = activeLoadout.GetComponent<Loadout> ();
            }
        }
    }

    public void SaveEquipment ()
    {
        var settings = new JsonSerializerSettings ();
        settings.TypeNameHandling = TypeNameHandling.Auto;
        settings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore;

        string json = JsonConvert.SerializeObject (equipment, Formatting.Indented, settings);
        File.WriteAllText (Application.persistentDataPath + "/dat/Equipment.json", json);
    }

    public void LoadEquipment ()
    {
        var settings = new JsonSerializerSettings ();
        settings.TypeNameHandling = TypeNameHandling.Auto;

        string json = File.ReadAllText (Application.persistentDataPath + "/dat/Equipment.Json");
        equipment = JsonConvert.DeserializeObject<List<Equipment>> (json, settings);

        //Assign icons and models
        foreach (Equipment e in equipment)
        {
            e.itemIcon = Resources.Load<Sprite> ("Items/Icons/" + e.itemSlug);
            e.itemModel = Resources.Load<GameObject> ("Items/Models/" + e.itemSlug);
        }

        BuildEquipmentUI ();
    }

    public void SaveResources ()
    {
        var settings = new JsonSerializerSettings ();
        settings.TypeNameHandling = TypeNameHandling.Auto;
        settings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore;

        string json = JsonConvert.SerializeObject (resources, Formatting.Indented, settings);
        File.WriteAllText (Application.persistentDataPath + "/dat/Resources.json", json);
    }

    public void LoadResources ()
    {
        var settings = new JsonSerializerSettings ();
        settings.TypeNameHandling = TypeNameHandling.Auto;

        string json = File.ReadAllText (Application.persistentDataPath + "/dat/Resources.Json");
        resources = JsonConvert.DeserializeObject<List<Resource>> (json, settings);

        //Assign icons and models
        foreach (Resource r in resources)
        {
            r.itemIcon = Resources.Load<Sprite> ("Items/Icons/" + r.itemSlug);
            r.itemModel = Resources.Load<GameObject> ("Items/Models/" + r.itemSlug);
        }

        BuildResourceUI ();
    }

    public void SaveConsumables ()
    {
        var settings = new JsonSerializerSettings ();
        settings.TypeNameHandling = TypeNameHandling.Auto;
        settings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore;

        string json = JsonConvert.SerializeObject (consumables, Formatting.Indented, settings);
        File.WriteAllText (Application.persistentDataPath + "/dat/Consumables.json", json);
    }

    public void LoadConsumables ()
    {
        var settings = new JsonSerializerSettings ();
        settings.TypeNameHandling = TypeNameHandling.Auto;

        string json = File.ReadAllText (Application.persistentDataPath + "/dat/Consumables.Json");
        consumables = JsonConvert.DeserializeObject<List<Consumable>> (json, settings);

        //Assign icons and models
        foreach (Consumable c in consumables)
        {
            c.itemIcon = Resources.Load<Sprite> ("Items/Icons/" + c.itemSlug);
            c.itemModel = Resources.Load<GameObject> ("Items/Models/" + c.itemSlug);
        }

        BuildConsumableUI ();
    }

    private void CreateSlots ()
    {
        //Equipment slots
        for (int i = 0; i < equipmentInvSize; i++)
        {
            if (equipSlotContainer.childCount < equipmentInvSize)
            {
                var _slot = Instantiate (slot);
                _slot.transform.SetParent (equipSlotContainer, false);
                _slot.name = "Slot_" + i;
            }
        }
        //Resources slots
        for (int i = 0; i < resourceInvSize; i++)
        {
            if (resourceSlotContainer.childCount < resourceInvSize)
            {
                var _slot = Instantiate (slot);
                _slot.transform.SetParent (resourceSlotContainer, false);
            }
        }
        //Consumables slots
        for (int i = 0; i < equipmentInvSize; i++)
        {
            if (consumableSlotContainer.childCount < consumableInvSize)
            {
                var _slot = Instantiate (slot);
                _slot.transform.SetParent (consumableSlotContainer, false);
            }
        }
    }

    //Build happens after a load
    public void BuildEquipmentUI ()
    {
        //EquipmentUI
        bool isEquipped = false;
        string loadName = loadCon.SelectedLoadout;
        foreach (Equipment e in equipment)
        {
            switch (loadName)
            {
            case "Load_One":
                isEquipped = e.isEquipped_Load1;
                break;
            case "Load_Two":
                isEquipped = e.isEquipped_Load2;
                break;
            case "Load_Three":
                isEquipped = e.isEquipped_Load3;
                break;
            default:
                break;
            }
            AddItemToUI (e, isEquipped);
        }
    }

    //Called on loadout change to determine which inv items are equipped
    public void RefreshEquipmentUI ()
    {
        foreach (Equipment e in equipment)
        {
            bool isEquipped = false;
            string loadName = loadCon.SelectedLoadout;
            switch (loadName)
            {
            case "Load_One":
                isEquipped = e.isEquipped_Load1;
                break;
            case "Load_Two":
                isEquipped = e.isEquipped_Load2;
                break;
            case "Load_Three":
                isEquipped = e.isEquipped_Load3;
                break;
            default:
                break;
            }
            foreach (Transform slots in equipSlotContainer)
            {
                foreach (Transform slotImage in slots)
                {
                    if (slotImage.GetComponent<Image> ().sprite.name == e.itemSlug)
                    {
                        var equipIcon = slotImage.transform.GetChild (0);
                        if (isEquipped == true)
                        {
                            equipIcon.gameObject.SetActive (true);
                            slotImage.GetComponent<DragHandler> ().enabled = false;
                            slotImage.GetComponent<CanvasGroup> ().blocksRaycasts = false;
                        } else
                        {
                            equipIcon.gameObject.SetActive (false);
                            slotImage.GetComponent<DragHandler> ().enabled = true;
                            slotImage.GetComponent<CanvasGroup> ().blocksRaycasts = true;
                        }
                    }
                }
            }
        }
    }

    public void BuildResourceUI ()
    {
        foreach (Resource r in resources)
        {
            AddItemToUI (r);
        }
    }

    public void BuildConsumableUI ()
    {
        foreach (Consumable c in consumables)
        {
            AddItemToUI (c);
        }
    }

    //Adds to list (from store)
    public void AddItemToInventory (int id)
    {
        Item i = itemDB.GetItemByID (id);
        if (i is Equipment && equipment.Count < equipmentInvSize)
        {
            int tempID = 0;
            Equipment itemToAdd = null;
            if (i is Armor)
            {
                Armor a = (Armor)i;
                itemToAdd = itemDB.Clone (a);
            } else if (i is Weapon)
            {
                Weapon w = (Weapon)i;
                itemToAdd = itemDB.Clone (w);
            }
            equipment.Add (itemToAdd);
            //Set equipID
            foreach (Equipment equips in equipment)
            {
                if (equips.itemID == i.itemID)
                {
                    tempID++;
                }
            }
            Equipment e = (Equipment)itemToAdd;
            e.equipID = tempID - 1;
            SaveEquipment ();
        } else if (i is Resource)
        {
            Resource r = (Resource)i;
            resources.Add (r);
            SaveResources ();
        } else if (i is Consumable)
        {
            Consumable c = (Consumable)i;
            consumables.Add (c);
            SaveResources ();
        }
        AddItemToUI (i);
    }

    //Store equipment and other items
    public void AddItemToUI (Item i)
    {
        var icon = Instantiate (slotImage);
        icon.transform.GetChild (0).gameObject.SetActive (false);
        icon.name = i.itemSlug;
        icon.GetComponent<Image> ().sprite = i.itemIcon;

        Transform emptySlot = null;
        if (i is Equipment)
        {
            emptySlot = GrabNextEmptySlot (equipSlotContainer);
            if (emptySlot == null)
            {
                CreateSlots ();
                emptySlot = GrabNextEmptySlot (equipSlotContainer);
            }
        } else if (i is Resource)
        {
            emptySlot = GrabNextEmptySlot (resourceSlotContainer);
            if (emptySlot == null)
            {
                CreateSlots ();
                emptySlot = GrabNextEmptySlot (resourceSlotContainer);
            }
        } else if (i is Consumable)
        {
            emptySlot = GrabNextEmptySlot (consumableSlotContainer);
            if (emptySlot == null)
            {
                CreateSlots ();
                emptySlot = GrabNextEmptySlot (consumableSlotContainer);
            }
        }

        icon.transform.SetParent (emptySlot.transform, false);
    }

    //Equipment only (Only called on load)
    public void AddItemToUI (Item i, bool isEquipped)
    {
        var icon = Instantiate (slotImage);
        var equipIcon = icon.transform.GetChild (0);
        equipIcon.gameObject.SetActive (false);
        icon.name = i.itemSlug;
        icon.GetComponent<Image> ().sprite = i.itemIcon;

        Transform emptySlot = null;
        if (i is Equipment)
        {
            if (isEquipped == true)
            {
                equipIcon.gameObject.SetActive (true);
                icon.GetComponent<DragHandler> ().enabled = false;
                icon.GetComponent<CanvasGroup> ().blocksRaycasts = false;
            } else if (isEquipped != true)
            {
                equipIcon.gameObject.SetActive (false);
                icon.GetComponent<DragHandler> ().enabled = true;
                icon.GetComponent<CanvasGroup> ().blocksRaycasts = true;
            }
            emptySlot = GrabNextEmptySlot (equipSlotContainer);
            if (emptySlot == null)
            {
                CreateSlots ();
                emptySlot = GrabNextEmptySlot (equipSlotContainer);
            }
        }           
        icon.transform.SetParent (emptySlot.transform, false);
    }

    public void RemoveItemFromInventory (int id, int equipID)
    {
        Equipment itemToRemove = GetEquipByID (id, equipID);
        if (itemToRemove is Equipment)
        {
            Equipment e = (Equipment)itemToRemove;
            equipment.Remove (e);
            SaveEquipment ();
        } //else if (itemToRemove is Resource)
//        {
//            Resource r = (Resource)itemToRemove;
//            resources.Remove (r);
//            SaveResources ();
//        } else if (itemToRemove is Consumable)
//        {
//            Consumable c = (Consumable)itemToRemove;
//            consumables.Remove (c);
//            SaveConsumables ();
//        }
        RemoveItemFromUI (itemToRemove);
    }

    public void RemoveItemFromUI (Item i)
    {
        Transform slotContainer = null;
        if (i is Equipment)
        {
            slotContainer = equipSlotContainer;
        } else if (i is Resource)
        {
            slotContainer = resourceSlotContainer;
        } else if (i is Consumable)
        {
            slotContainer = consumableSlotContainer;
        }
        foreach (Transform t in slotContainer)
        {
            if (t.childCount > 0)
            {
                Transform u = t.GetChild (0);
                if (u.gameObject.GetComponent<Image> ().sprite.name == i.itemSlug) //Destroys first item found, need to link items to items in inventory
                {
                    u.SetParent (temp_destroy_parent.transform);
                    Destroy (u.gameObject);
                    break;
                }
            }
        }
    }

    public void Equip (Item i, Transform slot)
    {
        SyncLoadoutList ();
        Equipment equip = i as Equipment;
        foreach (Transform slots in equipSlotContainer)
        {
            foreach (Transform slotImage in slots)
            {
                if (slotImage.GetComponent<Image> ().sprite.name == i.itemSlug)
                {
                    var equipIcon = slotImage.transform.GetChild (0);
                    equipIcon.gameObject.SetActive (true);
                }
            }
        }
        string loadName = activeLoadout.GetComponent<Loadout> ().loadoutName;
        switch (loadName)
        {
        case "Load_One":
            equip.isEquipped_Load1 = true;
            break;
        case "Load_Two":
            equip.isEquipped_Load2 = true;
            break;
        case "Load_Three":
            equip.isEquipped_Load3 = true;
            break;
        default:
            break;
        }
        loadout.loadoutList.Add (equip);
        SaveEquipment ();
    }

    public void Unequip (Equipment e, Transform gearImage)
    {
        SyncLoadoutList ();
        if (equipment.Count < equipmentInvSize)
        {
            Equipment equip = loadout.GetItemBySlug (e.itemSlug);
            string loadName = activeLoadout.GetComponent<Loadout> ().loadoutName;
            switch (loadName)
            {
            case "Load_One":
                equip.isEquipped_Load1 = false;
                break;
            case "Load_Two":
                equip.isEquipped_Load2 = false;
                break;
            case "Load_Three":
                equip.isEquipped_Load3 = false;
                break;
            default:
                break;
            }
            loadout.loadoutList.Remove (equip);

            foreach (Transform slots in equipSlotContainer)
            {
                foreach (Transform slotImage in slots)
                {
                    if (slotImage.GetComponent<Image> ().sprite.name == e.itemSlug)
                    {
                        var equipIcon = slotImage.transform.GetChild (0);
                        equipIcon.gameObject.SetActive (false);
                        var drag = slotImage.gameObject.GetComponent<DragHandler> ();
                        drag.enabled = true;
                        var block = slotImage.GetComponent<CanvasGroup> ();
                        block.blocksRaycasts = true;

                        Destroy (gearImage.gameObject);
                    }
                }
            }
            SaveEquipment ();
        }
    }

//    public Transform GetItemFromUI (string slug, Transform slotContainer)
//    {
//        Item i = GetItemBySlug (slug);
//        foreach (Transform slot in slotContainer)
//        {
//            Transform item = slot.GetChild (0); //Out of bounds
//
//            if (item.gameObject.GetComponent<Image> ().sprite.name == i.itemName)
//            {
//                return item;
//            }
//        }
//        return null;
//    }

    public Equipment GetEquipByID (int id, int equipID)
    {
        foreach (Equipment e in equipment)
        {
            if (e.itemID == id && e.equipID == equipID)
            {
                return e;
            }
        }
        return null;
    }

    public Item GetItemBySlug (string slug)
    {
        foreach (Equipment e in equipment)
        {
            if (e.itemSlug == slug)
            {
                Item i = (Item)e;
                return i;
            }
        }
        foreach (Resource r in resources)
        {
            if (r.itemSlug == slug)
            {
                Item i = (Item)r;
                return i;
            }
        }
        foreach (Consumable c in consumables)
        {
            if (c.itemSlug == slug)
            {
                Item i = (Item)c;
                return i;
            }
        }
        return null;
    }

    public Transform GrabNextEmptySlot (Transform slotParent)
    {
        foreach (Transform t in slotParent)
        {
            if (t.childCount == 0)
            {
                return t;
            }
        }
        return null;
    }
}

I’ve not tried to write a system where you can modify stats of an individual item, but I have thought about it a few times. I think I would wrap my object in a container class that has any modifiable stats ‘outside’ of the object (in the container)… and the container, is course, also holding the item.

Your design has some significant differences to mine, but in case this could be at all helpful, I once posted some partial code of mine for an inventory: Few samples I made (UI Scaling, inventory, drag + resize panel)

If you want to look at it.

Maybe you can post the Equipment class
To help you really I need more details about class and what itemDb is.

For sure its an ItemDatabase… But is it global, do you store prefab items in it or real objects something that help me / us to understand the full process…

Thank you both for the replies. I will post my equipment code when I get on my computer. I also look forward to testing the project you kindly shared methos5k.

Just a thought I had today though. If I only include variables that don’t need altering after creation (name and icon for example) in the item constructor, I could then have a monobehaviour class called itemData attached to the prefab of the item that contains the variables I wish to be changed. Does this sound practical/ a good way of achieving what I require?

I’m not sure without seeing the code, but what I would guess is happening:

  1. ItemDatabase has a List or Dictionary of Equipment
  2. itemDB.GetItemByID (id) returns the item from the collection rather than a copy of it.
  3. Therefore, all results from GetItemByID with the same id point to the same object

You could just copy it, but depending on how large an Equipment is, it may make more sense to create an EquipmentInstance that has a reference to the base Equipment item plus whatever parameters vary from instance to instance.

That’s kinda what I was thinking…

I believe that having an EquipmentInstance class is the best way for me to go really. I think I would prefer to have all of the variables that differ between the instances grouped together. Due to the way my UI is setup I also think this will make things easier. The only downside to this is the extra data that will need to be serialized and deserialized but that shouldn’t be too much of an issue.

Apologies that I haven’t managed to post any further code for you guys to see. I really to appreciate the help and advice!

i just read another thread (still on the first page) that said you should never use gameobject.find. this is news to me, thought i’d share it: Regarding GameObject.Find · UnityTipsRedux

this actually showed me why my inventory icons were working unreliably. don’t mean to be a parrot, just trying to be helpful and making you aware that there may be better alternatives!