Home > Software engineering >  How can I store a gameobject in a variable, destroy it in the scene, but not in the variable?
How can I store a gameobject in a variable, destroy it in the scene, but not in the variable?

Time:07-30

I have an inventory that can store up to 5 items and every item in the scene is different, there are no duplicates. There's gonna be like one letter, one book, one gun, etc. in the scene.

The inventory is displayed on screen with 5 slots that I can iterate through with the mouse wheel.

So, instead of using a list or a linked list, I have an array of InventoryObjects and an InventoryObject contains a slot displayed on screen and the item displayed in the slot. It's basically like the equipped item bar in Minecraft. You have some slots with an item per slot and you can use whatever item you have currently selected. Only difference being I have 5 slots and Minecraft has 10.

using UnityEngine;

public class InventoryObject
{
    public GameObject ItemObject;
    public GameObject SlotObject;
    
    // Only used for initialization
    public InventoryObject(GameObject AddSlot)
    {
        SlotObject = AddSlot;
        ItemObject = null;
    }
}

The problem is that I want to pick up an item, add it to the inventory and destroy it, then instantiate it when selected. However, if I do that, the item will be added to the inventory and when I use Destroy() it also destroys the item in the inventory.

Here's the player script:

private void Update()
{
    // _input.grab detects when I press the key to grab an item
    if (_input.grab)
    {
        Grab();
        _input.grab = false;
    }
}

void Grab()
{
    if (!CheckGrabbability()) return; // Check if what we are looking at can be grabbed

    Inventory.instance.Add(objectToGrab);
    Destroy(objectToGrab);
}

And here's the inventory script:

public class Inventory : MonoBehaviour
{
    public static Inventory instance;
    public int maxItems;
    // Slots are added in editor
    public GameObject[] InventorySlots;
    public InventoryObject[] SlotList = new InventoryObject[5];

    void Awake()
    {
        instance = this;
        // Init list of InventoryObjects
        for (int i = 0; i < 5; i  )
        {
            SlotList[i] = new InventoryObject(InventorySlots[i]);
        }
    }

    public void Add(GameObject item)
    {
        if (item == null) return;   // Guard clause
        // Adding the item in the first available slot
        for (int i = 0; i < maxItems; i  )
        {
            // Detecting an available slot
            if (SlotList[i].ItemObject == null)
            {
                // Adding the item and getting out of the loop
                SlotList[i].ItemObject = item;
                i = maxItems;
            }
        }
    }
}

I have found someone with a similar issue and the comments suggested object pooling. But I don't generate a bunch of objects quickly, I only generate one and it's a specific one, it's not like a bullet taken from a pool of a thousand bullets. So I don't think object pooling is suitable here and I'm wondering how I can solve my problem.

If I can't just copy an item, destroy the original and instantiate the copy, then how about disabling the item and enabling it when necessary? I'm just not sure how that would work because if the item is disabled, it's still in the scene, so could it conflict with other things or is it like if it wasn't there at all from the player's POV?

CodePudding user response:

Disabling a GameObject will also disable any script attached to it, this should be the only drawback of using this solution.

If you want scripts and components attached to it to still be working while the item is hidden, you could simply disable the renderer instead.

Here's a small code snippet to illustrate my example, although I wouldn't recommend using the GetComponent function outside of Unity's Awake and Start event functions.

Disabling the item's renderer :
private void Grab()
{
    if (!CheckGrabbability()) return; // Check if what we are looking at can be grabbed

    Inventory.instance.Add(objectToGrab);
    objectToGrab.GetComponent<Renderer>().enabled = false;
}

But as I think that disabling the Item's renderer shouldn't be delegated to a player function, here's a better implementation of this solution using a proper component for items that can be grabbed by the player.

IGrabItem interface :
public interface IGrabItem
{
    public bool Enabled { get; set; }
}
Script to be attached to items that can be grabbed :
public class Item: MonoBehaviour, IGrabItem
{
    [SerializeField] private Renderer itemRenderer;

    private void Awake()
    {
        if (itemRenderer == null)
        {
            itemRenderer = GetComponent<Renderer>();
        }
    }

    public bool Enabled
    {
        get => itemRenderer.enabled;
        set => itemRenderer.enabled = value;
    }
}
We add a new line in the Add function of Inventory :
public class Inventory : MonoBehaviour
{
    public void Add(IGrabItem item)
    {
        item.Enabled = false;
        // Do your thingy with the item here
    }
}
Disabling the item is not the responsability of the Grab function anymore :
private void Grab()
{
    if (!CheckGrabbability()) return; // Check if what we are looking at can be grabbed

    Inventory.instance.Add(objectToGrab);
}
  • Related