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:
//Make inventory slots array
inventoryPanel = GameObject.FindGameObjectWithTag("InventoryPanel");
for (int i = 0; i < inventoryPanel.transform.childCount; i++)
{
GameObject newSlot = inventoryPanel.transform.GetChild(i).gameObject;
newSlot.GetComponent<InventorySlot>().playerInventory = this;
inventorySlots[i] = newSlot;
Debug.Log(inventorySlots[i].name);
}
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:
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.
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.
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?
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.
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.
using FishNet.Object;
using System;
using System.Collections.Generic;
using UnityEngine;
public class PlayerInventory : NetworkBehaviour
{
public List<InventoryObject> inventoryObjects = new();
public List<InventoryObject> equipmentObjects = new();
public List<InventoryObject> weaponsObjects = new();
GameObject inventoryPanel;
GameObject equipmentPanel;
GameObject weaponsPanel;
//public ItemData EmptyObject;
public InventoryItem inventoryItemPrefab;
public int inventorySpace = 24;
[Serialize Field] GameObject[] inventorySlots = new GameObject[24];
public int equipmentSpace = 5;
GameObject[] equipmentSlots = new GameObject[5];
public int weaponsSpace = 2;
GameObject[] weaponsSlots = new GameObject[2];
public override void OnStartClient()
{
base.OnStartClient();
if (!base.IsOwner)
{
enabled = false;
return;
}
//Make inventory slots array
inventoryPanel = GameObject.FindGameObjectWithTag("InventoryPanel");
for (int i = 0; i < inventoryPanel.transform.childCount; i++)
{
GameObject newSlot = inventoryPanel.transform.GetChild(i).gameObject;
Debug.Log(newSlot);
newSlot.GetComponent<InventorySlot>().playerInventory = this;
inventorySlots[i] = newSlot;
Debug.Log(inventorySlots[i].name);
}
//Make equipment slots array
equipmentPanel = GameObject.FindGameObjectWithTag("EquipmentPanel");
for (int i = 0; i < equipmentPanel.transform.childCount; i++)
{
GameObject newSlot = equipmentPanel.transform.GetChild(i).gameObject;
newSlot.GetComponent<InventorySlot>().playerInventory = this;
equipmentSlots[i] = newSlot;
}
//Make weapon slots array
weaponsPanel = GameObject.FindGameObjectWithTag("WeaponsPanel");
for (int i = 0; i < weaponsPanel.transform.childCount; i++)
{
GameObject newSlot = weaponsPanel.transform.GetChild(i).gameObject;
newSlot.GetComponent<InventorySlot>().playerInventory = this;
weaponsSlots[i] = newSlot;
}
/*
* check for existing player data and get inventory here
*/
//Assign empty inventory
/*
for (int i = 0; i < inventorySpace; i++)
{
if (inventoryObjects.Count < inventorySpace)
{
inventoryObjects.Add(new InventoryObject() { inventoryItem = EmptyObject, count = 1 });
}
else break;
}
for (int i = 0; i < equipmentSpace; i++)
{
if (equipmentObjects.Count < equipmentSpace)
{
equipmentObjects.Add(new InventoryObject() { inventoryItem = EmptyObject, count = 1 });
}
else break;
}
for (int i = 0; i < weaponsSpace; i++)
{
if (weaponsObjects.Count < weaponsSpace)
{
weaponsObjects.Add(new InventoryObject() { inventoryItem = EmptyObject, count = 1 });
}
else break;
}
*/
UpdateInventoryUI();
}
private void Update()
{
if (Input.GetKeyDown("a"))
{
UpdateInventoryUI();
}
}
public bool TryAddItem(ItemData newItem)
{
bool openSlot = GetOpenSpace("Inventory") > 0;
foreach (InventoryObject inventoryObject in inventoryObjects)
{
if (newItem.stackable == true) //Check if stackable
{
if (inventoryObject.inventoryItem == newItem //Check if item is owned and not max stacks
&& inventoryObject.count < inventoryObject.inventoryItem.maxStackCount)
{
inventoryObject.count++; //Increment
UpdateInventoryUI();
return true;
}
}
}
if (openSlot == true) //Add item to open slot
{
AddNewItemToInventory(newItem);
return true;
}
else //no space
{
Debug.Log("no space");
}
return false;
}
public void AddNewItemToInventory(ItemData item)
{
inventoryObjects.Add(new InventoryObject() { inventoryItem = item, count = 1 });
UpdateInventoryUI();
}
public void RemoveItemFromInventory(int index)
{
inventoryObjects.RemoveAt(index);
UpdateInventoryUI();
}
public void UpdateInventoryUI()
{
//Update inventory
foreach (InventoryObject inventoryObject in inventoryObjects)
{
int index = inventoryObjects.IndexOf(inventoryObject);
GameObject inventorySlot = inventorySlots[index];
if (inventorySlot.transform.childCount > 0)
{
Destroy(inventorySlot.transform.GetChild(0).gameObject);
}
//Add InventoryItem to slot
InventoryItem inventoryItem = Instantiate(inventoryItemPrefab, inventorySlot.transform);
inventoryItem.item = inventoryObject.inventoryItem;
inventoryItem.count = inventoryObject.count;
}
//Remove empty slots
int emptyInventorySlots = GetOpenSpace("Inventory");
for (int i = 0; i < emptyInventorySlots - 1; i++)
{
int emptyIndex = inventoryObjects.Count + (i + 1);
GameObject inventorySlot = inventorySlots[emptyIndex];
if (inventorySlot.transform.childCount > 0)
{
Destroy(inventorySlot.transform.GetChild(0).gameObject);
}
}
//Update equipment
foreach (InventoryObject inventoryObject in equipmentObjects)
{
int index = equipmentObjects.IndexOf(inventoryObject);
GameObject inventorySlot = equipmentSlots[index];
if (inventorySlot.transform.childCount > 0)
{
Destroy(inventorySlot.transform.GetChild(0).gameObject);
}
//Add InventoryItem to slot
InventoryItem inventoryItem = Instantiate(inventoryItemPrefab, inventorySlot.transform);
inventoryItem.item = inventoryObject.inventoryItem;
inventoryItem.count = inventoryObject.count;
}
//Remove empty slots
int emptyEquipmentSlots = GetOpenSpace("Equipment");
for (int i = 0; i < emptyEquipmentSlots; i++)
{
int emptyIndex = inventoryObjects.Count + (i + 1);
GameObject inventorySlot = inventorySlots[emptyIndex];
if (inventorySlot.transform.childCount > 0)
{
Destroy(inventorySlot.transform.GetChild(0).gameObject);
}
}
//Update weapons
foreach (InventoryObject inventoryObject in weaponsObjects)
{
int index = weaponsObjects.IndexOf(inventoryObject);
GameObject inventorySlot = weaponsSlots[index];
if (inventorySlot.transform.childCount > 0)
{
Destroy(inventorySlot.transform.GetChild(0).gameObject);
}
//Add InventoryItem to slot
InventoryItem inventoryItem = Instantiate(inventoryItemPrefab, inventorySlot.transform);
inventoryItem.item = inventoryObject.inventoryItem;
inventoryItem.count = inventoryObject.count;
}
//Remove empty slots
int emptyWeaponsSlots = GetOpenSpace("Weapons");
for (int i = 0; i < emptyWeaponsSlots; i++)
{
int emptyIndex = inventoryObjects.Count + (i + 1);
GameObject inventorySlot = inventorySlots[emptyIndex];
if (inventorySlot.transform.childCount > 0)
{
Destroy(inventorySlot.transform.GetChild(0).gameObject);
}
}
}
public int GetOpenSpace(string inventoryType)
{
List<InventoryObject> inventoryList = null;
int maxSpace = 0;
switch (inventoryType)
{
case "Inventory":
inventoryList = inventoryObjects;
maxSpace = inventorySpace;
break;
case "Equipment":
inventoryList = inventoryObjects;
maxSpace = equipmentSpace;
break;
case "Weapons":
inventoryList = inventoryObjects;
maxSpace = weaponsSpace;
break;
}
int openSpace = maxSpace - inventoryList.Count;
return openSpace;
}
[System.Serializable]
public class InventoryObject
{
public ItemData inventoryItem;
public int count;
}
}
int openSpace = maxSpace - inventoryList.Count;
return openSpace;
}
[System.Serializable]
public class InventoryObject
{
public ItemData inventoryItem;
public int count;
}
}
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.
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.
Debug.Log("something", this);
Use something like this to figure out what actual object(s) you’re working with.
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.