Inventory scriptable object

Hello everyone, unity and c# study for a few months, I’m developing an inventory system made with scriptable object; at the moment I’m at a dead end:
inventory works, prefabricated weapons are instantiated to the player, when I collect an item it is loaded into the inventory UI, but the onClickCallBack is not assigned to the slot and the item is unusable. You have some advice.
Thank you thank you

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;


[CreateAssetMenu(menuName ="Scriptable Objects/Inventory System/Inventory")]



public class Inventory : ScriptableObject
{
    // pickup item

    public static Inventory inventory;

   [SerializeField] private InventoryUI inventoryUIPrefab;



   private InventoryUI _inventoryUI;
   private InventoryUI inventoryUI
   {
       get
       {
           if(!_inventoryUI)
           {
               _inventoryUI = Instantiate(inventoryUIPrefab, playerEquipment.GetUIParent());
           }
           return _inventoryUI;
       }
   }



    public Dictionary<InventoryItem, int> itemToCountMap = new Dictionary<InventoryItem, int>();
    public List<InventoryItemWrapper> items = new List<InventoryItemWrapper>(); // pickup item here
    private PlayerEquipmentController playerEquipment;
 
 
   

   public void InitInventory(PlayerEquipmentController playerEquipment)
   {   
     
       this.playerEquipment = playerEquipment;
       for(int i = 0; i < items.Count; i++)
       {
           itemToCountMap.Add(items[i].GetItem(), items[i].GetItemCount());
           
       }
   }

   public void OpenInventory()
   {
       inventoryUI.gameObject.SetActive(true);
       inventoryUI.InitInventoryUI(this);
   }

   public void AssignItem(InventoryItem item)
   {

       item.AssignItemToPlayer(playerEquipment);
      
      
   }

   public Dictionary<InventoryItem, int> GetAllItemsMap()
   {
       return itemToCountMap;
 
   }

   public void RemoveItem(InventoryItem item, int count)
   {
       int currentItemCount;
      
       if(itemToCountMap.TryGetValue(item, out currentItemCount))
       {
           itemToCountMap[item] = currentItemCount - count;

           if(currentItemCount - count <= 0)
           {
               inventoryUI.DestroySlot(item);
           }
           else
           {
               inventoryUI.UpdateSlot(item, currentItemCount - count);
           }
       }
       else
       {
           Debug.Log(string.Format("Cant Remove {0}. This item is not in the inventory"));
       }
   }

}
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class InventoryUI : MonoBehaviour
{
    [SerializeField] private Transform slotsParent;
    [SerializeField] private InventorySlot slotPrefab;
    private Dictionary<InventoryItem, InventorySlot> itemToSlotMap = new Dictionary<InventoryItem, InventorySlot>();
    public PlayerPIckUp playerPIckUp;
    public static InventoryUI inventoryUI;

   
  
    public void InitInventoryUI(Inventory inventory)
    {
        var itemsMap = inventory.GetAllItemsMap();

        foreach(var kvp in itemsMap)
        {
            CreateOrUpdateSlot(inventory, kvp.Key, kvp.Value);
        }
    }



    public void CreateOrUpdateSlot(Inventory inventory, InventoryItem item, int itemCount)
    {
        if (itemCount != 0)
        {
            // if inventory does not contain the item
            if (!itemToSlotMap.ContainsKey(item))
            {
                var slot = CreateSlot(inventory, item, itemCount);
                itemToSlotMap.Add(item, slot);
           
            }
            else
            {
             
                UpdateSlot(item, itemCount);
            }
        }
    }
   

    public void UpdateSlot(InventoryItem item, int itemCount)
    {
       
           itemToSlotMap[item].UpdateSlotCount(itemCount);
      
    }

 

    public InventorySlot CreateSlot(Inventory inventory, InventoryItem item, int itemCount)
    {
        var slot = Instantiate(slotPrefab,slotsParent);
        slot.InitSlotVisualization(item.GetSprite(), item.GetName(), itemCount);
        slot.AssignSlotButtonCallBack(() => inventory.AssignItem(item));
        return slot;
    }

 

    public void DestroySlot(InventoryItem item)
    {
        Destroy(itemToSlotMap[item].gameObject);
        itemToSlotMap.Remove(item);

    }
}
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using TMPro;

public class InventorySlot : MonoBehaviour
{
    [SerializeField] public Image emptySlot;
    [SerializeField] private Image itemImage;
    [SerializeField] private TextMeshProUGUI itemNameText;
    [SerializeField] private TextMeshProUGUI itemCountText;
    public Button slotButton;

    public void InitSlotVisualization(Sprite itemSprite, string itemName, int itemCount)
    {
        itemImage.sprite = itemSprite;
        itemNameText.text = itemName;
        UpdateSlotCount(itemCount);
    }

    public void UpdateSlotCount(int itemCount)
    {
        itemCountText.text = itemCount.ToString();
    }

    public void AssignSlotButtonCallBack(System.Action onClickCallBack)
    {
        slotButton.onClick.AddListener(() => onClickCallBack());
    }
}
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

// il problema sempre essere alla riga 61 di inventory e di conseguenza l'errore si trasmette alla riga 30 di inventory slot


public class PlayerPIckUp : MonoBehaviour // raccoglie un oggetto lo instanzia nella UI ma poi rimane inutilizzabile per il mancato callback
{
    [SerializeField] public InventoryItem item;

    public int itemCount;

    private InventoryUI inventoryUI;
    private Inventory inventory;
    private InventorySlot inventorySlot;
   
    void Start()
    {
        inventoryUI = FindObjectOfType<InventoryUI>();
        inventory = FindObjectOfType<Inventory>();
      
       
    }

    public void OnTriggerStay(Collider other)
    {
        var player = CompareTag("Player");

          if(player = true && Input.GetButtonDown("Fire2"))
        {
           
                inventoryUI.CreateSlot(inventory, item, itemCount);
                this.gameObject.SetActive(false);  
          
        }
    }

}

You must find a way to get the information you need in order to reason about what the problem is.

What is often happening in these cases is one of the following:

  • the code you think is executing is not actually executing at all
  • the code is executing far EARLIER or LATER than you think
  • the code is executing far LESS OFTEN than you think
  • the code is executing far MORE OFTEN than you think
  • the code is executing on another GameObject than you think it is
  • you’re getting an error or warning and you haven’t noticed it in the console window

To help gain more insight into your problem, I recommend liberally sprinkling Debug.Log() statements through your code to display information in realtime.

Doing this should help you answer these types of questions:

  • is this code even running? which parts are running? how often does it run? what order does it run in?
  • what are the values of the variables involved? Are they initialized? Are the values reasonable?
  • are you meeting ALL the requirements to receive callbacks such as triggers / colliders (review the documentation)

Knowing this information will help you reason about the behavior you are seeing.

If your problem would benefit from in-scene or in-game visualization, Debug.DrawRay() or Debug.DrawLine() can help you visualize things like rays (used in raycasting) or distances.

You can also call Debug.Break() to pause the Editor when certain interesting pieces of code run, and then study the scene manually, looking for all the parts, where they are, what scripts are on them, etc.

You can also call GameObject.CreatePrimitive() to emplace debug-marker-ish objects in the scene at runtime.

You could also just display various important quantities in UI Text elements to watch them change as you play the game.

If you are running a mobile device you can also view the console output. Google for how on your particular mobile target, such as this answer or iOS: https://discussions.unity.com/t/700551 or this answer for Android: https://discussions.unity.com/t/699654

Another useful approach is to temporarily strip out everything besides what is necessary to prove your issue. This can simplify and isolate compounding effects of other items in your scene or prefab.

Here’s an example of putting in a laser-focused Debug.Log() and how that can save you a TON of time wallowing around speculating what might be going wrong:

https://discussions.unity.com/t/839300/3

These things (character customization, inventories, shop systems) 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?
  • 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.

Or… do like I like to do: just jump in and make it up as you go. It is SOFT-ware after all… evolve it as you go! :slight_smile:

Breaking down a large problem such as inventory:

https://discussions.unity.com/t/826141/4

“Combining a bunch of stuff into one line always feels satisfying, but it’s always a PITA to debug.” - Star Manta on the Unity3D forums

Thank you for your reply man.
is this not working–> slot.AssignSlotButtonCallBack(() => inventory.AssignItem(item));

Why not just:

public void AssignSlotButtonCallBack(System.Action onClickCallBack)
    {
        slotButton.onClick.AddListener(onClickCallBack);
    }

the error is found at line 61 of the inventoryUI script and line 30 of the inventrorySlot script, but I don’t understand how to solve it, do I need to create a monobeaviour inventory?

“The error” ???

MonoBehaviour Inventory has some advantages, you can add multiple components per Item (durability, damage, weight, … where required).
But it won’t fix any issues. I cannot find any problems in your code (when looking at it briefly). I guess you have a wrong assignment somewhere. Maybe the slotButton points to the wrong object.

Also I don’t quite understand what kind of code is executed and what is not executed (based on your comments here).
If the listener doesn’t fire anything, maybe you call “ClearAllListeners” somewhere else in your code? Or, as said, it’s pointing to the wrong object. Or the button is obstructed in your UI. Or you don’t have an EventSystem in your hierarchy. Or your UI doesn’t have a graphics raycaster. Or the Button is disabled. Or …

You see, this can have a million issues and only one of those I mentioned is a coding issue.
All you can do is going through this one by one. Checking if the Button is pressed at all, checking if the signal reaches your script. Checking if it’s passed through to the action.

NullReferenceException: Object reference not set to an instance of an object
InventoryUI+<>c__DisplayClass8_0.b__0 () (at Assets/Vertengest/Script/Inventory/InventoryUI.cs:61)
InventorySlot+<>c__DisplayClass7_0.b__0 () (at Assets/Vertengest/Script/Inventory/InventorySlot.cs:30)
UnityEngine.Events.InvokableCall.Invoke () (at :0)
UnityEngine.Events.UnityEvent.Invoke () (at :0)
UnityEngine.UI.Button.Press () (at Library/PackageCache/com.unity.ugui@1.0.0/Runtime/UI/Core/Button.cs:68)
UnityEngine.UI.Button.OnPointerClick (UnityEngine.EventSystems.PointerEventData eventData) (at Library/PackageCache/com.unity.ugui@1.0.0/Runtime/UI/Core/Button.cs:110)
UnityEngine.EventSystems.ExecuteEvents.Execute (UnityEngine.EventSystems.IPointerClickHandler handler, UnityEngine.EventSystems.BaseEventData eventData) (at Library/PackageCache/com.unity.ugui@1.0.0/Runtime/EventSystem/ExecuteEvents.cs:50)
UnityEngine.EventSystems.ExecuteEvents.Execute[T] (UnityEngine.GameObject target, UnityEngine.EventSystems.BaseEventData eventData, UnityEngine.EventSystems.ExecuteEvents+EventFunction`1[T1] functor) (at Library/PackageCache/com.unity.ugui@1.0.0/Runtime/EventSystem/ExecuteEvents.cs:262)
UnityEngine.EventSystems.EventSystem:Update() (at Library/PackageCache/com.unity.ugui@1.0.0/Runtime/EventSystem/EventSystem.cs:385)

Thank you all for the interest and advice, I will try to better explain what I want to do:
I have an inventory that is a scriptable object, objects can be added manually to the InventoryItemWrapper list

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

[System.Serializable]

public class InventoryItemWrapper
{
   public InventoryItem item;
    [SerializeField] public int count;

    public InventoryItem GetItem()
    {
        return item;
       
    }

    public int GetItemCount()
    {
        return count;
    }

   
}

I would like that through the trigger of PlyerPickUp, that is collecting the object, the latter is automatically added to the list becoming usable in battle whether it is a weapon or a consumable object