Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Items in inventory are treated as the same item...

Discussion in 'Scripting' started by Draconic, Apr 23, 2014.

  1. Draconic

    Draconic

    Joined:
    Oct 12, 2013
    Posts:
    82
    Hi, I have an item class where I create new items. Then, I have an item database script that holds all the items in a list. I also have an inventory, which is added to with:

    inventory.Add(new Item(database.items[x]);

    The Item class has a constructor that takes an item from the item database. So, shouldn't each item that I add be new?
    When there are two items in the inventory that are from the same item in the database, when I change one of them the other one changes. Also, when I change the item in the database, all the inventory items that were made off of that item in the database change as well, even though I created a new instance. I don't know what to do about this, help would be appreciated!
     
  2. angrypenguin

    angrypenguin

    Joined:
    Dec 29, 2011
    Posts:
    15,614
    That depends entirely on what the constructor does. And I suspect that you have a copy-by-reference in there (that's the C# default for any reference type) so you're actually just making wrappers of the original item all the time.

    If you post code someone might be able to help. ;)
     
  3. GarthSmith

    GarthSmith

    Joined:
    Apr 26, 2012
    Posts:
    1,240
    Can you show us your Item class and constructor? I'm guessing that when you make multiple items using data from the database, the multiple items are all referencing the same data instead of making their own copy of the data.
    Code (csharp):
    1.  
    2. itemA.data = database.items[x];
    3. itemB.data = database.items[x];
    4. database.items[x].name = "NewNameForBothItems";
    5.  
    If database.items[x] is a reference type, changing database.items[x] will affect both itemA and itemB.
     
  4. Draconic

    Draconic

    Joined:
    Oct 12, 2013
    Posts:
    82
    Code (csharp):
    1.  
    2. using UnityEngine;
    3. using System.Collections;
    4. using System.Collections.Generic;
    5. [System.Serializable]
    6.  
    7.  
    8. public class Item {
    9.  
    10.     public string itemName;
    11.     public string itemDesc;
    12.    
    13.     public Texture2D itemIcon;
    14.     public Texture3D itemModel;
    15.     public List<ItemFunction> functions;
    16.  
    17.     public Item(Item item){
    18.         functions = item.functions;
    19.         itemDesc = item.itemDesc;
    20.         itemName = item.itemName;
    21.         itemIcon = Resources.Load<Texture2D>("Item Icons/" + itemName);
    22.         itemModel = Resources.Load<Texture3D>("Item Models/"  + itemName);
    23.     }
    24.  
    25.     public Item(string name, string desc){
    26.         functions = new List<ItemFunction>();
    27.         itemDesc = desc;
    28.         itemName = name;
    29.         itemIcon = Resources.Load<Texture2D>("Item Icons/" + name);
    30.         itemModel = Resources.Load<Texture3D>("Item Models/"  + name);
    31.         //do functions.Add inside the item database. This will be after the new Item.
    32.         //each time you create a new item set the item made to a variable, then add the variable.functions.Add();
    33.     }
    34.  
    35.  
    36.     public Item(){
    37.    
    38.     }
    39.  
    40.  
    41.     void Update(){
    42.  
    43.     }
    44. }
    45.  
    Code (csharp):
    1.  
    2. using UnityEngine;
    3. using System.Collections;
    4. using System.Collections.Generic;
    5.  
    6.  
    7. public class ItemDatabase : MonoBehaviour {
    8.     public List<Item> items = new List<Item>();
    9.     public SkillDatabase skillDatabase;
    10.     /* Parameters:
    11.      * string Name, Texture2D Icon, Texture3D Model
    12.      * Functions:
    13.      * functionType function, float magnitude, List<Skill> skillLine
    14.      * */
    15.  
    16.  
    17.     void Start () {
    18.         skillDatabase  = GameObject.FindGameObjectWithTag("Database").GetComponent<SkillDatabase>();
    19.  
    20.         //List<Skill> skillLine = new List<Skill>();
    21.        
    22.        
    23.         //new item
    24.        
    25.  
    26.  
    27.  
    28.  
    29.     }
    30.     void Update(){
    31.  
    32.     }
    33. }
    34.  
    This is the inventory script, sorry it's very long and most of it is drawing the gui.

    Code (csharp):
    1.  
    2. using UnityEngine;
    3. using System.Collections;
    4. using System.Collections.Generic;
    5.  
    6.  
    7.  
    8.  
    9.  
    10. public class Inventory : MonoBehaviour {
    11.     public List<Item> inventory = new List<Item>();
    12.     public List<Item> slots = new List<Item>();
    13.     public Item equippedLeft = new Item();
    14.     public Item equippedRight = new Item();
    15.     public ItemDatabase database;
    16.     public SkillDatabase skillDatabase;
    17.     public bool showingInventory = false;
    18.     public int slotsX = 8;
    19.     public int slotsY = 2;
    20.     public float slotSize = 22;
    21.     public float inventoryX, inventoryY;
    22.     public float startX, startY, inventoryStartX, inventoryStartY, draggedItemStartX, draggedItemStartY, draggedFunctionStartX, draggedFunctionStartY;
    23.     public bool draggingInventory;
    24.     public bool draggingItem;
    25.     public Item selectedItem;
    26.     public int selectedItemIndex;
    27.     public Item draggedItem;
    28.     public int draggedItemIndex;
    29.     public List<ItemFunction> tempFunctions = new List<ItemFunction>();
    30.     //public bool draggingItem = false;
    31.     public GUISkin skin;
    32.     List<Item> tempInventory;
    33.     public ItemFunction tempItemFunction, tempItemFunction2;
    34.  
    35.     //function dragging
    36.     public int draggedFunctionIndex;
    37.     public bool draggingFunction;
    38.     public List<ItemFunction> functionSlots = new List<ItemFunction>();
    39.     public ItemFunction draggedFunction;
    40.  
    41.     //item hovering
    42.     public List<float> itemHoverSize = new List<float>();
    43.     public List<bool> waitUntilOut = new List<bool>();
    44.  
    45.     float Clamp (float value, float min, float max) {
    46.         if(value < min){
    47.             value = min;
    48.         }
    49.         if(value > max){
    50.             value = max;
    51.         }
    52.         return value;
    53.     }
    54.  
    55.  
    56.  
    57.     void Start(){
    58.         inventoryX = Screen.width - slotSize * slotsX;
    59.         inventoryY = Screen.height - slotSize * slotsY;
    60.         showingInventory = true;
    61.     slots.
    62.         //deny change of slots if inventory too full.
    63.         for (int i = 0; i < slotsX * slotsY; i++){
    64.             slots.Add (new Item());
    65.             inventory.Add(new Item());
    66.             itemHoverSize.Add(0);
    67.             waitUntilOut.Add(false);
    68.         }
    69.  
    70.         database  = GameObject.FindGameObjectWithTag("Database").GetComponent<ItemDatabase>();
    71.         skillDatabase  = GameObject.FindGameObjectWithTag("Database").GetComponent<SkillDatabase>();
    72.  
    73.         for(int i = 0; i < database.items.Count; i ++){
    74.             inventory[i] = new Item(database.items[i]);
    75.             inventory[i + database.items.Count] = new Item(database.items[i]);
    76.         }
    77.     }
    78.  
    79.     void DrawItemBox(Item item){
    80.         if(item.itemName != ""){
    81.        
    82.             Rect iconBoxRect = new Rect(inventoryX + slotSize/4, inventoryY - slotSize * (slotsY + item.functions.Count) + slotSize/4, slotSize * 1.5f, slotSize * 1.5f);
    83.             Rect nameRect = new Rect(inventoryX + slotSize * 2, inventoryY - slotSize * (slotsY + item.functions.Count), slotSize * (slotsX - 2), slotSize * 2);
    84.             GUI.Box(iconBoxRect, item.itemIcon);
    85.             GUI.Box(nameRect, item.itemName + "\n\n" + item.itemDesc, skin.GetStyle("Slot"));
    86.             if(item.functions.Count > 0){
    87.                 for(int i = 0; i < item.functions.Count; i ++){
    88.                     Rect functionRect = new Rect(inventoryX + slotSize, inventoryY - item.functions.Count * slotSize + (slotSize * i), slotSize * (slotsX - 1), slotSize);
    89.                     Rect functionDragRect = new Rect(inventoryX, functionRect.y, slotSize, slotSize);
    90.                     GUI.Box(functionRect, GetFunctionBox(item.functions[i]), skin.GetStyle("Slot"));
    91.    
    92.                     GUI.Box(functionDragRect, "", skin.GetStyle("Slot"));
    93.                     Texture2D texture = Resources.Load<Texture2D>("Function Type Icons/"  + item.functions[i].functionType.ToString());
    94.                     GUI.DrawTexture(functionDragRect, texture);
    95.                     if(functionDragRect.Contains(Event.current.mousePosition)){
    96.                         if(Input.GetKeyDown(KeyCode.Mouse1)  !draggingInventory  !draggingItem){
    97.                             draggingFunction = true;
    98.                             draggedFunctionIndex = i;
    99.                             tempFunctions = item.functions;
    100.                             draggedFunction = item.functions[i];
    101.                             startX = Input.mousePosition.x;
    102.                             startY = Input.mousePosition.y;
    103.                             draggedFunctionStartX = functionDragRect.x;
    104.                             draggedFunctionStartY = functionDragRect.y;
    105.                         }
    106.                         else{
    107.                             Rect functionToolTipRect = new Rect(functionRect.x - 3 * slotSize, functionRect.y, slotSize * 2, slotSize);
    108.                             GUI.Box(functionToolTipRect, CreateFunctionToolTip(i), skin.GetStyle("Slot"));
    109.                         }
    110.                        
    111.                     }
    112.                     if(!Input.GetKey(KeyCode.Mouse1)  draggingFunction  functionDragRect.Contains(Event.current.mousePosition) ){
    113.  
    114.                         tempItemFunction = item.functions[i];
    115.                         tempItemFunction2 = item.functions[draggedFunctionIndex];
    116.                         item.functions[draggedFunctionIndex] = tempItemFunction;
    117.                         item.functions[i] = tempItemFunction2;
    118.                         draggingFunction = false;
    119.                     }
    120.                     if(draggingFunction){
    121.                         float draggedFunctionRectY = Clamp (
    122.                             draggedFunctionStartY - (Input.mousePosition.y - startY),
    123.                             inventoryY - item.functions.Count * slotSize,
    124.                             inventoryY - item.functions.Count * slotSize + (slotSize * (item.functions.Count - 1))
    125.  
    126.                             );
    127.                        
    128.                         Rect draggedFunctionRect = new Rect(inventoryX, draggedFunctionRectY, slotSize, slotSize);
    129.                         GUI.Box(draggedFunctionRect, "", skin.GetStyle("Slot"));
    130.                         GUI.DrawTexture(draggedFunctionRect, Resources.Load<Texture2D>("Function Type Icons/"  + draggedFunction.functionType.ToString()));
    131.                     }
    132.  
    133.                 }
    134.             }
    135.         }
    136.     }
    137.  
    138.     string CreateFunctionToolTip(int index){
    139.             if(index == 0){
    140.                 return "Click";
    141.             }
    142.             if(index == 1){
    143.                 return "Shift + Click";
    144.             }
    145.             if(index == 2){
    146.                 return "Ctrl + Click";
    147.             }
    148.             else{
    149.                 return "N/A";
    150.         }
    151.     }
    152.  
    153.     float GetFunctionProf(ItemFunction function){
    154.         if(function.functionSkillLine[0] != ""){
    155.             return skillDatabase.skills[FindSkillWithName(function.functionSkillLine[0])].skillLevel;
    156.         }
    157.         else{
    158.             return 0;
    159.         }
    160.     }
    161.  
    162.     int FindSkillWithName(string name){
    163.         for(int i = 0; i < skillDatabase.skills.Count; i ++){
    164.             if(skillDatabase.skills[i].skillName == name){
    165.                 return i;
    166.                 break;
    167.             }
    168.         }      
    169.         return 0; //all strings that aren't an actual skill return to first skill
    170.     }
    171.  
    172.  
    173.     string GetFunctionBox(ItemFunction function){
    174.         string functionString = "";
    175.         functionString = function.functionType.ToString();
    176.  
    177.             for(int i = 0; i < function.functionSkillLine.Count; i ++){
    178.             functionString += " - " + function.functionSkillLine[i];
    179.         }
    180.         functionString += " - " + GetFunctionProf(function).ToString();
    181.  
    182.         return functionString;
    183.     }
    184.     //RoomAvailable return int, where int is the index of the next room available
    185.  
    186.    
    187.  
    188.     void DrawInventory(){
    189.         int i = 0;
    190.         for(int y = 0; y < slotsY; y ++){
    191.             for(int x = 0; x < slotsX; x ++){
    192.                 itemHoverSize[i] = Clamp(itemHoverSize[i], 0.9f, 1.1f);
    193.                 Rect slotRect = new Rect();
    194.                 slotRect = new Rect(inventoryX + x * slotSize + ((1 - itemHoverSize[i]) * slotSize)/2, inventoryY + y * slotSize + ((1 - itemHoverSize[i]) * slotSize)/2, slotSize * itemHoverSize[i], slotSize * itemHoverSize[i]);
    195.                 if(slots[i] == selectedItem  i == selectedItemIndex){
    196.                     GUI.Box (slotRect, "", skin.GetStyle("SelectedSlot"));
    197.                 }
    198.                 else{
    199.                     GUI.Box (slotRect, "", skin.GetStyle("Slot"));
    200.                 }
    201.                 GUI.Box (slotRect, "");
    202.                 if(!draggingItem){
    203.                     slots[i] = inventory[i];
    204.                 }
    205.                 if(slotRect.Contains(Event.current.mousePosition)  slots[i].itemName != null  !waitUntilOut[i]){
    206.                     itemHoverSize[i] += 0.6f * Time.deltaTime;
    207.                 }
    208.                 else{
    209.                     itemHoverSize[i] -= 0.6f * Time.deltaTime;
    210.                 }
    211.                 if(draggingItem  slotRect.Contains(Event.current.mousePosition)){
    212.                     waitUntilOut[i] = true;
    213.                 }
    214.                 if(waitUntilOut[i]  !slotRect.Contains(Event.current.mousePosition)){
    215.                     waitUntilOut[i] = false;
    216.                 }
    217.  
    218.  
    219.  
    220.                 if(slots[i].itemName != null){
    221.  
    222.                     GUI.DrawTexture(slotRect, slots[i].itemIcon);
    223.                     if(slotRect.Contains(Event.current.mousePosition)){
    224.                         if(Input.GetKeyDown(KeyCode.Mouse0)  !draggingInventory  !draggingItem  !draggingFunction){
    225.                             selectedItem = inventory[i];
    226.                             selectedItemIndex = i;
    227.                         }
    228.  
    229.                     }
    230.                     else{
    231.                         itemHoverSize[i] -= 0.2f * Time.deltaTime;
    232.                     }
    233.                     if(slotRect.Contains(Event.current.mousePosition)  Input.GetKeyDown(KeyCode.Mouse1)  !draggingInventory  !draggingFunction){
    234.                         tempInventory = inventory;
    235.                         draggedItemIndex = i;
    236.                         slots[i] = new Item();
    237.                         draggingItem = true;
    238.                         draggedItem = inventory[i];
    239.                         startX = Input.mousePosition.x;
    240.                         startY = Input.mousePosition.y;
    241.                         draggedItemStartX = slotRect.x;
    242.                         draggedItemStartY = slotRect.y;
    243.                     }
    244.        
    245.  
    246.                 }
    247.                 //make it so if it has changed, dont do anything
    248.                 if(!Input.GetKey(KeyCode.Mouse1)  draggingItem  slotRect.Contains(Event.current.mousePosition) ){
    249.                     if(tempInventory == inventory){
    250.  
    251.                         inventory[draggedItemIndex] = inventory[i];
    252.                         inventory[i] = draggedItem;
    253.                         if(draggedItem == selectedItem){
    254.                             selectedItemIndex = i;
    255.                         }
    256.  
    257.                     }
    258.                     draggingItem = false;
    259.                 }
    260.  
    261.                 i++;
    262.             }
    263.         }
    264.     }
    265.  
    266.     /*void DrawDraggedItem(Item item){
    267.         Rect draggedItemRect = new Rect(
    268.     }*/
    269.  
    270.     void OnGUI(){
    271.  
    272.  
    273.  
    274.         //inventory dragging
    275.         Rect dragRect = new Rect(inventoryX, inventoryY, slotSize * slotsX, slotSize * slotsY);
    276.         if(Input.GetKeyDown(KeyCode.Mouse2)  showingInventory  dragRect.Contains(Event.current.mousePosition)   !draggingItem  !draggingFunction){
    277.             startX = Input.mousePosition.x;
    278.             startY = Input.mousePosition.y;
    279.             inventoryStartX = inventoryX;
    280.             inventoryStartY = inventoryY;
    281.             draggingInventory = true;
    282.         }
    283.         if(Input.GetKeyUp(KeyCode.Mouse2)){
    284.             draggingInventory = false;
    285.         }
    286.         if(draggingInventory){
    287.            
    288.             inventoryX = inventoryStartX + (Input.mousePosition.x - startX);
    289.             inventoryY = inventoryStartY - (Input.mousePosition.y - startY);
    290.             inventoryX = Clamp(inventoryX, 0, Screen.width * (1 - (slotsX * slotSize / Screen.width)));
    291.             inventoryY = Clamp(inventoryY, 0, Screen.height * (1 - (slotsY * slotSize / Screen.height)));
    292.        
    293.         }
    294.  
    295.  
    296.         if(showingInventory){
    297.                     //also make it so if you are dragging an item it doesnt draw inventory so you can take out items from slots
    298.  
    299.  
    300.    
    301.             DrawInventory();
    302.             DrawItemBox(inventory[selectedItemIndex]);
    303.            
    304.         }
    305.  
    306.         if(draggingItem){
    307.             //DrawDraggedItem(draggedItem);
    308.             Rect draggedItemRect = new Rect(draggedItemStartX + (Input.mousePosition.x - startX), draggedItemStartY - (Input.mousePosition.y - startY), slotSize, slotSize);
    309.             //there will be problems with gui.depth if having gui in other scripts
    310.            
    311.             GUI.Box(draggedItemRect, "", skin.GetStyle(""));
    312.             GUI.DrawTexture(draggedItemRect, draggedItem.itemIcon);
    313.            
    314.             //make it so when let go, a texture is instantiated which then goes back to original place
    315.            
    316.         }
    317.     }
    318.  
    319.     void Update(){
    320.         if(Input.GetKeyDown(KeyCode.Q)){
    321.             inventory[0].functions[0].functionType = ItemFunction.functionTypes.Throw;
    322.         }
    323.         if(Input.GetKeyDown(KeyCode.R)){
    324.             database.items[0].functions[0].functionType = ItemFunction.functionTypes.Throw;
    325.         }
    326.  
    327.  
    328.         inventoryX = Clamp(inventoryX, 0, Screen.width * (1 - (slotsX * slotSize / Screen.width)));
    329.         inventoryY = Clamp(inventoryY, 0, Screen.height * (1 - (slotsY * slotSize / Screen.height)));
    330.         /* Example of changing aspects of items
    331.          * if(Input.GetButtonDown("Jump")){
    332.             inventory[0].itemDesc = "it broked";
    333.            }*/
    334.  
    335.         if(Input.GetKeyDown(KeyCode.E)){
    336.             showingInventory = !showingInventory;
    337.         }
    338.     }
    339. }
    340.  
     
    Last edited: Apr 23, 2014
  5. GarthSmith

    GarthSmith

    Joined:
    Apr 26, 2012
    Posts:
    1,240
    "functions" sticks out in your Item(Item) constructor. string is a value type, a List is a reference type!
    Code (csharp):
    1.  
    2.     public Item(Item item) {
    3.          // This way has the new item and old item share the SAME LIST
    4.         // functions = item.functions;
    5.  
    6.         // This way, the new item will have it's own separate copy of the list
    7.         functions = new List<ItemFunction>(item.functions);
    8.  
    WARNING: If ItemFunctions is a reference type, the two lists will reference the same ItemFunctions! This means that if you change one ItemFunction in one list, the second list will also see that change.

    You may want to look into Value vs Reference types, and Shallow Copy vs Deep Copy.
     
  6. Draconic

    Draconic

    Joined:
    Oct 12, 2013
    Posts:
    82
    Thanks! How do you suggest I fix this? I still don't really understand, but I'm looking into value vs reference types now.
     
  7. Draconic

    Draconic

    Joined:
    Oct 12, 2013
    Posts:
    82
    Thanks! I tested changing an items name, and it changed each item individually, regardless if they came from the same item in the database. So, the problem is with the function list. What could I change the list to a value type?
    Code (csharp):
    1.  
    2. using UnityEngine;
    3. using System.Collections;
    4. using System.Collections.Generic;
    5. [System.Serializable]
    6.  
    7. public class ItemFunction{
    8.     public enum functionTypes{
    9.         Attack,
    10.         Consume,
    11.         Equip,
    12.         Throw
    13.     }
    14.     //change enum into string, then doFunction(string);
    15.     public functionTypes functionType;
    16.     public float functionMagnitude;
    17.     public List<string> functionSkillLine;
    18.    
    19.     public ItemFunction(functionTypes type, float magnitude, List<string> skills){
    20.         functionType = type;
    21.         functionMagnitude = magnitude;
    22.         functionSkillLine = skills;
    23.     }
    24.  
    25.     public ItemFunction(){
    26.  
    27.     }
    28. }
    29.  
     
    Last edited: Apr 23, 2014
  8. GarthSmith

    GarthSmith

    Joined:
    Apr 26, 2012
    Posts:
    1,240
    Generally a copy constructor or a Clone() method is written that supports deep copying.
    Code (csharp):
    1.  
    2. /// Copy Constructor in the ItemFunction class
    3. public ItemFunction(ItemFunction functionToCopy) {
    4.   functionType = functionToCopy.functionType;
    5.   functionMagnitude = functionToCopy.functionMagnitude;
    6.   // List is a reference type, so make a copy of the list.
    7.   functionSkillLine = new List<string>(functionToCopy.functionSkillLine);
    8. }
    9.  
    Code (csharp):
    1.  
    2. /// Copy constructor for Item
    3. public Item(Item itemToCopy) {
    4.  
    5.   // We can't just copy the list, we have to copy each
    6.   // ItemFunction inside the list too.
    7.   functions = new List<ItemFunction>();
    8.   foreach (ItemFunction func in itemToCopy.functions) {
    9.     functions.Add(new ItemFunction(func));
    10.   }
    11.  
    12.   itemDesc = itemToCopy.itemDesc;  
    13.   itemName = itemToCopy.itemName;  
    14.   itemIcon = Resources.Load<Texture2D>("Item Icons/" + itemName);
    15.   itemModel = Resources.Load<Texture3D>("Item Models/"  + itemName);
    16. }
    17.  
     
  9. mweldon

    mweldon

    Joined:
    Apr 19, 2010
    Posts:
    109
    Generally the items in your game consist of read-only parameters that are the same for all instances (like base value, weight, size, max durability), and instance parameters that can change like (current durability, upgrades, enchantments). Your item class can contain a reference to your db for the read-only parameters, but the mutable parameters need to be unique per instance and should be initialized to their starting values in the constructor.

    When you serialize your items, i.e. save your game, you need to store the db id as well as all the mutable parameters.

    Another option would be to store all the parameters, read-only and mutable, of the item in the db object and make a clone of item in the constructor. This would work also and maybe makes the code a little simpler, but if you have a lot of read-only parameters, then your Item instances and save games could be much larger than necessary.