0
\$\begingroup\$

I'm currently trying to get a simple inventory system to work but I'm having a lot of trouble.

This isn't the exact code I have, but it's a shorter version that I'm pretty sure would function the same:

public struct ItemSlot {
    public Item item;

    public ItemSlot(Item item) {
        this.item = item;
    }
}

public class Item : MonoBehaviour {
    public string itemName;
}

public class Accessory : Item {

}

public class Inventory : MonoBehaviour {
    public ItemSlot[] itemSlots = new ItemSlot[1];

    public void AddItem(ItemSlot slot) {
        itemSlots[0] = slot;
    }
}

public class Player : MonoBehaviour {
    public Inventory inventory;

    private void Update() {
        if (Input.GetKeyDown(KeyCode.E)) {
            Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
            RaycastHit hitInfo;

            if (Physics.Raycast(ray, out hitInfo, 20)) {
                GameObject target = hitInfo.collider.gameObject;

                inventory.AddItem(new ItemSlot(target.GetComponent<Item>()));
                Destroy(target);   
            }
        }

        if (Input.GetKeyDown(KeyCode.G)) {
            print(inventory.itemSlots[0].item);
        }
    }
}

For a bit more prefacing I have a capsule object with the Player script on it, an empty with the Inventory script on it, which is then attached to the Player script (I'm not sure if I want to make the inventory a scriptable object which is why I've done this), and then lastly I have a cube object with the Accessory script on it.

So, when I press E and pick up the item it gets added into the player's inventory and then destroyed from the scene. Then, when I press G to display that item in the inventory the console just says null. Not Null, which is what an actual null would show up as, but the string "null". If I use visual studio's debugger and have a look at what the accessory's base is, it also says null, but for some reason if I then try and print the accessory's name then that works fine, even though that variable is defined in the Item class.

If I instead do this in the Inventory class:

public void AddItem(ItemSlot slot) {
    ItemSlot[0] = slot;
    print(ItemSlot[0].item);    
}

It works fine and prints Item (Accessory) in the console. Even if I print it right after I destroy target in the Player class, that also prints Item (Accessory), so I have no idea why it doesn't work on the next block of code.

Sorry this is so long and maybe an easy fix, but I'm lost.

Edit: I think everything should be fixed now.

\$\endgroup\$
  • \$\begingroup\$ It looks like you might have over-simplified the version of the code in this question. The version of AddItem you've shown us takes an ItemSlot as an argument, but you're calling it with an Item. And your Player type lacks an itemSlots variable to use in your call to the print function. So this code will not compile as-is. Can you try placing this code in a new, empty project, so you can make sure it compiles and serves as a Minimal Complete Verifiable Example of the problem? \$\endgroup\$ – DMGregory May 6 at 17:33
  • \$\begingroup\$ When you get the string "null" printed out, that's a sign that you're trying to reference a UnityObject that has either been destroyed, or was never attached/assigned in the Inspector. For those purposes, Unity inserts a fake null that can give you more instructive error messages if you try to use it. \$\endgroup\$ – DMGregory May 6 at 17:38
  • \$\begingroup\$ @DMGregory Sorry about that, I typed the code on here so I figured there would be some errors. It should all be fixed now, and I also went and created a project with this code like you suggested and it compiles and produces the same null error. Thanks for the link as well, I'll have a look at it tomorrow as it is quite late for me now. \$\endgroup\$ – Genzou May 6 at 18:20
0
\$\begingroup\$

You've destroyed the object the Item component was attached to. That destroys the item component too.

For the rest of its lifetime until it's collected by the garbage collector, it will compare as equal to null, print as null, etc. It's effectively become just a tombstone marking the place of a destroyed object.

If you want to keep the thing around, don't destroy it. Or, store a reference to an item archetype data structure on your item component, and when you want to collect the item, store that archetype. Then you can destroy the container item with no problems, because you've already copied out the data about it that you need to use later.

| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ Oh, right that makes sense, I just figured it would instead store the information. However, why does it still return Item (Accessory) directly after the object has been destroyed? Anyway, could you provide an example of an archetype data structure as I haven't used anything like that before. \$\endgroup\$ – Genzou May 6 at 18:57
  • \$\begingroup\$ You can still identify the type of the object because the object still exists in managed memory. The rules of C# say we can't delete something as long as any reachable variable still holds a reference to it. But this floating bit of managed memory no longer corresponds to a real component on a real game object, so you should treat it as garbage and null it out so the garbage collector can claim it. As for item archetypes, please see previous answers on the topic \$\endgroup\$ – DMGregory May 6 at 19:21
  • \$\begingroup\$ I originally had my items as scriptable objects but wasn't sure what benefit it actually had over monobehaviour, so I ended up switching. Anyway, I see why now, thanks for the help. \$\endgroup\$ – Genzou May 6 at 19:33

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.