Item system: how to structure/store data

Hello,

I am currently developing an item system that should work similarly to a game like Diablo.

The last few days I have done some research on how to structure such a system, but I’m still a bit unsure. So to make sure my planned approach is solid I’d like to present it here and get some feedback.
I have not looked much into data assets since for now I would like to avoid using C++, but if they would be the best way to go, I would absolutely look into them.

This is the current plan:

  • a separate object for every item type (weapon, armor, consumable, etc.) that holds the following information:

    • a data table row handle to get the (default) item values from a data table
    • a struct that holds all the data for this specific item type; this is needed in addition to the row handle, because items don’t always use the static values from the data table, e.g. when a weapon gets created some values (e.g. damage) might be randomized and the actual values then get saved in the struct
  • a single base item struct that holds data that all items have in common (regardless of type), e.g. inventory icon, name, description, static mesh, etc.

  • a separate struct for every item type that holds:

    • the base item struct
    • all variables that are needed for this specific item type
  • a single item actor for all items so items can be seen/picked up in the actual game world (and not just in the inventory) that holds the following information:

    • an item object
    • static mesh
  • a single item manager object that handles functionality for all items (e.g. what should happen when the player uses a consumable)

Some things I’m still not sure on how to handle:

  1. Let’s say a certain weapon (e.g. shotgun x) should get a damage value in the range of 10 to 15 on creation. Where should that damage range (DamageMin: 10, DamageMax: 15) be stored? In the same weapon item struct that holds the actual values? But that would mean that each actual created weapon item would hold a lot of variables that are ultimately unnecessary, since these values are only ever needed when the item is created. So ideally these value ranges for randomizing should be stored separately, no? How to handle this properly?

  2. This system would use nested structs. Like described above each item type struct would hold the base item struct as a member. In my readings on forums/reddit some people mentioned problems with nested structs and that you should therefore try to avoid them. E.g. the second post from user Memetron69000 here: https://www.reddit.com/r/unrealengine/comments/160mjkx/how_reliable_and_scalable_are_the_data_tables/
    Any thoughts on that? If you were to avoid nested structs, you would have to store all the variables for the base item struct in each item type struct, which seems kind of annoying and unflexible.

I’m grateful for any inputs.
Thanks!

Best regards,
David

Your approach seems solid, but here is how I would do it. This is in no way the only way to do it, nor is it the best.

What I would do is use OOP and implement a base class called Item. It will hold data such as the item name and size, its position in the inventory, and a few functions to move it from slot to slot and drop it on the ground.

Then from there, I would look at the items I want to add to the game and think “What categories do they fit in?”. In your example, you mention weapons, armor, and consumables. In this case, I would go for two main categories: Gear (any item that I can equip), and Consumable (any item that I can consume).

Looking at the Gear category, it is still a little generic. It contains stuff like weapons, armor and jewellery. So I would create a few subclasses: Weapon, Armor, and Jewellery.

Here is a little diagram of what it looks like:

Item
├─ Gear
│  ├─ Weapon
│  │  ├─ MeleeWeapon
│  │  │  ├─ Axe
│  │  │  ├─ Sword
│  │  ├─ RangedWeapon
│  │  │  ├─ Bow
│  │  │  ├─ Shotgun
│  ├─ Armor
│  │  ├─ ...
│  ├─ Jewellery
│  │  ├─ ...
├─ Consumable
│  ├─ ...

In this example, a Shotgun can do everything that a RangedWeapon can do and more. A RangedWeapon can do everything that a Weapon can do and more, etc…

This approach is easy to scale, and has a few advantages like being able to disregard something as a bow or shotgun and only consider it as ranged weapon if needed.

Disclaimer: I mostly use C++ (99%) and I am not sure this is possible to do in only blueprint.

1 Like

To add on to your questions:

  1. You can handle the random damage roll inside your class constructor (that would be the blueprint’s construction script). You can have a variable or a simple constant for the bounds. This way multiple instances of the same class will still have different rolls.
  2. My approach does not use structs, and relies on inheritance to access data from more generic classes.

In my game i use a struct to store the data asset and quantity

From the data asset i can get the stack size allowed per item, the actual mesh, name etc whatever i put in there, even an enum for the type

So your inventory would look like an array of these structs that then have the item data based on type and how much of it… you could add more information to the struct that you may need but essentially its all avaliable to you by dragging off of item or quantity, the struct only holds the values that will change, ie quantity or item damage etc

This Item class would be of type Object, right?

I don’t get how you would go about creating individual items with your solution.
Let’s say we want to create 2 different shotguns, a “Legendary Shotgun of Doom” and a “Crappy Hillbilly Shotgun”.
With your solution you would create 2 different child classes of the shotgun class? So every single item in the game has its own class?

Items are a big focus of this game, so there will be a lot of them. I’m not sure if this approach is good in that case. But maybe I misunderstood you.

i use an array of ‘Attributes’ so you can dynamically add/remove what you need rather than have a massive struct of potentially unused data.

the attribute is a struct that could be something like
GameplayTag - Damage
min - 10
max -30

i recommend against massive nested structs because they are very prone to break in UE

Considering that inheritance of structures in Unreal does not always work well (if you make changes to structures already in use), I would make a database with several Data Tables.
A separate table for basic information (name, description), a separate one for weapon information (damage, type of cartridges), a separate one for random parameters (?), etc.

If you make a widget utility to work with all the necessary tables at once, synchronizing names will not be a problem.

In the base class of the item you only need to store the “row name”, using which you can get all the information about the item. An item MUST NOT contain structures with all the information about it, except for those parameters that will be generated randomly.

I was thinking about separating the base item data from the item type data in different data tables before.

I would prefer the solution with the nested structs, because the workflow of creating a new item would be much nicer: you would just create a single new entry (row) in the specific item type data table and be done with it.
While with this solution, you would have to create a new entry in 3 different data tables (at least for items with randomized values; 2 otherwise).

But if nested structs are really so problematic in UE, maybe it is the way to go.

i’ve been meaning to test if FInstancedStructs are more stable which they should be since you’re not editing a massive struct.

So create a struct of base item data with optional InstancedStruct array and you can add/remove them as desired, ie armor data, weapon data. etc

There is another problem with this. For example, if you need to get only the name of the item - you will not be able to get information from the data table by accessing the base structure (at least in Blueprint it did not work), you always need to know exactly which child structure was selected for the table and get data for this structure (or did I do something wrong?).

If it is a blueprint structure, then everything is usually (but not always) fixed by refresh all nodes in broken blueprints.

For C++ structures it’s worse - when you recompile the source code, you get a new type of your structure, and the old one is now called REINTS Struct Name, and although it may be identical to the new one, they are not compatible. And now you have to manually change the type in all variables to the new structure and replace all nodes make/break/set member struct (or am I missing something again?).

Yes, Item would inherit from UObject.

Now for your question about individual items of the same type. Assuming they both function the same (same gameplay, different stats), you can simply create 2 Shotgun objects with different attributes:

Legendary shotgun:

  • Name: “Legendary Shotgun of Doom”
  • Rarity: Legendary (enum)
  • Damage roll range: 200-300
  • etc…

Common shotgun:

  • Name: “Crappy hillbilly Shotgun”
  • Rarity: Common (same enum)
  • Damage roll range: 10-15
  • etc…

Now one thing I did not really consider when writing my original response is: what if you wanted to spawn a certain predetermined type of shogun?

Let me take Path of Exile as an example. In this game, you have various item bases and rarities. Let’s say for instance a mob drops a “Simple Robe”. This is a certain kind of armor, with predetermined stats. It also has Unique (Legendary in Diablo IIRC) variants that have special stats.

In that case, you can store the different base types in a table (for example). An entry could look like: { "ShotgunName", Rarity, DamageRollRange, ... }. You can simply call the constructor and give it the table entry as parameters to create your item.

This way you got a simple and scalable way of creating any item, while still being able to easily spawn specific already defined items.

However, if you wanted to create a Unique/Legendary item that functions very differently compared to other items of its class, for example, a Flamethrower Shotgun that shoots a continuous beam of fire instead of multiple pellets, you would need to create a specific class inheriting from Shotgun and overriding some of its functions.

(Edit: you could also change a few parameters like multishot and fire rate to make it seem like a beam while it is just a very fast firing single shot shotgun)

If you are willing to give me more details on how you plan to generate, use, and store (inventory) your items in your game, I wouldn’t mind giving you better suited examples and advice

1 Like

Thank you for your detailed reply and sorry for my late response.

I don’t like the idea of having to create a separate UObject for every single item in the game.
It sounds like a nightmare for tweaking values, e.g. if you want to balance all the shotguns in relation to each other. You would have no overview of all the values of all the shotguns in a single place.

I am using this structure for now:
A data table DT_ItemsBASE where ALL items need to be inserted; holds only variables that all items have in common

Additional data tables for each item type; right now I have these tables: DT_Firearms, DT_Ammo, DT_ConsumablesHealing

This is the table for firearms for example:

So if you want to create e.g. a new shotgun, you have to add a new row in DT_ItemsBASE and in DT_Firearms with the same row name

In addition I have a different UObject for each item type.
The UObject for firearms e.g. holds the following:

And the InstanceData struct for a firearm holds the following:

I still have 2 problems with this solution:

  1. If I change the row name of an item in a data table, all the assigned items in the game don’t update accordingly, i.e. the row name of the data table row handle does not get updated. How to solve this issue?
    I hope it’s clear what the problem is; if not, I can try to explain it with additional screenshots to make it more clear.

  2. If I export a data table as CSV and open it in Excel, it doesn’t work properly. All the data of a single row is in a single cell.

If I understand your approach correctly:

DT_ItemsBASE store the list of all items with the values they all share (all values in 1st screenshot), while the specialized tables (DT_Firearms, etc) hold the specific values.

I assume you are trying to change the handle of an entry of a table but it remains unchanged in the other.
For example, renaming the entry “ShotgunA” to “Shotgun1” in the general table, but it stays as ShotgunA in DT_Firearms.

If you need to do this often, I suggest creating a function (BP or C++) that takes both tables, and changes the name in both. I am not very familiar with data tables as I haven’t used them, but it should be straight forward.

I am not familiar with the way Unreal exports to CSV or Excel opens CSV files, but there are probably answers on Stack Overflow or similar websites on how to separate a one-cell CSV (vba - Excel drops all the data to one cell after saving to CSV - Stack Overflow).

The specialized tables hold the data that is unique to their item type.

No, that’s not what I meant. I’ll try to explain it better.
Let’s say I want to rename “ShotgunA” to “ShotgunAwesome”. It’s clear to me that I need to manually rename the row name in both tables (DT_ItemsBASE and DT_Firearms).

Now, let’s say I placed this item (former ShotgunA) in my level where the player can pick it up. For this I have an actor BP_ItemActor where I can set a data table row handle, so it knows which item should get created. In this data table row handle the row name does not get updated automatically (from “ShotgunA” to “ShotgunAwesome”) and therefore the item can’t be created properly.

So if I were to change the row name in a data table, I would have to manually reassign the proper row name (“ShotgunAwesome”) in all the data table row handles that are placed inside a level (or also in a blueprint), because the change (“ShotgunA” → “ShotgunAwesome”) does not get updated automatically there.
This is obviously not practical so I have to be missing something here.

I hope it’s clear now.

That worked. Thanks :slightly_smiling_face:

And one more question about data tables:
Is there any way to limit the amount of decimal places for float values?
It really hurts readability.

I tried to google this, but couldn’t find a solution.

Here is one way (I’m pretty sure there are more efficient ways) to do this:

You would have to implement both functions, but I believe it would be fairly simple.

You could use integers, and have values like fire rate as Rounds Per Minute, precision as a percentage (75 instead of 0.75), or as a last resort (please don’t do this) use strings and functions to decode them (Atoi, Atof, etc).

I will look into this. Thanks again.

I’d rather not use integers. I think I’ll just have to live with the gazillion decimal places.