Tips for making an inventory system?

As a beginner/intermediate blueprinter, I’ve tried multiple times and every time it ends up with a tangled mess that I have to throw out and start over.

I just want to make a simple inventory for a survival horror game (limited slots, use items, etc) I can handle all the other logic but just getting the ■■■■ items to appear in an array is a struggle for me.

I am gearing up to try again soon but on the off chance any of you black magic programmers have any insight of what to do/not to do, I’d really appreciate it.

Also I will be using common UI for sanity.

Here is a sample of the inventory layout I’m planning to make if that helps: https://imgur.com/a/js28uCO

You can use a WrapBox to lay out widgets in a grid:

Create a new widget class called something like InventoryItemWidget that has a button and a texture. Write your logic for what happens when the widget is clicked. Update the texture when a new item has been added. Behind the scenes have a data struct for information.

Inventory requires planning all the way from the start.

There are at least three different directions it needs to integrate:

  1. World Actor: does the inventory item turn into an actor in the world? does an actor in the world turn into an inventory item?
  2. Stacking/Combining/Mutating: does the inventory item “stack” if you have more than one, or is each one unique? if they “stack” do you also have “half consumed” items?
  3. Display/Limits: does the inventory item show up in the inventory list/grid? does it take up more than one slot, maybe in some particular shape? is there a weight limit or volume limit across all items?

Depending on your choices (especially “are there half-consumed/damaged items?”) you will need slightly different solutions.

However, the most important design decision, is to split “what is the template of an item” versus “what is the current inventory holding of an item” versus “what is the in-world actor of an item.”

The “template” is a row in a data table. It contains text and number values like “weight” and “durability” and “description” and “name” and so forth.

The “inventory holder” is a struct or UObject, not an AActor. It either copies the values from the data table, or it references the data table row using ID. It adds inventory-specific information, such as “current stack count” (if stacking) or “current location in inventory” (if you support re-ordering the inventory) or “actor currently in the world” if it’s something like a mount that you can recall. If there is damage/durability/use, this struct/object also holds how much damage/use it’s seen.

The “in world actor” is an actual AActor, which is typically a blueprint instance. The actor to create when “using” or “dropping” an inventory item, is an object class reference stored in the inventory holder and/or template row. Typically this actor will also get a copy or reference of the inventory and template definition, so you can have a “10 pound mushroom” and a “1 pound mushroom” share the same actor blueprint.

Whether you remove the inventory holder when you drop an item in the world, is up to you, depending on how you want the game to play.

All of this also needs to integrate appropriately with your save-game system. The neat thing is that unchanging properties, that live in the template row, typically don’t need to be saved, only the ID of the template row is needed. Meanwhile, the inventory struct/UObject will store all mutations/modifications.

Once you have these pieces wired up and working, adding a sensor overlapping ball in front of the player, that looks for some IPickup interface or maybe a blueprint that subclasses your own APickup actor class, and displays the appropriate “pick up?” screen overlay, is pretty straightforward. Similarly, once you have the set/array of all inventory holder items, displaying that in a widget should also work out OK.

2 Likes

game design is hard, not because any given thing is “hard” perse (some things are hard, but most are just a bunch of relatively simple steps), the real “hard part” has to do with what seems like a “simple” decision is actually 30 knock-on-effect decisions in a trench-coat. for more details on this you can look at “Game Design” + “The Door Problem”, “The Ladder Problem”, or “The Stairs Problem”; although these are more or less examples.

the biggest issues with an inventory itself (it can just be an array of structs UObjects at the worst, but most games don’t need to go beyond an Array of Structs, an Actor is way to heavy, and creates a lot of headaches especially if there are loading transitions), but the actions an inventory needs to do, and the questions attached to it:

  • is the number of items to be held known? (or at least the maximum)
  • are any of the items not unique (this mostly applies to like point and click adventures, but can be usable for things like KeyItems, as you might not need an “item” specifically but just a “bitflag”)
  • can items be stacked?
  • can items be manually moved by the user? does the order matter?
  • do items have a condition, durability?
    • can items with similar condition, durability be stacked?
  • can Items be dropped/discarded?
  • if items can be dropped/discarded can this put the game in a soft-lock state? Is that OK?
  • if Items can be dropped/discarded/sold can only one item be done at a time, or multiple at once?
  • can items be lost in some other way?
  • are items added to the inventory in batches, or one at a time? can multiple of the same item be added at once?

depending on how hard you want to go into “Single Responsibility Principle” and interfaces is effectively your difficulty selection for the feature, and as a “Beginner/Intermediate” developer (do not constrain your abilities to your programming language, and yes Blueprints is programming you just get boxes, and lines instead of structured English and those random symbols on the keyboard).


a tangled mess means that you have a deeply integrated system, and that isn’t a bad thing, heck a deeply integrated system is less “reusable”, but also easier to reason about because you don’t have to jump between multiple elevations of abstraction to follow a function chain, and get to the goal, especially when you are starting out/learning.

If you are talking about how “tangled” your lines of spaghetti are in the blueprint graph, that is a “feature” of the language, and why you can hover over a pin to see where the line on the graph goes, or put comments in the graph as breadcrumbs for your future self

Comments are for helping other team members understand what a thing is doing, and whether you’re on a large team, or working by yourself the “other team member” is likely to be yourself 6 weeks from now.

a “basic” inventory system (with minimal prescriptions) :

  • a component class for the inventory itself.
    • is a component so it can be owned by an actor.
    • has an array of “itemStruct”, or UObject, you will need at least “add(Item)”, “remove(Item)”, “check(Item)”, “Use(Item)”, and maybe a “merge(Inventory)”
  • an actor class for the items that exist in the world, and maybe for the item as it exists being held.
    • will probably also have its own inventory, though if you go with interfaces then the actor might be the inventory
  • a DataTable, or collection of DataAssets that are the detailed definition of the items

the bare minimume for the struct or UObject (struct if you want a unified what an item does in some manager, the inventory itself, or on the thing that “uses” the item, but UObject allows for the item to have scripting on itself)

  • itemID: this could even be just an integer; instead of “Thor’s Mighty Lightning Axe” it can be just “15”, and then in the DataTable the rowName can be 15, and then the DataTable has a member with the “DisplayText”, for DataAsset it would be “Item_15”.
  • the other stuff that is needed for a specific item in the inventory.

a more advanced “cute” implementation if items cannot stack then the Inventory could just be an Array of Integers (the “ItemID”), or for items that can stack a TMap<Int ItemID, int Quantity>, then your dataTable, or collection of DataAsset would have all the details. again this a a more advanced, and needs to be weighed heavily against quantity and frequency of lookups.

the bigger the amount of information in the struct or UObject for the item in the Inventory the more on hand the data is when you need it saving a lookup, while the opposite will lead to “slower” lookups, but the Inventory and the array has a lot less duplicated data with a smaller memory footprint as they are running around the world which could help in other ways