Press "Enter" to skip to content

How to build stackable inventory for a game in C#

Last updated on August 18, 2020

A while back, someone on Reddit asked how to write some C# code to handle the inventory for an RPG game, with the ability to stack similar items into the same inventory slot.

I wrote some code to do this, and added the ability for different types of items to be stacked to different limits, before they needed to fill another inventory slot. For example, you could say that plants can be stacked to 50 (per inventory slot), while potions could only be stacked to 10.

Here’s what I wrote to show how to do that.

Defining the inventory item classes

The base class for all inventory items is:

ObtainableItem.cs

using System;
namespace RPGInventory
{
    public abstract class ObtainableItem
    {
        public Guid ID { get; set; }
        public string Name { get; set; }
        public int MaximumStackableQuantity { get; set; }
        protected ObtainableItem()
        {
            MaximumStackableQuantity = 1;
        }
    }
}

The different types of items will inherit from this class. If you want the item to be stackable, you can set the MaximumStackableQuantity to a value other than 1 (which is the default).

Plant.cs – which can be stacked to 50 per inventory slot

namespace RPGInventory
{
    public class Plant : ObtainableItem
    {
        public Plant()
        {
            MaximumStackableQuantity = 50;
        }
    }
}

Potion.cs – which can be stacked to 10 per inventory slot

namespace RPGInventory
{
    public class Potion : ObtainableItem
    {
        public Potion()
        {
            MaximumStackableQuantity = 10;
        }
    }
}

Weapon.cs – which cannot be stacked, so its MaximumStackableQuantity is 1

namespace RPGInventory
{
    public class Weapon : ObtainableItem
    {
        // No constructor needed, since we want to use the base class default of MaximumStackableQuantity of 1
    }
}

This assumes that, somewhere in your program, you have a place to hold all the different types of items that exist in your game world.

So, somewhere, you’d have a static class (or some other thing that is always available, everywhere in your program), with lists of all the types of plants, potions, or weapons that the player could every encounter.

 

Adding an item to the player’s inventory

Now we need a place to store the player’s inventory.

For this, I’ve created the InventorySystem class. It has a public InventoryRecords list that contains everything the player currently holds in their inventory.

When we want to add something to the player’s inventory, we call the AddItem method, passing in the item object, and the amount you’re trying to add to the player’s inventory.

In the AddItem method, we do this by trying to find an inventory slot that already contains an item with the same ID as the item passed into the method, and hasn’t reached the maximum stack size. If we have one, we put as much in that inventory slot as possible. If we reach the limit, and still have more to add, or if there isn’t a partially-filled slot with that item type, we try to add the remaining quantity to another slot. If there isn’t another slot available, we throw an exception.

Throwing an exception isn’t what you really want to do, because those are really used for breaking errors – and this is not really an error.

It would be better to create and raise an event for OnInventoryCannotAcceptMoreItems, passing it the item and remaining quantity. With that, your user interface could display a message such as, “Your inventory is full, and you are unable to pick up 3 healing potions.”

 

InventorySystem.cs

using System;
using System.Collections.Generic;
using System.Linq;
namespace RPGInventory
{
    public class InventorySystem
    {
        private const int MAXIMUM_SLOTS_IN_INVENTORY = 10;
        public readonly List<InventoryRecord> InventoryRecords = new List<InventoryRecord>();
        public void AddItem(ObtainableItem item, int quantityToAdd)
        {
            while(quantityToAdd > 0)
            {
                // If an object of this item type already exists in the inventory, and has room to stack more items,
                // then add as many as we can to that stack.
                if(InventoryRecords.Exists(x => (x.InventoryItem.ID == item.ID) && (x.Quantity < item.MaximumStackableQuantity)))
                {
                    // Get the item we're going to add quantity to
                    InventoryRecord inventoryRecord =
                    InventoryRecords.First(x => (x.InventoryItem.ID == item.ID) && (x.Quantity < item.MaximumStackableQuantity));
                    // Calculate how many more can be added to this stack
                    int maximumQuantityYouCanAddToThisStack = (item.MaximumStackableQuantity - inventoryRecord.Quantity);
                    // Add to the stack (either the full quanity, or the amount that would make it reach the stack maximum)
                    int quantityToAddToStack = Math.Min(quantityToAdd, maximumQuantityYouCanAddToThisStack);
                    inventoryRecord.AddToQuantity(quantityToAddToStack);
                    // Decrease the quantityToAdd by the amount we added to the stack.
                    // If we added the total quantityToAdd to the stack, then this value will be 0, and we'll exit the 'while' loop.
                    quantityToAdd -= quantityToAddToStack;
                }
                else
                {
                    // We don't already have an existing inventoryRecord for this ObtainableItem object,
                    // so, add one to the list, if there is room.
                    if(InventoryRecords.Count < MAXIMUM_SLOTS_IN_INVENTORY)
                    {
                        // Don't set the quantity value here.
                        // The 'while' loop will take us back to the code above, which will add to the quantity.
                        InventoryRecords.Add(new InventoryRecord(item, 0));
                    }
                    else
                    {
                        // Throw an exception, or somehow let the user know they are out of inventory space.
                        // This exception here is just a quick example. Do something better in your code.
                        throw new Exception("There is no more space in the inventory");
                    }
                }
            }
        }
        public class InventoryRecord
        {
            public ObtainableItem InventoryItem { get; private set; }
            public int Quantity { get; private set; }
            public InventoryRecord(ObtainableItem item, int quantity)
            {
                InventoryItem = item;
                Quantity = quantity;
            }
            public void AddToQuantity(int amountToAdd)
            {
                Quantity += amountToAdd;
            }
        }
    }
}

 

7 Comments

  1. Max
    Max December 17, 2018

    Very nice tutorial! I’ve seen some before but this one is very good at explaining how to subclass items like this and properly implement an inventory system with stacking. It’s also done in a way that makes it easy to expand upon, thank you very much 🙂

    • Scott Lilly
      Scott Lilly December 17, 2018

      You’re welcome! I’m glad to hear it helped.

  2. KurtM
    KurtM January 10, 2019

    Thanks for the tutorial. I have an inventory system similar to this, which means I’m on the right track.

    How would you randomly populate this inventory with items? Such as a weapon store with random weapons.

    I looked into the factory pattern, but most of them use a switch statement based on an items’ unique ID from a database/dictionary and then return the new ObtainableItem instance, which can then be added.

    This is fine, but it won’t allow the game to be moddable, because you would need the source code to add new items to the factory switch statement… Do you know of any tutorials that show how to do this in an abstract way?

    • Scott Lilly
      Scott Lilly January 11, 2019

      You’re welcome. Let me know if you have any problems with it.

  3. Corey
    Corey August 11, 2021

    I know I’m late to the party, but where and when would you go about setting each item’s Id?

    • Scott Lilly
      Scott Lilly August 12, 2021

      In a real program, I’d probably pass in the ID to the constructor and set the property inside the constructor.

Leave a Reply

Your email address will not be published. Required fields are marked *