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

Question Array element returning null, but has data is assigned

Discussion in 'Scripting' started by sleepyfunnyfish, Mar 13, 2023.

  1. sleepyfunnyfish

    sleepyfunnyfish

    Joined:
    Oct 12, 2022
    Posts:
    8
    I'm making an inventory system with drag and drop UI. The manager script has an array that contains the GameObjects of each InventorySlot UI GameObject (so the UI has slots to drag and drop from). When I mark the array with [SerializeField] to view it in the inspector, the array has all the InventorySlot GameObjects like it should, but when I try to use those in a method (ex. InventorySlots[index]) it returns null and the console says it no value was assigned.

    Assignment code:
    Code (CSharp):
    1.  //Make inventory slots array
    2.         inventoryPanel = GameObject.FindGameObjectWithTag("InventoryPanel");
    3.         for (int i = 0; i < inventoryPanel.transform.childCount; i++)
    4.         {
    5.             GameObject newSlot = inventoryPanel.transform.GetChild(i).gameObject;
    6.             newSlot.GetComponent<InventorySlot>().playerInventory = this;
    7.             inventorySlots[i] = newSlot;
    8.             Debug.Log(inventorySlots[i].name);
    9.         }
    Read code:
    Code (CSharp):
    1. GameObject inventorySlot = inventorySlots[index];
     
  2. QuinnWinters

    QuinnWinters

    Joined:
    Dec 31, 2013
    Posts:
    494
    It sounds like something is clearing the array elements before you call on them. Is there anything else that modifies that array?
     
  3. sleepyfunnyfish

    sleepyfunnyfish

    Joined:
    Oct 12, 2022
    Posts:
    8
    This is what I thought but that is not the case. The array is also declared at the start of the script so its available throughout the entire script.

    Also as I mentioned in the original post, the entire array is correctly assigned when viewed in the inspector.
     
  4. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,520
    I'm gonna guess you didn't ACTUALLY do step #1 but you think that you did.

    How to fix a NullReferenceException error

    https://forum.unity.com/threads/how-to-fix-a-nullreferenceexception-error.1230297/

    Three steps to success:
    - Identify what is null <-- any other action taken before this step is WASTED TIME
    - Identify why it is null
    - Fix that

    Either that or you sprinkled the script on more than one GameObject.

    These things (inventory, shop systems, character customization, crafting, etc) are fairly tricky hairy beasts, definitely deep in advanced coding territory.

    They contain elements of:

    - a database of items that you may possibly possess / equip
    - a database of the items that you actually possess / equip currently
    - perhaps another database of your "storage" area at home base?
    - persistence of this information to storage between game runs
    - presentation of the inventory to the user (may have to scale and grow, overlay parts, clothing, etc)
    - interaction with items in the inventory or on the character or in the home base storage area
    - interaction with the world to get items in and out
    - dependence on asset definition (images, etc.) for presentation

    Just the design choices of an inventory system can have a lot of complicating confounding issues, such as:

    - can you have multiple items? Is there a limit?
    - if there is an item limit, what is it? Total count? Weight? Size? Something else?
    - are those items shown individually or do they stack?
    - are coins / gems stacked but other stuff isn't stacked?
    - do items have detailed data shown (durability, rarity, damage, etc.)?
    - can users combine items to make new items? How? Limits? Results? Messages of success/failure?
    - can users substantially modify items with other things like spells, gems, sockets, etc.?
    - does a worn-out item (shovel) become something else (like a stick) when the item wears out fully?
    - etc.

    Your best bet is probably to write down exactly what you want feature-wise. It may be useful to get very familiar with an existing game so you have an actual example of each feature in action.

    Once you have decided a baseline design, fully work through two or three different inventory tutorials on Youtube, perhaps even for the game example you have chosen above.

    Breaking down a large problem such as inventory:

    https://forum.unity.com/threads/weapon-inventory-and-how-to-script-weapons.1046236/#post-6769558

    If you want to see most of the steps involved, make a "micro inventory" in your game, something whereby the player can have (or not have) a single item, and display that item in the UI, and let the user select that item and do things with it (take, drop, use, wear, eat, sell, buy, etc.).

    Everything you learn doing that "micro inventory" of one item will apply when you have any larger more complex inventory, and it will give you a feel for what you are dealing with.

    Breaking down large problems in general:

    https://forum.unity.com/threads/opt...n-an-asteroid-belt-game.1395319/#post-8781697
     
    LuckyMisterSleven likes this.
  5. sleepyfunnyfish

    sleepyfunnyfish

    Joined:
    Oct 12, 2022
    Posts:
    8
    didnt ask.
     
  6. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    7,610
    I mean they posted how to solve a null reference, which is literally your problem.

    Go through their process of working out how to solve a null reference. You'll be surprised at how effective it is.
     
  7. sleepyfunnyfish

    sleepyfunnyfish

    Joined:
    Oct 12, 2022
    Posts:
    8
    bro was passive aggressive then dumps unrelated info. i already went through the process as i said in the original post
     
  8. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,520
    bro is going to get some popcorn and watch.

    bro already told you directly (not passively-aggressively) what the problem is and it is what you reported:

    - you say "it" isn't null
    - the computer says it is

    Hundred to one odds that what you think is "it" isn't what is null.

    Hence, Step #1 not completed.

    GL!
     
  9. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    7,610
    It's useful information so you'd be doing yourself a disservice by not heeding the advice.

    And every null-ref is the same, 100% of the time. You have something there that you expect to be present, but it isn't present. Computers don't lie. There is either a logic bug in your code or something has not been set up correctly in your project.

    It could be as simple as an extra component you didn't realise was present.

    We can't answer this with the two tiny slices of out-of-context code provided. You'll need to go through your code and project with judicious use of Debug.Log (don't forget about its second overload) and figure out where the error lies.
     
    Kurt-Dekker likes this.
  10. sleepyfunnyfish

    sleepyfunnyfish

    Joined:
    Oct 12, 2022
    Posts:
    8
    Did more debugging, I was right. It is not null.

    The issue occurs when calling my UpdateUI method (the one which the array elements return null in) from within any other method that reads data as well. Only a certain number of the elements in the data are actually read (always 6) instead of the full 24 again.

    My best guess is this is a unity limitation. When calling the UpdateUI method from my TryAddItem method, it searches the existing 24 elements for a matching item to stack with or an open slot if the new item does not stack.

    As I said this may be a unity limitation since the first 24 searched and the next 6 amount to 30, maybe only 30 searches can be done in one method?

    Really not sure, none of it makes sense.
     
  11. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    7,610
    There is no such limitation. There is a flaw in your code. Do more debugging until you find it.
     
    Kurt-Dekker likes this.
  12. BABIA_GameStudio

    BABIA_GameStudio

    Joined:
    Mar 31, 2020
    Posts:
    497
    You're going to have to provide all of your script code, and not just snippets like before. We would need to see exactly how all of these interact, along with all of your debugging statements for comparison.
    There is not a limitation like that otherwise arrays would be useless if they could go above such a small number of entries. It is more likely you have some logic somewhere (in a spot that you think is unrelated) that is potentially changing the contents of the array to only be 6 items rather than 30 (I'm assuming that is what you meant in your last post about this).
    You are going to have to keep debugging now that you have found that the array is changing, so you should be able to track down where it is being changed from.
     
  13. sleepyfunnyfish

    sleepyfunnyfish

    Joined:
    Oct 12, 2022
    Posts:
    8
    Below is the entire script. I altered it since last post to reduce ANY potential issues. I checked the entire solution for any references to the inventorySlots[] array and the only time is one client start, when assigning the InventorySlot GameObjects to the array. The GameObjects are UI elements which act as containers for the inventory to drag and drop from.

    The issue has not changed. When running UpdateInventoryUI from inside TryGetItem or AddItemToInventory, the inventorySlots[] array returns null no matter what index is used. When called from any other method like the Update KeyDown event or OnStartClient, it runs correctly with no errors.

    Nothing in the array is changed any any point between these times. No matter which order you do either TryAddItem, AddItemToInventory, or KeyDown event in, each still functions as I've mentioned above. I checked the FishNet documentation and this is not a limitation of it, I have also changed the script to monobehavior and the error persisted.

    At this point I'm convinced its a bug with Unity.

    Code (CSharp):
    1. using FishNet.Object;
    2. using System;
    3. using System.Collections.Generic;
    4. using UnityEngine;
    5.  
    6. public class PlayerInventory : NetworkBehaviour
    7. {
    8.     public List<InventoryObject> inventoryObjects = new();
    9.     public List<InventoryObject> equipmentObjects = new();
    10.     public List<InventoryObject> weaponsObjects = new();
    11.  
    12.     GameObject inventoryPanel;
    13.     GameObject equipmentPanel;
    14.     GameObject weaponsPanel;
    15.  
    16.     //public ItemData EmptyObject;
    17.  
    18.     public InventoryItem inventoryItemPrefab;
    19.  
    20.     public int inventorySpace = 24;
    21.     [Serialize Field] GameObject[] inventorySlots = new GameObject[24];
    22.     public int equipmentSpace = 5;
    23.     GameObject[] equipmentSlots = new GameObject[5];
    24.     public int weaponsSpace = 2;
    25.     GameObject[] weaponsSlots = new GameObject[2];
    26.  
    27.     public override void OnStartClient()
    28.     {
    29.         base.OnStartClient();
    30.         if (!base.IsOwner)
    31.         {
    32.             enabled = false;
    33.             return;
    34.         }
    35.         //Make inventory slots array
    36.         inventoryPanel = GameObject.FindGameObjectWithTag("InventoryPanel");
    37.         for (int i = 0; i < inventoryPanel.transform.childCount; i++)
    38.         {
    39.             GameObject newSlot = inventoryPanel.transform.GetChild(i).gameObject;
    40.             Debug.Log(newSlot);
    41.             newSlot.GetComponent<InventorySlot>().playerInventory = this;
    42.             inventorySlots[i] = newSlot;
    43.             Debug.Log(inventorySlots[i].name);
    44.         }
    45.         //Make equipment slots array
    46.         equipmentPanel = GameObject.FindGameObjectWithTag("EquipmentPanel");
    47.         for (int i = 0; i < equipmentPanel.transform.childCount; i++)
    48.         {
    49.             GameObject newSlot = equipmentPanel.transform.GetChild(i).gameObject;
    50.             newSlot.GetComponent<InventorySlot>().playerInventory = this;
    51.             equipmentSlots[i] = newSlot;
    52.         }
    53.         //Make weapon slots array
    54.         weaponsPanel = GameObject.FindGameObjectWithTag("WeaponsPanel");
    55.         for (int i = 0; i < weaponsPanel.transform.childCount; i++)
    56.         {
    57.             GameObject newSlot = weaponsPanel.transform.GetChild(i).gameObject;
    58.             newSlot.GetComponent<InventorySlot>().playerInventory = this;
    59.             weaponsSlots[i] = newSlot;
    60.         }
    61.         /*
    62.          * check for existing player data and get inventory here
    63.          */
    64.         //Assign empty inventory
    65.         /*
    66.         for (int i = 0; i < inventorySpace; i++)
    67.         {
    68.             if (inventoryObjects.Count < inventorySpace)
    69.             {
    70.                 inventoryObjects.Add(new InventoryObject() { inventoryItem = EmptyObject, count = 1 });
    71.             }
    72.             else break;
    73.         }
    74.         for (int i = 0; i < equipmentSpace; i++)
    75.         {
    76.             if (equipmentObjects.Count < equipmentSpace)
    77.             {
    78.                 equipmentObjects.Add(new InventoryObject() { inventoryItem = EmptyObject, count = 1 });
    79.             }
    80.             else break;
    81.         }
    82.         for (int i = 0; i < weaponsSpace; i++)
    83.         {
    84.             if (weaponsObjects.Count < weaponsSpace)
    85.             {
    86.                 weaponsObjects.Add(new InventoryObject() { inventoryItem = EmptyObject, count = 1 });
    87.             }
    88.             else break;
    89.         }
    90.         */
    91.         UpdateInventoryUI();
    92.     }
    93.  
    94.     private void Update()
    95.     {
    96.         if (Input.GetKeyDown("a"))
    97.         {
    98.             UpdateInventoryUI();
    99.         }
    100.     }
    101.  
    102.  
    103.     public bool TryAddItem(ItemData newItem)
    104.     {
    105.         bool openSlot = GetOpenSpace("Inventory") > 0;
    106.         foreach (InventoryObject inventoryObject in inventoryObjects)
    107.         {
    108.             if (newItem.stackable == true) //Check if stackable
    109.             {
    110.                 if (inventoryObject.inventoryItem == newItem //Check if item is owned and not max stacks
    111.                     && inventoryObject.count < inventoryObject.inventoryItem.maxStackCount)
    112.                 {
    113.                     inventoryObject.count++; //Increment
    114.                     UpdateInventoryUI();
    115.                     return true;
    116.                 }
    117.             }
    118.         }
    119.         if (openSlot == true) //Add item to open slot
    120.         {
    121.             AddNewItemToInventory(newItem);
    122.             return true;
    123.         }
    124.         else //no space
    125.         {
    126.             Debug.Log("no space");
    127.         }
    128.         return false;
    129.     }
    130.  
    131.  
    132.     public void AddNewItemToInventory(ItemData item)
    133.     {
    134.         inventoryObjects.Add(new InventoryObject() { inventoryItem = item, count = 1 });
    135.         UpdateInventoryUI();
    136.     }
    137.  
    138.     public void RemoveItemFromInventory(int index)
    139.     {
    140.         inventoryObjects.RemoveAt(index);
    141.         UpdateInventoryUI();
    142.     }
    143.  
    144.     public void UpdateInventoryUI()
    145.     {
    146.         //Update inventory
    147.         foreach (InventoryObject inventoryObject in inventoryObjects)
    148.         {
    149.             int index = inventoryObjects.IndexOf(inventoryObject);
    150.             GameObject inventorySlot = inventorySlots[index];
    151.             if (inventorySlot.transform.childCount > 0)
    152.             {
    153.                 Destroy(inventorySlot.transform.GetChild(0).gameObject);
    154.             }
    155.             //Add InventoryItem to slot
    156.             InventoryItem inventoryItem = Instantiate(inventoryItemPrefab, inventorySlot.transform);
    157.             inventoryItem.item = inventoryObject.inventoryItem;
    158.             inventoryItem.count = inventoryObject.count;
    159.         }
    160.         //Remove empty slots
    161.         int emptyInventorySlots = GetOpenSpace("Inventory");
    162.         for (int i = 0; i < emptyInventorySlots - 1; i++)
    163.         {
    164.             int emptyIndex = inventoryObjects.Count + (i + 1);
    165.             GameObject inventorySlot = inventorySlots[emptyIndex];
    166.             if (inventorySlot.transform.childCount > 0)
    167.             {
    168.                 Destroy(inventorySlot.transform.GetChild(0).gameObject);
    169.             }
    170.         }
    171.         //Update equipment
    172.         foreach (InventoryObject inventoryObject in equipmentObjects)
    173.         {
    174.             int index = equipmentObjects.IndexOf(inventoryObject);
    175.             GameObject inventorySlot = equipmentSlots[index];
    176.             if (inventorySlot.transform.childCount > 0)
    177.             {
    178.                 Destroy(inventorySlot.transform.GetChild(0).gameObject);
    179.             }
    180.             //Add InventoryItem to slot
    181.             InventoryItem inventoryItem = Instantiate(inventoryItemPrefab, inventorySlot.transform);
    182.             inventoryItem.item = inventoryObject.inventoryItem;
    183.             inventoryItem.count = inventoryObject.count;
    184.         }
    185.         //Remove empty slots
    186.         int emptyEquipmentSlots = GetOpenSpace("Equipment");
    187.         for (int i = 0; i < emptyEquipmentSlots; i++)
    188.         {
    189.             int emptyIndex = inventoryObjects.Count + (i + 1);
    190.             GameObject inventorySlot = inventorySlots[emptyIndex];
    191.             if (inventorySlot.transform.childCount > 0)
    192.             {
    193.                 Destroy(inventorySlot.transform.GetChild(0).gameObject);
    194.             }
    195.         }
    196.         //Update weapons
    197.         foreach (InventoryObject inventoryObject in weaponsObjects)
    198.         {
    199.             int index = weaponsObjects.IndexOf(inventoryObject);
    200.             GameObject inventorySlot = weaponsSlots[index];
    201.             if (inventorySlot.transform.childCount > 0)
    202.             {
    203.                 Destroy(inventorySlot.transform.GetChild(0).gameObject);
    204.             }
    205.             //Add InventoryItem to slot
    206.             InventoryItem inventoryItem = Instantiate(inventoryItemPrefab, inventorySlot.transform);
    207.             inventoryItem.item = inventoryObject.inventoryItem;
    208.             inventoryItem.count = inventoryObject.count;
    209.         }
    210.         //Remove empty slots
    211.         int emptyWeaponsSlots = GetOpenSpace("Weapons");
    212.         for (int i = 0; i < emptyWeaponsSlots; i++)
    213.         {
    214.             int emptyIndex = inventoryObjects.Count + (i + 1);
    215.             GameObject inventorySlot = inventorySlots[emptyIndex];
    216.             if (inventorySlot.transform.childCount > 0)
    217.             {
    218.                 Destroy(inventorySlot.transform.GetChild(0).gameObject);
    219.             }
    220.         }
    221.     }
    222.  
    223.     public int GetOpenSpace(string inventoryType)
    224.     {
    225.         List<InventoryObject> inventoryList = null;
    226.         int maxSpace = 0;
    227.         switch (inventoryType)
    228.         {
    229.             case "Inventory":
    230.                 inventoryList = inventoryObjects;
    231.                 maxSpace = inventorySpace;
    232.                 break;
    233.             case "Equipment":
    234.                 inventoryList = inventoryObjects;
    235.                 maxSpace = equipmentSpace;
    236.                 break;
    237.             case "Weapons":
    238.                 inventoryList = inventoryObjects;
    239.                 maxSpace = weaponsSpace;
    240.                 break;
    241.         }
    242.         int openSpace = maxSpace - inventoryList.Count;
    243.         return openSpace;
    244.     }
    245.  
    246.     [System.Serializable]
    247.     public class InventoryObject
    248.     {
    249.         public ItemData inventoryItem;
    250.         public int count;
    251.     }
    252. }
    253.  
    254.         int openSpace = maxSpace - inventoryList.Count;
    255.         return openSpace;
    256.     }
    257.  
    258.     [System.Serializable]
    259.     public class InventoryObject
    260.     {
    261.         public ItemData inventoryItem;
    262.         public int count;
    263.     }
    264. }
     
  14. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    7,610
    I see huge amounts of repeated code that could potentially contain any number of bugs.

    It's also important to note that when you
    Object.Destroy()
    game objects, they aren't destroyed until the end of the current frame. You're iterating via
    transform.childCount
    a lot after destroying objects, so there's a lot of chance you're iterating through more or less transform than you expected.

    It's better not to rely on this, but instead manage your own references to whatever transform you care about. These should be referenced via a pertinent component on these game objects, rather than just their transform too. I see a lot of behaviour that could be encapsulated into their own components, too, to reduce the bulk of this code.

    Judicious use of
    Debug.Log
    probably would've revealed this to you.
     
  15. Bunny83

    Bunny83

    Joined:
    Oct 18, 2010
    Posts:
    3,919
    In almost all cases when you have a situation that the "same variable" has one value in one case and magically a different value in another case, the reason is usually that you're not working with the same instance. Common issues are that you may have a prefab of your object and the actual instance in the scene. You can call methods on the prefab as well, though that's a separate instance which is most likely not initialized. Try adding some Debug.Log statements with a context argument. That way you can identify the actual object by simply clicking on the log message.

    Code (CSharp):
    1. Debug.Log("something", this);
    Use something like this to figure out what actual object(s) you're working with.
     
    spiney199 likes this.
  16. sleepyfunnyfish

    sleepyfunnyfish

    Joined:
    Oct 12, 2022
    Posts:
    8
    Did this, when the array elements would return it was the correct instance.
     
  17. sleepyfunnyfish

    sleepyfunnyfish

    Joined:
    Oct 12, 2022
    Posts:
    8
    Every use of transform.childcount and gameobject.destroy is on a different object and only one gameobject should exist under an inventoryslot at a time so this shouldnt be an issue. the repeated code shouldnt matter, reducing it to a method with a param like the GetOpenSpace method wouldnt change anything

    Additionally, when executing from TryAddItem or AddItemToInventory, it does not even get this far because of the unassigned error occuring at the start of the UpdateInventoryUI method, so this is not the issue at all.