help debugging inventory

so something always goes wrong with my code … and then i get stuck debugging for a month or two. hoping to avoid that this time.

i now have an item asset. i am trying to get the hero to be able to pick up an item and i am having a problem with my Item class functions - the console says there is an “Object reference not set to an instance of an object” error in the Inv_Add and Pick_Up functions:

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

namespace CoS
{
        //Wrapper class containing old info in a prettier way
        public class Item : ScriptableObject
        {
            heroInventory inv;
            public Transform hero;
            GameObject itemonground;

            private void Start()
            {
                inv = new heroInventory();
            }
            //do i need swinging and thrusting?
            //Make all of these readonly so you can be sure they won't be changed.
            public string objname;
            public string description;
            public double price;
            public int quantity;   //this doesn't belong here?
            public string sprite;
            public string type;        //!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
            public int dr_crushing;    //need to make objects for these, so i don't have so many properties
            public int dr_cutting;
            public int dr_impaling;
            public int pd_crushing;
            public int pd_cutting;
            public int pd_impaling;
            public int thrusting_modifier;
            public int swinging_modifier;

            public Item(string objname, string description, double price, int quantity, string sprite, string type,
                int dr_crushing, int dr_cutting, int dr_impaling, int pd_crushing, int pd_cutting, int pd_impaling,
                int thrusting_modifier, int swinging_modifier)
            {
                this.objname = objname;
                this.description = description;
                this.price = price;
                this.quantity = quantity;
                this.sprite = sprite;
                this.type = type;
                this.dr_crushing = dr_crushing;
                this.dr_cutting = dr_cutting;
                this.dr_impaling = dr_impaling;
                this.pd_crushing = pd_crushing;
                this.pd_cutting = pd_cutting;
                this.pd_impaling = pd_impaling;
                this.thrusting_modifier = thrusting_modifier;
                this.swinging_modifier = swinging_modifier;
            }
        /**********************
         * Inventory Functions
         * *******************/
        // remove from inventory
        public void Inv_Remove(Item item, int qnty)
        {
            //DID THIS WRONG!  Need inventory list
            //In the script we will remove an item from the inventory
            var found = -1;
            //We will check if the item exists and take its place
            for (var i = 0; i < 5; i++)
            {
                //ItemID eyedee1 = (ItemID)i;
                if (inv.inventory[i].objname == item.objname) //maybe have a unique id rather than use name?  possibility.
                {
                    found = i;
                    break;
                }
            }

            if (found != -1)
            {
                //If we found the item, we remove it
                inv.inventory[found].quantity -= qnty;//qnty is the amount to remove
                if (inv.inventory[found].quantity <= 0)
                {
                    inv.inventory[found] = ItemDatabase.items[ItemDatabase.ItemID.NOTHING];
                }  //If there is no more of the item we change the place to an empty place (Nothing item)
            }
        }

        //In this script we will add items to the inventory
        public void Inv_Add(Item item, int qnty)
        {
            var found = -1;
            bool fullflag = false;

            for (var i = 0; i < inv.invcapacity; i++)  // !!!!!!This is the line the debugger is pointing out
            {
                if (inv.inventory[i].objname == item.objname)
                {
                    found = i; //We affect to found the place of the item if we find it
                    break;
                }
            }

            //If it doesn't exist

            if (found == -1)
            {
                //Let's check if there is an empty place to add our item
                for (var j = 0; j < inv.invcapacity; j++)
                {
                    if (inv.inventory[j].objname == "Nothing")
                    {
                        found = j;
                        break;
                    }
                    else if (j == (inv.invcapacity - 1) && found != j)
                    {
                        fullflag = true;
                        break;
                    }
                }
            }

            //should have an "inventory is full" contingency
            if (fullflag)
            {
                // print this out!  in a game-friendly gui - depends on whether looting or picking up, or buying
                Debug.Log("inventory is full, cannot pick up object");
            }

            //Now let's add our item to the place we have *
            if (found != -1)
            {
                inv.inventory[found] = item; //Argument 0 is the id of the item
                inv.inventory[found].quantity += qnty; //Argument 1 is the amount of the item to add
            }
            // need a script for looting, for buying, and for picking up
        }

        // Drop an item
        public void Inv_Drop(Item item)
        {
            // item = item clicked in gui - right click and click drop
            //Instantiate(hero, item);
            //not sure what to use in place of "item"
            //use groundobject prefab
            Inv_Remove(item, 1);
        }

        //Pick up item
        public void Pick_Up(Item item)
        {
            Inv_Add(item, 1);
        }
    }
}

Someone will probably help you find the error. I’ll help you address the bigger problem you have.

Yeah. No doubt. Things go wrong with everyone’s code. I’ve been coding for the vast majority of my life and things go wrong in my code, from time to time.

You can’t change that but you can change whether or not you get stuck debugging for a month or two by adopting the discipline of Test-Driven Development.

An extreme simplification of TDD is as follows:

  • Make a meaningful, passing test that could fail for the right reasons your primary objective.
  • Always try to work on, at most, one test at a time.
  • Let the design and implementation of your code be derived from the needs expressed by your tests.
  • When it’s difficult to define a test for a behavior, refactor (improve the design of your code without modifying its behavior in any way).

Some people get it confused with the Test-First technique because they two dovetail together so nicely. Test-First is as follows:

  • Write your test first.
  • Stub out just production code enough to see the test fail.
  • Make your test pass.
  • Repeat.

You can find copious reading materials on both subjects, if you choose.

1 Like

Agree that tests are good. As for your actual error: ScriptableObject’s don’t have Start() or Update() methods. Change Start() to either Awake() or OnEnable() and it should work.

Edit: Oh wait… ScriptableObjects ALSO shouldn’t have constructors. You’re supposed to create them by using ScriptableObject.CreateInstance(). If you don’t, then Awake and OnEnable and so on won’t get called either.

See the docs here: Unity - Scripting API: ScriptableObject

1 Like

yipee! thank you so much! also thanks Max - i will try to become a better debugger. my one tutor is like a master of debugging. still much to learn.

okay, premature celebration. it’s still giving me the null reference error even though i commented out the constructor and replaced start with awake. not sure what i could be doing wrong.

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

namespace CoS
{
    public class heroInventory : ScriptableObject
    {
        public Canvas hinventorycanvas;
        public int invcapacity = 50;
        public Item[] inventory;

        public Canvas hinvequippedcanvas;

        // Use this for initialization
        void Start()
        {
            hinventorycanvas.enabled = false;
            hinvequippedcanvas.enabled = false;
            Item[] inventory = new Item[invcapacity];
            // going to have to refactor this later.  can't re-initialize inventory at the beginning of every scene!
            for (int i = 0; i < invcapacity; i++)
            {
                inventory[i] = CreateInstance("Item") as Item;  // was the constructor method with 16 arguments
            }
        }

        // Update is called once per frame
        void Update()
        {
            if (Input.GetKeyDown(KeyCode.I))
            {
               hinvequippedcanvas.enabled = !hinvequippedcanvas.enabled;
            }

        }
     
        public void XButton()
        {
            hinventorycanvas.enabled = false;
        }

        public void InventoryIconButton()
        {
            hinventorycanvas.enabled = true;
            ShowInventory();
        }

        // got to look at GM to figure out the types for the inventory
        void ShowInventory()
        {
            string labeltxt = "";
            string labelimg = "";
            int counter = 0;
            for (int i = 0; i < invcapacity; i++)
            {
                counter = i + 1;
                //update icon and text
                labeltxt = "Text" + counter;
                labelimg = "Image" + counter;

                Text text = GameObject.Find(labeltxt).GetComponent("Text") as Text;
                text.text = inventory[i].objname + " x " + inventory[i].quantity;
                Image image = GameObject.Find(labelimg).GetComponent("Image") as Image;
                Sprite invicon = Resources.Load<Sprite>(inventory[i].sprite);
                image.sprite = invicon;
            }
        }
    }
}

Er… that still looks like it says Start() and not Awake() to me. Also, like I mentioned, ScriptableObject’s can’t have Update() either. Maybe you should make it a MonoBehaviour instead of a ScriptableObject?

1 Like

In situations like these you should log the calls of methods related to the object in question. if inv is null and that seems to be the only option that it is either not yet created or has been destroyed or disallocated.

Are you using VisualStudio? If so right click on inv and select Find All references (or similar). It will list all the lines where it is used. You can look for assignments, if there is a line like:

inv = null;
inv = value;

Then it could be overriden somewehere else and you can see if those are called.

Another option is to check if the actual assignemnt in Start() is executed before your method is called.
Do that either with Debug.logs or Breakpoints.

Sometimes you have NullReferences on calls like inv.object, then you don’t know exaclty which object is null.
To be sure check that before using hte object like so:

if(inv==null) DebugLog("inv is null");
if(inv.object==null) DebugLog("inv.object is null");
//now you can use inv.

Once you solved the issue and can assure it is sound, you can remove these checks again.

Hope that helps. Also often your code is totally fine and you miss assignments or so on another part of your proejct. Then nobody else can help you who does not have access to your project.

From personal experience, learn and enjoy debugging! It can be nasty but it will make your life easier. With time you will learn to avoid these errors and write code without errors :slight_smile:

1 Like

EDIT2: maybe the problem is i’m using inventory from heroInventory but i’m not actually initializing it to an object of type Item in the Item script? Something like that? Been tinkering but can’t get it to work yet.

@makeshiftwings , thank you for catching that. one option would be to make heroinventory a monobehaviour, yes, but i’m not sure how to do line 26 then, as it requires the class to inherit from scriptable object. any ideas, anyone, how i can fix that then?

EDIT: just had to use scriptableobject.createinstance(“item”) as Item;

Just offering an opinion. When I worked on my inventory stuff, I used a monobehaviour as the inventory with monobehaviour scripts called “slots” to store the item + its quantity (for stackable items). All of the actual items were scriptable objects. To me, that just made the most sense, but whatever you find working for you is good :slight_smile:

1 Like

okay, i got it working. i tried to initialize the inventory in code in the start method, but that didn’t work. after initializing it in the editor it finally works now. and yeah methos5k, my items are all assets that i can put in with my other assets in the editor. seems to work though it could cause slow down if i have too many items. my only worry is that i intend to have a crafting system and that could clutter it up a bit.

Cool… If it’s working for you, it’s all good :slight_smile: