The big inventory problem - runtime data

To make things short, I am an experienced unity dev (8+ years experience, wrote Behaviour Trees, GOAP and such stuff from scratch).

How do you handle runtime data in your inventory systems? Things like durability, fill amount of a potion/bottle, items that can only be used once every two minutes, …

Let’s say we have a big inventory, something like a skyrim inventory at it’s limit. 3000 items.

Each item has a Type, represented as ScriptableObject (icon, description, reference to the Prefab).

Obviously we won’t copy those ScriptableObjects because it would create a lot of useless data, instead we just reference them in our PlayerInventory : MonoBehaviour.
This means, we can’t save runtime data in our Item : ScriptableObject.

Also we can’t save it in our ItemInstance : MonoBehaviour (which is attached to the GameObject representing the Item as 3D Object), because we would have to keep an instance to all those 3000 items in memory, just to alter some Data.

Which leads to the big Question: Where should we store runtime data?

An obvious “place” would be the PlayerInventory : MonoBehaviour. Maybe we have some Slot class there, containing a reference to the Item : ScriptableObject and int amount; - but we can’t extend out Slot class to contain all the possible runtime data of all our items…

We could use a shared dictionary, where each of our items could read/write from/to, but this would lead to a lot of magic strings and overall bad performance (when called every frame).

We could create our own component pattern inside the Slot class, using plain C# Classes and the [SerializedReference] attribute - but renaming such a class would break all those references (unless this is already fixed by unity?).

I have no answer. Best I can think of is using ScriptableObjects for the custom component pattern and copy them for each item that needs runtime data.

But I wonder if anyone has a better solution? (maybe avoiding the (small) overhead of ScriptableObjects?)

Such Items are by definition non stackable in the usual sens, which hint you to the fact that you can store these generic infos into either the slots or in impostors (semi instanced items that act as aliases). Whether you want it or not, if you got 3000 swords with different current integrity values to save, you WILL have to save those values in some form or another.

If I understand you correctly you are asking about how to store the runtime item data for convenient access, possbily in the Update() loop, right? Have you profiled this? Where is the problem exactly? Do you have very complex queries on your item list every frame or what is slowing you down? Are these queries really necessary every frame?

What I like to do is to make an Item class (extending nothing) which has some logic (whatever your item needs to do, implement some interfaces, …). To this Item I then also add a link to an ItemData class (extending nothing) which then has three purposes:
a) keep some “type” info which allows it (or the ItemFactory in my case) to find the config (the SOs in your case)
b) keep the runtime data in serializable form or provide methods to convert it to a serializable form (for me that’s usually just a couple of floats and ints)
c) keep and int ID which is unique for every item (created by the ItemFactory in my case)

My slots (and pretty much the entire game) only ever have a ref to Item objects (or interfaces), not the ItemData or config.

Doing it that way I can save it and keep a permanent ref (ID) to every item, even across sessions. At the start of the game I just hand the deserialized ItemDatas to my ItemFactory which the spits out a Collection with ready made and configured items. Using the ID as key for a Dictionary (or whatever matches your queries best) should work just fine I think, even for 3k items.

So the gist is, don’t store your item data in MonoBehaviours or SOs. If you really have a lot of items you could think about using a database but my guess would be that 3k items is way below that limit.

I admit, I still am not entirely sure what the problem is. But I hope this helped.

Not a real problem per se, I don’t have 3k items right now, it’s just, I wrote like a dozen different inventory systems over the past years, re-inventing the wheel over and over and all of them have their limitations and are bound to the specific games they were made for.
I started building a libary of tools and I want to create an inventory system that is extensible, that supports stacking, runtime data, limited or unlimited space, so I just have to write it once and it works for any game I make.

Which lead to the problem of “how to store runtime data without having all items in memory” - A factory seems indeed very handy. I think I’ll go with this approach. BUT I will also use instantiated scriptable objects for things like durability, because it’s easier to draw them in an editor window and it’s very unlikely that all (theoretical) 3k items will need this kind of runtime data.

Queries is another interesting keyword - of course I need fast access methods to get the total amount of a specific item (ammo for example) or all magic stones that are useable right now. But I think I will just go with a dictionary for now and leave more complex approaches up to profiling, when needed.

1 Like

That’s kinda how inventory systems are. The inventory in Call of Duty won’t suffice for Skyrim.

Conversely if you had a “Hannibal Leo Inventory” module based on what you’d need for Skyrim, trying to use it in Call of Duty would be a pretty awful game experience.

Every decision you make affects what the inventory system will do and how it does it.

Here’s my inventory blurb:

Inventories are fairly tricky hairy beasts. They contain elements of:

  • a database of items that you can possess
  • a database of the items you actually possess
  • persistence of this information to storage between game runs
  • presentation of the inventory to the user (may have to scale and grow)
  • interaction with items in the inventory
  • interaction with the world to get items in and out
  • dependence on asset definition (images, etc.) for presentation

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

  • can you have multiple items? Is there a limit?
  • 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?
  • can users substantially modify items with other things like spells, gems, etc.?
  • does a worn-out item (shovel) become something else (like a stick) when the item wears out?
  • 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.

Or… do like I like to do: just jump in and make it up as you go. It is SOFT-ware after all… evolve it as you go! :slight_smile:

Breaking down a large problem such as inventory:

https://discussions.unity.com/t/826141/4

1 Like

Aha, haven’t we all tried (and failed) to reach that goal?

Personally I have given up on trying to achieve that. I rather keep them as minimal as possible so that I don’t have to redo the very basics every time. From there I build up the game specific solution. Anything beyond that becomes “functional” extensions, more like a toolkit instead of a framework.

It’s a tough balance to strike. What do you add to your default (reusable) implementation, what do you keep out. How much bending do you perform in your code to make it extensible (and with it more complicated). I don’t envy your position. But I admit, I enjoy dreaming of the one-lib-to-rule-them-all solution. It’s so much fun to think about. Until reality strikes you down :smile:

A lot of a games personality is in the user interface a one size fits all doesn’t really work. Good for prototyping. But for the artistic product at the end, unless it had been a specific theme throughout your game releases, you wouldn’t want the games feeling similar to each other as a finished product unless it’s a sequel.

Well it works for other systems already, like the “Logic Graph” which I wrote to setup puzzles. As well as the Behaviour Trees (Performance is still WIP), Dialogue Trees - and I think the same can be archived for the Inventory System.
Just recently, I created a forum thread to list all my tools.

Because at a very basic level it’s just ScriptableObjects (ItemSO) linked to GameObjects (ItemInstance) with a MonoBehaviour (Inventory).
They can be added / removed at the inventory script, persistance → so a save data class is generated which can then be saved by any system that can save and load C# classes - and those things are true for all inventories in all games.

The “Store” method may needs an override when the inventory is bascially a tetris game like in the Resident Evil Games.

But everything else? Categories, a dynamic description text, Tags, Weight - all those things can be Modifiers and some modifiers can be runtime modifiers.

The UI has to be built for each individual game, but the basic system of having an inventory where (stackable) items can be added/removed, saved/loaded, with localized descriptions and an Editor to create/alter them - is something I can create once for all my games.

1 Like

Nice list of handy tools you got there :slight_smile:

I didn’t mean to say it’s an unworthy endeavor. I think the requirement to “write once” and “it works for any game I make” do not play well together. At least not if the system you build gets sufficiently complex. It would require you to know which games you are making in advance. But I guess I have been a bit too pedantic and got hung up on you using the words “write once” and “any game”, my apologies.

Having dicussed this a couple of times with colleagues I think in the end you would realise that any none trivial implementation automatically includes (has to include) some assumptions about how the game works and thus excludes some other possibilities. The engine itself already is a compromise full of assumptions and you decide how many more assuptions you make by building the tools for your games.

What I meant to convey in my first response was that, at least for me, it’s about where you make the cut between reusability and getting things done for the concrete game. Thinking about where that cut-off point is first instead of trying to build the one tool to rule them all is something I found very helpful in the past.

I know what I say may sound nebulous but it’s the most accurate description of the experience I had with these things.

For the sake of an inventory, any list of gameobject will do. Having the inventory be objects on the screen for example sprites, that fix to nodes in a grid, or are displayed in a list of strings, or are sorted by category. It all boils down to which features you want for the specific game. I could see the value in a pack of inventory scripts. Where we are dealing with single scripts, modular of different basic inventory starter codes. But in the real world,

an inventory is just a list containing gameobjects. And That list could be cycled through with arrow keys. It’s still an inventory in four lines of code. Is no trouble extending four lines of code.

So you don’t wanna check for int i inventory or for each obj in inventory. You just wanna go currentindex + 1. And grab one item at a time as the selected. There in you can have an inventory of any capacity. But if you want to display entire inventory on screen at one time and it’s huge. And you iterate a list of 3000 to acquire the onscreen content. Then you might face lag problems when opening it. But a 3000 capacity single list can be iterated through 1 by 1 instantly. Or 5 by 5 for example: and then the content displayed on screen in HowManyResultsPerPage. This let you have a huge list.

That’s the thing xD Right now (this was a jam game) items are just GameObjects and each item is unique. A “list of gameobjects” is not good enough, it has to be scriptable-objects in order to be stackable and in order to work with save/load.

Displaying them is not an issue at all - we use the UI Toolkit, so we can just use ListView and Unity does the job for us.

Yea, true - but those things are basically frameworks. I can build “any” AI with the Behaviour Tree System because I have to write the nodes myself, the package only contains the basic system (accessing data, editor tool to create and link nodes, basic Save/Load Method, very common nodes like “Start”, “Selector”, “Sequence”).

Same for the Logic Graph, it contains basic accessors to variables, math node and everything else is up to the specific implementation.

And the inventory will contain an example UI, example items/modifiers but I can extend it infinitely just by using ItemModifiers (small ScriptableObjects which add values to items) as well as MonoBehaviours on the actual GameObject Item.
So the basic system is:

  • Item Database (a list of all scriptable objects with an unique ID)
  • ItemSO (Scriptable Object representation of an item, has a List(), reference to Prefab, localized Name/Description, Sprite, ID)
  • ItemInstance (MonoBehaviour, knows about it’s ScriptableObject)
  • Inventory (has a List slots (ItemSO, amount), Methods to Get / Store / Pick / AmountOf / Delete Items - can be derived / extended)
  • ItemModifierSO (abstract ScriptableObject, derived classes can contain any data and can override the “wantUpdate” Method to get copied at runtime and receive an Update() call for runtime modifications like durability, once they are added to an inventory (with a specifc flag? … maybe …))
  • UI_Inventory (MonoBehaviour on the UI that get’s a reference to the inventory to draw and nothing more)
    – UI_InventoryExample (derived from UI_Inventory, draws item sprites on a grid with a tooltip showing the name & description)
  • Editor Window to create/modify them
  • Save&Load classes to persist inventories

And that’s it. This should work for pretty much anything related to Inventory in games, a chest would have an Inventory, the Player would have one, NPCs can have one and so on.
Well, for a multiplayer game, things could get more complicated …

So the cut-off-point is at the basic funcionality level. Having enough options to extend the system, UI that can be overridden, Inventory class that can be overridden (tetris storage, multiplayer), I can’t imagine a game where this system would fail. (with still a lot of classes that can’t be overridden)

I have a stackable inventory system in User interface using layout group components, c script. Stackable to the extent

you could have 500 capacity per resource in the inventory slot. So it would absorb remove and create stacks of item when a spare was produced. Say I had 350 in my stack of type name A, I throw 350 more into the stack of type name A. it generates the spare and auto place it in the original inventory slot leaving me with full stack and spare. The type was just a string on a component script added to the inventory object. The objects also had real world, and inventory representation that differed. It had real world sprite and then an inventory sprite.

Using a vector 2 to decide on whether a slot was full or not.

you could also right click to split the currently held inventory stack into two amounts of 50%

items were not allowed to be automatically picked up when inventory was full

For non unique items (not stackable, single-use items, etc) you can just reference them by database index/id and stack size - just two ints! For unique items you can reference them the same way but you have to store their unique data too, for instance durability. Doing so isn’t a big mystery, there’s plenty of tutorials floating around on saving data.

My solution to such a problem was to make a wrapper POCO class which makes up the intermediary between the game and the item. Each wrapper stores a reference to the scriptable object item, and has a list of ‘meta components’ which define unique data. I posted the code for the wrapper here in a thread about the same subject .

There’s definitely some challenges with the approach. Comparing if two wrappers are the same requires you to overload the == and != operators and implement IEquatable with the wrapper, and all the meta components too. But once that was sorted, the system is pretty much flexible enough to encompass any additional or unique data you want per item. Though I am heavily relying on Odin to make the solution possible as well.

In my game, whilst not 3000 has a few hundred

(sorry this was meant to skip till 13:34 where it shows some items, So I assume you cant post youtube clips here at a specific time)

I split it up into 3 classes

public class ItemTemplate eg short sword, health potion, how much damage etc

public class ItemMB : MonoBehaviour // the unity visual representation of the item

public class ItemInfo // unique for each item, i.e. what template it uses where it is etc

So the only data I save is the list of ItemInfo items