Building a Robust Grid-Based Inventory System in Unity

Posted by Gemma Ellison
./
July 18, 2025

So, you want to build an inventory system? Everyone starts somewhere. Many tutorials focus on the “drag and drop” flash, but neglect the core: managing the data efficiently. Forget flimsy solutions – let’s build a robust, grid-based inventory in Unity that’s actually ready for a real game.

Data Structures: The Heart of Your Inventory

The key to a smooth inventory is its data structure. Ditch simple lists! We need a 2D array (or a list of lists) to represent the grid. This allows for quick access to item slots.

public class Inventory
{
    public int width = 5;
    public int height = 8;
    public Item[,] items;

    public Inventory()
    {
        items = new Item[width, height];
    }
}

Each element in the array represents a slot. It will either contain an Item object or be null, indicating an empty slot.

Challenge: Array indexing errors. If you’re not careful, you’ll get out-of-bounds exceptions when accessing the array.

Solution: Always validate your indices!

public bool IsValid(int x, int y)
{
    return x >= 0 && x < width && y >= 0 && y < height;
}

public Item GetItem(int x, int y)
{
    if (!IsValid(x, y)) return null;
    return items[x, y];
}

This IsValid function is crucial. Use it everywhere you access the array.

UI Implementation: Drag and Drop Done Right

Here’s where the visual magic happens. Each inventory slot in the UI is a Slot object. This object contains a reference to the Item it represents, and handles click and drag events.

The biggest mistake beginners make? Directly manipulating UI elements based on world positions. This is a recipe for bugs. Instead, leverage Unity’s EventSystems.

  1. Implement IDropHandler and IBeginDragHandler on your Slot class.
  2. On BeginDrag, store the original slot and item data.
  3. On Drop, swap the item data between the dragged slot and the target slot in the data structure. Then update the UI.
public class Slot : MonoBehaviour, IDropHandler, IBeginDragHandler
{
    public int x;
    public int y;
    public Inventory inventory; // Reference to the Inventory data structure
    private Item _item; //The item scriptable object
    public Image image; //Reference to the UI Image component

    public void OnDrop(PointerEventData eventData)
    {
        Slot sourceSlot = eventData.pointerDrag.GetComponent<Slot>();

        //Only process the drop if the item exists
        if (sourceSlot._item != null)
        {
            //swap data in the backend
            Item temp = inventory.items[x, y];
            inventory.items[x, y] = sourceSlot.inventory.items[sourceSlot.x, sourceSlot.y];
            sourceSlot.inventory.items[sourceSlot.x, sourceSlot.y] = temp;

            //Update the UI
            UpdateSlot();
            sourceSlot.UpdateSlot();
        }
    }

    //Helper Function to easily update the current sprite and item.
    public void UpdateSlot()
    {
        _item = inventory.items[x, y];

        if (_item != null)
        {
            image.sprite = _item.icon;
            image.enabled = true;
        }
        else
        {
            image.sprite = null;
            image.enabled = false;
        }
    }
}

Pitfall: Forgetting to update the underlying data structure after a UI change. The UI should reflect the data, not be the data.

Solution: Always update the Inventory data structure first, then update the UI to match. This ensures data consistency.

Persistence: Saving Your Hard-Earned Loot

No one wants to lose their inventory on every game restart! Unity’s built-in serialization is… okay. But for complex data, it can be a pain. I advocate for a custom solution:

  1. Create a simple data transfer object (DTO) representing your inventory. This DTO should contain only data, no Unity objects or logic.
  2. Serialize the DTO to JSON. JSON is human-readable and easy to debug.
  3. On load, deserialize the JSON back into the DTO, then populate your Inventory object.
[System.Serializable]
public class InventoryDTO
{
    public ItemData[] items; //ItemData is a simple data transfer object

    public InventoryDTO(Inventory inventory)
    {
        items = new ItemData[inventory.width * inventory.height];
        for (int x = 0; x < inventory.width; x++)
        {
            for (int y = 0; y < inventory.height; y++)
            {
                Item item = inventory.items[x, y];
                items[x + y * inventory.width] = (item != null) ? new ItemData(item.itemName) : null;
            }
        }
    }
}

[System.Serializable]
public class ItemData
{
    public string itemName;
    public ItemData(string name)
    {
        itemName = name;
    }
}

//Example serialization using JsonUtility
public static class SerializationManager
{
    public static void SaveInventory(Inventory inventory, string filePath)
    {
        InventoryDTO dto = new InventoryDTO(inventory);
        string json = JsonUtility.ToJson(dto);
        File.WriteAllText(filePath, json);
    }

    public static void LoadInventory(Inventory inventory, string filePath)
    {
        if (File.Exists(filePath))
        {
            string json = File.ReadAllText(filePath);
            InventoryDTO dto = JsonUtility.FromJson<InventoryDTO>(json);

            //Load from item database to populate the Inventory.
            ItemDatabase itemDatabase = Resources.Load<ItemDatabase>("ItemDatabase");

            for (int x = 0; x < inventory.width; x++)
            {
                for (int y = 0; y < inventory.height; y++)
                {
                    int index = x + y * inventory.width;
                    if (dto.items[index] != null)
                    {
                        //Find in the item database
                        inventory.items[x,y] = itemDatabase.GetItem(dto.items[index].itemName);
                    }
                    else
                    {
                        inventory.items[x, y] = null;
                    }
                }
            }

            //Update the UI after loading
            //UpdateUI() is a function to force the UI to update and display the
            //items correctly.
            inventory.UpdateUI();
        }
    }
}

Common Mistake: Trying to serialize Unity Object types directly. Unity’s serialization handles these types poorly, leading to broken references.

Best Practice: Serialize only the data you need (item IDs, quantities, etc.) and then re-hydrate the Unity objects from a database or asset store on load.

Conclusion: Building a Solid Foundation

Creating a robust inventory system isn’t about flashy UI effects; it’s about a solid data foundation, clear separation of concerns, and careful attention to detail. By focusing on these principles, you can build an inventory system that’s not only functional but also scalable and maintainable for any game project. And that, my friend, is the real treasure.