Making a Inventory System, learning how to use List<>. Please help if you can

I’m working on a inventory system and trying to get it working for my npc’s.

In my system it breaks into 5 primary classes that i believe you need to know. InventoryItemData, InventorySlot, InventorySystem, inventoryHolder, NPC Brain.

Here is a break-down of each of those classes:

InventoryItemData.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

[CreateAssetMenu(menuName = "Inventory System/Inventory Item/Misc")]
public class InventoryItemData : ScriptableObject
{
    public int ID;
    public string DisplayName;
    [TextArea(4,4)]
    public string Description;
    public Sprite Icon;
    public int MaxStackSize;

    public bool IsFood;
    public int HungerValue;
}
InventorySlot.cs
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

[System.Serializable]
public class InventorySlot 
{
    [SerializeField] private InventoryItemData itemData;
    [SerializeField] private int stackSize;
    public InventoryItemData ItemData => itemData;
    public int StackSize => stackSize;
    public InventorySlot(InventoryItemData source, int amount)
    {
        itemData = source;
        stackSize = amount;
    }

    public InventorySlot()
    {
        itemData = null;
        stackSize = -1;
    }

    public void ClearSlot()
    {
        itemData = null;
        stackSize = -1;
    }
    public void AssignItem(InventorySlot invSlot)
    {
        if(itemData == invSlot.ItemData) AddToStack(invSlot.stackSize);
        else
        {
            itemData = invSlot.itemData;
            stackSize = 0;
            AddToStack(invSlot.stackSize);
        }
    }
    public void UpdateInventorySlot(InventoryItemData data, int amount)
    {
        itemData = data;
        stackSize = amount;
    }

    public bool RoomLeftInStack(int amountToAdd, out int amountRemaining)
    {
        amountRemaining = itemData.MaxStackSize - stackSize;
        Debug.Log("amount remaining " + amountRemaining);
        return RoomLeftInStack(amountRemaining);
    }

    public bool RoomLeftInStack(int amountToAdd)
    {
        if(stackSize + amountToAdd <= itemData.MaxStackSize) return true;
        else 
        {
        return false;
        }
    }


    public void AddToStack(int amount)
    {
        stackSize += amount;
    }

    public void RemoveFromStack(int amount)
    {
        stackSize -= amount;
    }

    public bool SplitStack(out InventorySlot splitStack)
    {
        if (stackSize <= 1)
        {
            splitStack = null;
            return false;
        }
        int halfStack = Mathf.RoundToInt(stackSize / 2);
        RemoveFromStack(halfStack);

        splitStack = new InventorySlot(itemData,halfStack);
        return true;

    }
}
InventorySystem.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
using System.Linq;

[System.Serializable]
public class InventorySystem 
{
    [SerializeField] private List<InventorySlot> inventorySlots;
    public List<InventorySlot> InventorySlots => inventorySlots;
    public int InventorySize => InventorySlots.Count;

    public UnityAction<InventorySlot> OnInventorySlotChanged;
    public HungerBehavior hungerBehavior;


    public InventorySystem(int size)
    {
        inventorySlots = new List<InventorySlot>(size);
        for (int i = 0; i < size; i++ )
        {
            inventorySlots.Add(new InventorySlot());
        }
    }

    public bool AddToInventory(InventoryItemData itemToAdd, int amountToAdd)
    {
        if(ContainsItem(itemToAdd, out List<InventorySlot> invSlot)) //Check whether item exists in inventory
        {
            foreach (var slot in invSlot)
            {
                if(slot.RoomLeftInStack(amountToAdd))
                {
                    slot.AddToStack(amountToAdd);
                    OnInventorySlotChanged?.Invoke(slot);
                    return true;
                }
            }
        }

        if (HasFreeSlot(out InventorySlot freeSlot)) //gets the first free slot
        {
            freeSlot.UpdateInventorySlot(itemToAdd, amountToAdd);
            OnInventorySlotChanged?.Invoke(freeSlot);
            return true;
        }
        return false;
    }

    public bool ContainsItem(InventoryItemData itemToAdd, out List<InventorySlot> invSlot)
    {
        invSlot = InventorySlots.Where(i => i.ItemData == itemToAdd).ToList();
        return invSlot == null ? false : true;
    }

    public bool HasFreeSlot(out InventorySlot freeSlot)
    {
        freeSlot = InventorySlots.FirstOrDefault(i => i.ItemData == null);
        return freeSlot == null ? false : true;
    }

    public bool ConsumedInventoryItem(InventorySlot itemToEat)
    {
        if(itemToEat.ItemData.IsFood == true)
        {
            hungerBehavior.Hunger -= itemToEat.ItemData.HungerValue;
            itemToEat.RemoveFromStack(1);
            hungerBehavior.Eat(itemToEat);
            itemToEat.ClearSlot();
            return true;
        }
            return false;
    }

    private static bool Isfood(InventorySlot slot)
    {
        return((slot.ItemData.IsFood == true));
    }
    public bool CheckFood()
    {
        List<InventorySlot> foodSlots = new List<InventorySlot>();
        foodSlots = new List<InventorySlot>(InventorySlots.FindAll(Isfood));
            foreach( var x in foodSlots) 
            {
            Debug.Log( x.ToString());
            }
        return foodSlots == null ? false:true;
    }

        public int CheckFood(out int amountOfFood)
    {
        amountOfFood = 0;
        List<InventorySlot> foodSlots;
        foodSlots = InventorySlots.FindAll(i => i.ItemData.IsFood == true);
            foreach( var x in foodSlots) 
            {
                amountOfFood++;
                Debug.Log( x.ToString());
            }
        return amountOfFood;
    }

}
InventoryHolder.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;

[System.Serializable]
public class InventoryHolder : MonoBehaviour
{
    [SerializeField] private int inventorySize;
    [SerializeField] protected InventorySystem primaryInventorySystem;

    public InventorySystem PrimaryInventorySystem => primaryInventorySystem;

    public static UnityAction<InventorySystem> OnDynamicInventoryDisplayRequested;

    protected virtual void Awake() 
    {
        primaryInventorySystem = new InventorySystem(inventorySize);
    }

        public bool AddToInventory(InventoryItemData data, int amount)
    {
        if(primaryInventorySystem.AddToInventory(data,amount))
        {
            return true;
        }
        
        return false;
    }
}
NPCBrain.cs
using System;
using System.Collections;
using System.Collections.Generic;
using CrashKonijn.Goap.Behaviours;
using UnityEngine;
[RequireComponent(typeof(AgentBehaviour))]
public class NPCBrain : MonoBehaviour
{
    [SerializeField] private CharcterSensor charcterSensor;
    [SerializeField] private AttackConfigSO AttackConfig;
    [SerializeField] private CharcterConfigSO CharcterConfigSO;
    [SerializeField] private HungerBehavior Hunger;
    [SerializeField] private FoodSensor foodSensor;
    [SerializeField] private InventoryHolder inventoryHolder;
    private bool hasFood = false;
    private bool CharcterInRange;
    private bool FoodInRange;
    private int AmountOfFood;
    private int AcceptableAmountOfFood = 10;
    private AgentBehaviour AgentBehaviour;
    private void Awake() 
    {
        AgentBehaviour = GetComponent<AgentBehaviour>();
        inventoryHolder = GetComponent<InventoryHolder>();

    }
    void Start()
    {
        AgentBehaviour.SetGoal<ExploreGoal>(false);
        charcterSensor.Collider.size = AttackConfig.SensorSize;
    }
    private void Update() 
    {
        if(Hunger.Hunger > Hunger.HungerLimit + CharcterConfigSO.Constitution)
        {
            hasFood = inventoryHolder.PrimaryInventorySystem.CheckFood();
            if(hasFood == false)
            {
                AgentBehaviour.SetGoal<FindFoodGoal>(true);
            }
            else if(hasFood == true)
            {
                AgentBehaviour.SetGoal<EatGoal>(true);
            }
        }
        else if (Hunger.Hunger < Hunger.AcceptableHungerlimit + CharcterConfigSO.TemperateOrGluttonous && AgentBehaviour.CurrentGoal is EatGoal && CharcterInRange)
        {
            AgentBehaviour.SetGoal<KillCharcter>(false);
        }
        else if (Hunger.Hunger <= 0 && AgentBehaviour.CurrentGoal is EatGoal && !CharcterInRange)
        {
            AgentBehaviour.SetGoal<ExploreGoal>(false);
        }
    }

    private void OnEnable() 
    {
        charcterSensor.OnCharcterEnter += CharcterSensorOnCharcterEnter;
        charcterSensor.OnCharcterExit += CharcterSensorOnCharcterExit;
        foodSensor.OnFoodEnter += FoodSensorOnFoodEnter;
        foodSensor.OnFoodExit += FoodSensorOnFoodExit;

    }

    private void OnDisable() 
    {
        charcterSensor.OnCharcterEnter -= CharcterSensorOnCharcterEnter;
        charcterSensor.OnCharcterExit -= CharcterSensorOnCharcterExit;
        foodSensor.OnFoodEnter -= FoodSensorOnFoodEnter;
        foodSensor.OnFoodExit -= FoodSensorOnFoodExit;
    }

    private void CharcterSensorOnCharcterExit(Vector3 lastKnowPosition)
    {
        AgentBehaviour.SetGoal<ExploreGoal>(true);
        CharcterInRange = false;
    }

    private void CharcterSensorOnCharcterEnter(Transform Charcter)

    {  
        AgentBehaviour.SetGoal<KillCharcter>(true);
        CharcterInRange = true;
    }

        private void FoodSensorOnFoodEnter(Transform Food)
    {  
        inventoryHolder.PrimaryInventorySystem.CheckFood();
        AmountOfFood =  inventoryHolder.PrimaryInventorySystem.CheckFood(out int amountOfFood);
        FoodInRange = true;
        ///if(AmountOfFood * CharcterConfigSO.TemperateOrGluttonous < AcceptableAmountOfFood)
    }

      private void FoodSensorOnFoodExit(Vector3 lastKnowPosition)
    {
        AgentBehaviour.SetGoal<ExploreGoal>(true);
        FoodInRange = false;
    }
}

Quick summary:

InventoryItemData, InventorySlot, InventorySystem, inventoryHolder, NPC Brain

  • InventoryItemData contains item data.
  • InventorySlot Contains the InventoryItemData and how many items are currently in the slot.
  • InventorySystem creates a list of InventorySlots, and the interactions of the slots.
  • InventoryHolder initialize the InventorySystem and determine its size.
  • The NPCBrain References the InventoryHolder for its statement.

My goal:

My goal is to be able to check my inventory system list for Inventory slot’s item data, to know whether or not it is true or false. My current issue is the bool CheckFood() method i created in the Inventory System
I created a bool named hasFood in the brain set to false by default. I want to change the variable to equal CheckFood()

But when i run it i get this error:

NullReferenceException: Object reference not set to an instance of an object
InventorySystem.Isfood (InventorySlot slot) (at Assets/Scripts/Inventory Scripts/InventorySystem.cs:79)
System.Collections.Generic.List`1[T].FindAll (System.Predicate`1[T] match) (at <6aa56e57ab504395b555cf3ed50fa53d>:0)
InventorySystem.CheckFood () (at Assets/Scripts/Inventory Scripts/InventorySystem.cs:84)
NPCBrain.Update () (at Assets/CrashKonijn/GOAP/Behaviors/NPCBrain.cs:36)

Please help

yes my Inventory Holder was assigned to my NPCBrain.

Ultimately it looks like the error is happening in IsFood(), so either "slot", or "slot.ItemData" is null. Can you add Debug prints to check which one is null?

@dstears i added private static bool Isfood(InventorySlot slot) { Debug.Log("slot " + slot); Debug.Log("slot item data " + slot.ItemData); return((slot.ItemData.IsFood == true)); }

slot InventorySlot UnityEngine.Debug:Log (object) InventorySystem:Isfood (InventorySlot) (at Assets/Scripts/Inventory Scripts/InventorySystem.cs:79) System.Collections.Generic.List1<InventorySlot>:FindAll (System.Predicate1<InventorySlot>) InventorySystem:CheckFood () (at Assets/Scripts/Inventory Scripts/InventorySystem.cs:85) NPCBrain:FoodSensorOnFoodEnter (UnityEngine.Transform) (at Assets/CrashKonijn/GOAP/Behaviors/NPCBrain.cs:88)

slot item data UnityEngine.Debug:Log (object) InventorySystem:Isfood (InventorySlot) (at Assets/Scripts/Inventory Scripts/InventorySystem.cs:80) System.Collections.Generic.List1<InventorySlot>:FindAll (System.Predicate1<InventorySlot>) InventorySystem:CheckFood () (at Assets/Scripts/Inventory Scripts/InventorySystem.cs:86) NPCBrain:FoodSensorOnFoodEnter (UnityEngine.Transform) (at Assets/CrashKonijn/GOAP/Behaviors/NPCBrain.cs:88) FoodSensor:OnTriggerEnter2D (UnityEngine.Collider2D) (at Assets/CrashKonijn/GOAP/Sensors/FoodSensor.cs:25)