Null-checking index of array problem

Hi all. I’m having some trouble with this script for a loot container. I’ve attached the whole thing below because it won’t make sense otherwise.

The idea is that this is attached to every loot container in game. Each container uses the same UI object to display the loot inside. I’m using an Item[ ] on each container to actually store the items and use ContainerSlot[ ] to handle the UI updating of each slot (grabs the item from same index in Item[ ] and changes the image on that index in ContainerSlot[ ]).

In SelectSlot(ContainerSlot s) I use an int variable to get the index of s in the ContainerSlot[ ] and store it to later refer to the same index in the Item[ ]. (This function gets called by a button on whichever slot is clicked, passing that slot as a parameter).

Here’s my problem. When we get to the two functions at the bottom (UpdateButtons() and ContainsItems() neither of these work as expected. In both functions I’m trying to check if a certain index of the Item[ ] does not equal null. I’ve serialized this array so I can actually see what is at each index of it. As expected the AddLoot(List items) function works as intended and adds items to the array from the loot table passed in when opening the container. However even if I’m checking an index which I can see from the inspector does contain an Item my checks on this index in the bottom 2 functions are not picking this up.

Considering I do this exact same null check in the OnOpen() function to set the sprite for each slot and it works fine there I can’t figure out why it would not work the same way in the other functions. Been staring at this for over an hour now and would really appreciate some advice. Thanks in advance!

public class Container : MonoBehaviour
{
    [SerializeField] private GameObject _containerUI;
   
    public GameObject containerUI { get { return _containerUI; } }

    private ContainerSlot[] _slots;
    private ContainerButtons[] _buttons;
    [SerializeField] private Item[] _items;

    private ContainerSlot _selected;
    private bool _looted = false;
    [SerializeField] private int _selectedIndex = 0;
    public ContainerSlot selected { get { return _selected; } set { _selected = value; } }

    public List<Item> loot = new List<Item>();
   
    
    void Start()
    {
        _slots = _containerUI.GetComponentsInChildren<ContainerSlot>();
        _items = new Item[_slots.Length];
        _buttons = _containerUI.GetComponentsInChildren<ContainerButtons>();       
       
        foreach (ContainerSlot slot in _slots)
        {
            slot.container = this;           
        }
        foreach (ContainerButtons button in _buttons)
        {
            button.container = this;
        }
    }

    public void ToggleUI()
    {
        _containerUI.SetActive(!_containerUI.activeSelf);
        OnOpen();
    }

    public void AddLoot(List<Item> items)
    {
        int rand = Random.Range(0, 2);
        int quantity = 0;
        if(rand == 0)
        {
            quantity = 1;
        }

        else if(rand == 1)
        {
            rand = Random.Range(1, 3);
            if(rand == 1)
            {
                quantity = 2;
            }
            else if(rand == 2)
            {
                rand = Random.Range(2, 4);
                if(rand == 2)
                {
                    quantity = 3;
                }
                else if(rand == 3)
                {
                    quantity = 4;
                }
            }
        }
                      
        for(int i = 0; i < quantity; i++)
        {
            _items[i] = items[Random.Range(0, items.Count)];           
        }
    }

    public void SelectSlot(ContainerSlot s)
    {
        _selected = s;
        _selectedIndex = System.Array.IndexOf(_slots, s);
        Debug.Log("_selectedIndex equal to " + _selectedIndex);
        UpdateButtons();
    }

    public void OnOpen()
    {          
        foreach(ContainerSlot slot in _slots)
        {
            slot.icon.sprite = default;
        }
        foreach(ContainerButtons button in _buttons)
        {
            button.gameObject.SetActive(false);
        }
        if (!_looted)
        {
            AddLoot(loot);
            _looted = true;
        }
        for (int i = 0; i < _slots.Length; i++)
        {
            if (_items[i] != null)
            {
                _slots[i].icon.sprite = _items[i].icon;
            }
        }
    }

    public void UpdateButtons()
    {
        if (_items[_selectedIndex] != null)
        {
            Debug.Log("left button should be enabled");
            _buttons[0].gameObject.SetActive(true);
            _buttons[0].buttonText.text = "Take";
        }
        if(ContainsItems())
        {
            Debug.Log("right button should be enabled");
            _buttons[1].gameObject.SetActive(true);
            _buttons[1].buttonText.text = "Take All";
        }
    }

    public bool ContainsItems()
    {
        for(int i = 0; i < _slots.Length; i++)
        {
            if (_items[i] != null)
                return true;
        }
        return false;
    }
}

Sure you’re looking at the same Container in the inspector as the code’s getting called on?

try:

public void UpdateButtons() {
    Debug.Log("Calling UpdateButtons on " + name + ", selected index is " + _selectedIndex, this);
    // and if you're feeling fancy
    Debug.Log("Items are " + string.Join(", ", _items.Select(item => item == null ? "null" : item.name)));

Passing an object as the second parameter of Debug.Log (in this case, this) makes that object get pinged when you click the log entry.

1 Like

100%. This code makes sure that, when opened for the first time, every container will have at least one item added. I’ve actually changed the index I was checking to explicitly check index 0 only, which will always have an item. It still returns null.

The _selectedIndex variable is set correctly and changes when I click a different slot on the UI ingame. Have confirmed this with Debug.Logs printing the value of _selectedIndex each click.

I’m sure this isn’t what’s happening but it seems like the Item[ ] is getting wiped at some point. On line 102 I do the null check on the Item[ ]. This check works fine. The checks in UpdateButtons() and ContainsItems() both come back with null though.

Like I said I don’t think that’s what’s happening. From the inspector I can see that the array isn’t being wiped because the values are there in front of me and stay there the whole time. If I can see from the inspector that there’s an item at index 0 then how come, when checking that same index, the code says otherwise? I’m totally stumped with this.

I will try the UpdateButtons() which you suggested when I’m back at my desktop in a few hours. Out and about on my laptop rn with no Unity installed.

Well, debugging can be tricky. A very common mistake is to accidentally link button actions to prefabs instead of the actual instances in the scene. You can still execute methods on prefabs, however they do not represent the objects in the scene. Prefabs are actual instances in memory, but do not exist in the scene. You may call SelectSlot on the prefab which will of course set the selected Index inside the prefab correctly, however it won’t affect the instance in the scene. That’s why I also would highly recommend to add a context reference to your Log calls so when clicking on the message in the console you will see what object you’re actually dealing with.

Apart from that, you haven’t really said what exactly happens. Is it that you don’t get those log messages from line 113 and 119?

You know that your UpdateButtons method only ever enables buttons but never disables the button when you for example select a slot that is empty? You only deactivate the buttons in your OnOpen method. So it does not really “Update” the buttons, only enables them.

Your answer led me to the solution. Thank you so much!

I hadn’t prefabbed the container but had duplicated it in the scene a few times to test the random item table. Somehow _selected and _selectedIndex were updating on a totally different container in the scene than the one I was interacting with. The loot table was getting set on the correct one but the other variables were not.

After prefabbing the container and re-placing them in the scene I can confirm they now all work mostly properly although it seems I either have to create a UI prefab and assign it to the container prefab or manually assign the non-prefabbed UI to each individual container. If anyone’s got any final thoughts on that I’d love to hear them but if not I’m sure I’ll figure it out.

Thanks for the helpful replies!

1 Like