Dealing with multiple types of items for an inventory

I’ve been working on an Inventory system for quite a while now. I have a basic version working with UI, dropping, picking up and stuff, but now I am trying to add weapon support to the inventory. This was my InventorySlot class:

public class InventorySlot
{
    public ItemSOitem item;
    public int amount;
}

This has a huge flaw though. It only supports one var: the amount of an item. Weapons also have bullets that are loaded into the clip and I need to store that at the item level since I obviously need to be able to move a weapon between inventories or even to the scene (drop it) and it needs to remember the amount of bullets in its clip. So I updated my code:

public class InventorySlot
{
    public ItemSO item;
    public int amount;
}
public class WeaponInvSlot : InventorySlot
{
    public bulletsInClip;
}

Now I have another problem. In my Inventory, I need to have a List of the base class → InventorySlot

public List<InventorySlot> slots;

So if I am dealing with a weapon, I would have to verify the type and then use ‘as’ to basically cast it to the right type to use the bullet amount

(slots[i] as WeaponInvSlot).bulletsInClip

Is there a way to circumvent this? The obvious answer would be the oh so great generic class or even better a generic interface . My new code:

public interface IInventorySlot<T> where T : ItemSO
{
    public int Amount { get; set; }
    public T Item { get; set; } // This could be replaced by "public T GetItem();"
}

public class InvWeapon : IInventorySlot<WeaponItemSO>
{
    public int currBullets;
    public int Amount { get; set; }
    public WeaponItemSO Item { get; set; }
}

// InvItem looks basically the same except the currBullets field ^^

Now I can inherit from that Interface and I’m able to use weapon.currBullets, great! But how do I store all the different items in the inventory now??? I wish there was a way to write this in my Inv class:

public List<IInventorySlot<T>> slots;

but the compiler doesn’t think that’s a great idea.

In theory I could use dynamic, but that’s plain stupid, so I’m back to the drawing board aka StackOverflow: “HOW TF DO I MAKE A LIST OF GENERIC OBJECTS”. The answer is to make a non generic base class/interface and then use that to make the list… BUT then I can’t put the generic item field in it, so I am not able to use blahblahblah.item anymore without casting first. So… I am back to my first problem.

I am basically going in circle and not solving any problem. What am I missing? There is no way everyone out there is just casting their items every time, right?? So what’s going on? I looked for advanced Inventory systems on YouTube and GitHub, but I wasn’t able to find a solution in the limited time I looked for it. (Probably missing something obvious, or what I’m trying to do is just stupid, not quite sure which one. And yes, I searched this forum, but I wasn’t able to find exactly this problem. I only found stuff like how to hande Use() on all different items)

The problem is that it is an EXTREMELY complicated problem surface. Interfaces can help for one, but only help simplify it a bit. You still have a ton of hard decisions to address first.

Using Interfaces in Unity3D:

Check Youtube for other tutorials about interfaces and working in Unity3D. It’s a pretty powerful combination.

These things (inventory, shop systems, character customization, dialog tree systems, crafting, etc) are fairly tricky hairy beasts, definitely deep in advanced coding territory.

Inventory code never lives “all by itself.” All inventory code is EXTREMELY tightly bound to prefabs and/or assets used to display and present and control the inventory. Problems and solutions must consider both code and assets as well as scene / prefab setup and connectivity.

Inventories / shop systems / character selectors all contain elements of:

  • a database of items that you may possibly possess / equip
  • a database of the items that you actually possess / equip currently
  • perhaps another database of your “storage” area at home base?
  • persistence of this information to storage between game runs
  • presentation of the inventory to the user (may have to scale and grow, overlay parts, clothing, etc)
  • interaction with items in the inventory or on the character or in the home base storage area
  • interaction with the world to get items in and out
  • dependence on asset definition (images, etc.) for presentation

Just the design choices of such a system can have a lot of complicating confounding issues, such as:

  • can you have multiple items? Is there a limit?
  • if there is an item limit, what is it? Total count? Weight? Size? Something else?
  • are those items shown individually or do they stack?
  • are coins / gems stacked but other stuff isn’t stacked?
  • do items have detailed data shown (durability, rarity, damage, etc.)?
  • can users combine items to make new items? How? Limits? Results? Messages of success/failure?
  • can users substantially modify items with other things like spells, gems, sockets, etc.?
  • does a worn-out item (shovel) become something else (like a stick) when the item wears out fully?
  • etc.

Your best bet is probably to write down exactly what you want feature-wise. It may be useful to get very familiar with an existing game so you have an actual example of each feature in action.

Once you have decided a baseline design, fully work through two or three different inventory tutorials on Youtube, perhaps even for the game example you have chosen above.

Breaking down a large problem such as inventory:

If you want to see most of the steps involved, make a “micro inventory” in your game, something whereby the player can have (or not have) a single item, and display that item in the UI, and let the user select that item and do things with it (take, drop, use, wear, eat, sell, buy, etc.).

Everything you learn doing that “micro inventory” of one item will apply when you have any larger more complex inventory, and it will give you a feel for what you are dealing with.

Breaking down large problems in general:

The moment you put an inventory system into place is also a fantastic time to consider your data lifetime and persistence. Create a load/save game and put the inventory data store into that load/save data area and begin loading/saving the game state every time you run / stop the game. Doing this early in the development cycle will make things much easier later on.

Generics != Inheritance/polymorphism. They are two completely different things.

If you need to use generics to get around problems with inheritance/polymorphism, then there’s a high chance you’re using one of the two wrong.

Think about how we use Unity. Polymorphism is used to allow us to have all different kinds of components. Game Object’s contain components, and it does not care whatever what kind of component they are. It’s completely indifferent. And when we need a particular component, we use GetComponent<T> that finds and returns the right component in an already type safe manner.

So if you have an inventory with different types of slots, why not just make a method to retrieve all the slots of the correct type in a similar manner that we do with components?

Ahh, I think that’s a great idea!

But it’s still pretty hard to get a specific index without first retreiving the type. Unlike components, the order is important here

I mean you can just write a simple query system that you can provide a predicate, and it gives you as much information about the result(s) as you need wrapped in a class/struct.

I’d like to know how you would know it was a weapon vs a non-weapon in the first place to even verify it?

Which is sort of the whole thing. If you don’t know its a weapon, how is the compiler supposed to know? If you can describe a way you do know it’s a weapon, well… that might be a place to start on trying to design your inventory system.

Going to your list:

public List<IInventorySlot<T>> slots;

In what way did you expect to access this list under the desired results you want?

If I accessed the first element in the list ‘slots’, how would I know if it’s an InvWeapon or an InvItem and if I could access the ‘currBullets’ field of it when it’s an InvWeapon? So even if the compiler let you do this we’re in the same boat again where you’d have to cast it to the type you need using the ‘as’ operator.

Could you give me a use scenario rather than your attempted design?

I’m just hanging out tonight. So I figured I’d come back and give you a general example of something that might do what you’re asking for. Or rather… something along the lines of what I’d do.

I’m going to preface that all code here is written slap dash, not in an IDE, and so therefore likely contains spelling mistakes or other minor bugs. Treat this as pseudo-code.

First off I personally separate my inventory items into a few parts.

ItemDescriptor

public class ItemDescriptor : ScriptableObject
{

    public string Id; //I actually use a guid for this and tether it to the assetguid,  but that's outside the scope of this discussion
    public string DisplayName;
    public string Description;
    public int Rarity;
    //add any other fields that are related to all items regardless of type?
    [SerializeReference] //you're going to need to write a custom editor for this...
    [SerializeRefPicker] //I already have my own custom property drawer for it...
    public IItemSlotState StateTemplate;

    public virtual IItemSlotState CreateState()
    {
        this.StateTemplate.ItemDescriptor = this;
        return this.StateTemplate.Clone();
    }

    public virtual ItemAvatar CreateItemAvatar(IItemSlotState state, Vector3 pos, Quaternion rot, Transform parent = null)
    {
        var obj = UnityEngine.Object.Instantiate(AvatarPrefab, pos, rot, parent);
        obj.Initialize(state);
        return obj;
    }

}

ItemDescriptor would be a ScriptableObject that acts as the source information about items. This can contain all the information about the item that is generalized. Id, name, description, other info unique to your game, and… a state template. This is going to be our next part.

Item State

public interface IItemSlotState
{
    ItemDescriptor ItemDescriptor { get; }
    bool Stackable { get; }

    IItemSlotState Clone();
    ItemAvatar CreateAvatar(Entity owner, Vector3 pos, Quaternion rot, Transform parent = null);

    bool AttemptStack(Entity owner, IItemSlotState other);
}

[System.Serializable]
public class ItemSlotState : IItemSlotState
{
    public ItemAvatar AvatarPrefab;

    public ItemDescriptor ItemDescriptor { get; set; }
    public virtual bool Stackable => false;
    public virtual IItemSlotState Clone() => this.MemberwiseClone() as IItemSlotState;
    public virtual ItemAvatar CreateAvatar(Entity owner, Vector3 pos, Quaternion rot, Transform parent = null) => UnityEngine.Object.Instantiate(AvatarPrefab, pos, rot, parent).Initialize(owner, this);
    public virtual bool AttemptStack(Entity owner, IItemSlotState other) => false;
}

[System.Serializable]
public class StackableItemSlotState : ItemSlotState
{
    public int Count;

    public override bool Stackable => true;

    public override bool AttemptStack(Entity owner, IItemSlotState other)
    {
        if (other.ItemDescriptor.Id != this.ItemDescriptor.Id) return false;
        this.Count += (other as StackableItemSlotState)?.Count ?? 0;
    }
}

[System.Serializable]
public class WeaponItemSlotState : IItemSlotState
{
    public WeaponAvatar AvatarPrefab;
    public AmmoType AmmoType;
    public int AmmoCount;
    public int MaxCount;

    public ItemDescriptor ItemDescriptor { get; set; }
    public virtual bool Stackable => false;
    public virtual IItemSlotState Clone() => this.MemberwiseClone() as IItemSlotState;
    public virtual ItemAvatar CreateAvatar(Entity owner, Vector3 pos, Quaternion rot, Transform parent = null) => UnityEngine.Object.Instantiate(AvatarPrefab, pos, rot, parent).Initialize(owner, this);

    public virtual bool AttemptStack(Entity owner, IItemSlotState other)
    {
        if (other.ItemDescriptor.Id != this.ItemDescriptor.Id) return false;
        var ammopouch = owner.GetComponent<AmmoPouch>();
        ammopouch.AddAmmo(this.AmmoType, (other as WeaponItemSlotState)?.AmmoCount ?? 0);
    }
}

The state is what represents the items in game. It’s sort of like your ‘InventorySlot’, though it serves more than just the slot information. I still maintained the name ‘slot’ in my example here to emphasize that relationship. Though really I probably would have named it ‘IItemState’.

Note that I have various implementations of the state for my different item types. And our ItemDescriptor at the top had a special property drawer for configuring out ItemDescriptor as the appropriate item type. When doing so the item type specific fields would become editable to the ScriptableObject for things like the ammotype and avatarprefab. Which leads us to the next thing.

Item Avatar

public class ItemAvatar : MonoBehaviour
{

    public IItemSlotState State { get; protected set; }

    public virtual ItemAvatar Initialize(Entity owner, IItemSlotState state)
    {
        this.State = state;
        //do other initializing???
    }

}

public class WeaponAvatar : ItemAvatar
{

    private WeaponItemSlotState _weaponState;

    public override void Initialize(Entity owner, IItemSlotState state)
    {
        if (state == null) throw new System.ArgumentNullException(nameof(state));
        if (!(state is WeaponItemSlotState)) throw new System.ArgumentException($"State for '{state.ItemDescriptor.DisplayName}' was malformed, confirm the ItemDescriptor is configured correctly.", nameof(state));

        base.Initialize(state);
        _weaponState = state as WeaponItemSlotState;
    }

    public bool FireWeapon(Entity owner)
    {
        if (_weaponState.AmmoCount <= 0) return false;

        var ammopouch = owner.GetComponent<AmmoPouch>();
        ammopouch.UseAmmo(_weaponState.AmmoType, 1);
        _weaponState.AmmoCount--;
        //perform firing action
        return true;
    }

    public bool Reload(Entity owner)
    {
        var ammopouch = owner.GetComponent<AmmoPouch>();
        _weaponState.AmmoCount = Mathf.Min(ammopouch.GetAmmoCount(_weaponState.AmmoType), _weaponState.MaxCount);
        //TODO - signal reload anim???
    }

}

Item Avatar is the visual representation of our object in the world. It’s the script attached to the prefab for the item that represents it. Say you removed the item from inventory and wanted to drop it on the ground… you might call CreateAvatar and just place it on the ground.

Say it’s a weapon. You might call CreateAvatar, parent it in the player’s hand, and then use it accordingly.

I sort of made the Fire/Reload methods part of the WeaponAvatar. Honestly… I’d probably compose that into its own distinct script without using inheritance like this. But I used inheritance just for brevity sake in this post. Again… this is more like pseudo-code to demonstrate the dependency inversion and structural ideas.

Then finally we’d have things like your ItemPouch:

public class ItemPouch
{

    private List<IItemSlotState> _items = new List<IItemSlotState>();
    private IReadOnlyList<IItemSlotState> Items => _items;

    public void AddItem(ItemDescriptor item) => AddItem(item.CreateState());
    public void AddItem(IItemSlotState state)
    {
        if (_items.Contains(state)) return;

        if (state.Stackable)
        {
            foreach (var item in _items)
            {
                if (item.ItemDescriptor.Id == state.ItemDescriptor.Id &&
                    item.AttemptStack(state))
                {
                    return; //the item was consumed by another stack, just exit now
                }
            }
        }

        _items.Add(state);
    }

    public bool RemoveItem(IItemSlotState state)
    {
        return _items.Remove(state);
    }

    public IItemSlotState FindItem(string id)
    {
        foreach (var item in _items)
        {
            if (item.ItemDescriptor.Id == id) return item;
        }
        return null;
    }

}

And that’s honestly the main gist of it.

You may notice I still use the ‘as’ in some place. Because honestly… it’s useful. A creationary pattern like this may have to validate its creationary steps at times. Sure could I have made it more strict… but the code would become even longer. You don’t need to avoid ‘as’ like the plague though, it’s useful in place.

But if I were to… I’d just shim in another interface like so:

public interface IItemAvatar
{
    IItemSlotState State { get; }
    GameObject gameObject { get; }
}

public class ItemAvatar : MonoBehaviour, IItemAvatar
{

    public IItemSlotState State { get; protected set; }

    public virtual ItemAvatar Initialize(Entity owner, IItemSlotState state)
    {
        this.State = state;
        //do other initializing???
    }

}

public class WeaponAvatar : MonoBehaviour, IItemAvatar
{

    private WeaponItemSlotState _weaponState;
    public WeaponItemSlotState State => _weaponState;
    IItemSlotState IItemAvatar.State => _weaponState;

    public virtual void Initialize(Entity owner, WeaponItemSlotState state)
    {
        if (state == null) throw new System.ArgumentNullException(nameof(state));

        _weaponState = state;
    }

    public bool FireWeapon(Entity owner)
    {
        if (_weaponState.AmmoCount <= 0) return false;

        var ammopouch = owner.GetComponent<AmmoPouch>();
        ammopouch.UseAmmo(_weaponState.AmmoType, 1);
        _weaponState.AmmoCount--;
        //perform firing action
        return true;
    }

    public bool Reload(Entity owner)
    {
        var ammopouch = owner.GetComponent<AmmoPouch>();
        _weaponState.AmmoCount = Mathf.Min(ammopouch.GetAmmoCount(_weaponState.AmmoType), _weaponState.MaxCount);
        //TODO - signal reload anim???
    }

}

Of course now all the ‘CreateAvatar’ methods would need to return IItemAvatar rather than ItemAvatar. Also now we have a component represented as an interface and any generalize properties for the interface need to be duplicated between the avatar types all to avoid an ‘as’ operator. But, it’s easily doable, and honestly might even be the way I would do it in the end. But I have various tools that glue interface and Unity together since its direct support for interface components is only half-way there IMO (for example, you can’t easily have a serialized ref to a component by interface type).

Thanks for your help and your examples. This helped me a lot = )