Download

Data hierarchies: should my core Item be an Actor with data + model, or an empty data class?

This can’t possibly be as hard as I’m making it in my head, but I’m having a heck of a time working out how to structure items in my game. Every item needs both a physical model, generic data that is common to all items (name, value, description), and functionality which is unique to that item/item class. The latter two are easy enough to accomplish with a generic class to hold the common data and maintain an empty Use() function, and subclasses that override Use in specific ways (to differentiate consumables from weapons from inert loot).

Thus far everything I’ve described could be accomplished in an empty data class, but I can’t figure out what the best option is to actually associate that data with a model. The most straightforward approach would be to make the item class an Actor with a public UStaticMesh* to set in the editor, but if the player ends up with 30-40 items in their inventory, that’s 30-40 meshes I’m turning invisible and holding in memory without using. An alternative would be to make items an empty data class and create some ItemHolder actor which only has a ItemData* reference and a PickMeUp(ACharacter* looter) function, but after the item is looted, and its holder runs Destroy(), I’m still going to need a reference to the item’s model, since he’ll want to equip/unequip/drop it at a whim.

Am I overcomplicating this? It feels like making every item an Actor is the best solution, but I hate the idea of forcing what should be a small Struct() to carry geometry around with it in memory.

well im not sure on the right way

but i had a base item class that had an itemdata struct with:

name
mesh
class reference <- this is how i put it in a inventory

then my players had an array of the itemdata structs and whenever a player pulled an item out i used spawn actor from class and plugged the items class in and spawned it for the player

Ex:

BaseItemBP = has base functionality and itemdata struct

CanItem = child of base item with custom variables its class reference was to CanItem

when a player picks up an item i cast to baseitem as its the parent and pull the items into the array and destroy the item, when they want that item i go to the spot in the array, open the struct and grab the class the item was and spawn it at that moment

probably not the best but its what i did

IMHO your item should strictly be data. I’d recommend not even having a class reference in their either. I would make the “Item” simply be non-unreal-specific data. The reason being that you can then manipulate and store that data internally or externally to unreal. Maybe you choose to write tools that store Items in a database, which you then read from in UE4 to handle the actual runtime stuff. It depends on how many items you’re having and such though of course.

I’d recommend having an InventoryItem class which is simply a count and an id value, where the Id is the index of an item in the database of items. Then an inventory itself is simply an array of InventoryItems. When you want to equip an item, you’d lookup the Item data from the database by getting the InventoryItem’s Item id and get the associated item data properties (name of class to spawn etc). In the past this has worked for me for a pretty complex set of inventory usage (including crafting and trading).

I had an external DB file that held Inventories for each actor as well as items. You could then edit Items values in a C# app I wrote which was waaaay faster than doing it in-engine. Inventories were looked up based on the Inventory ID, which was pretty much the only thing that an actor in game used initially. Incidentally think of Inventories as simply stores of Item counts and you’ll see that they are useful for all sorts of things. Trading is simply exchange of two Inventories (although you can also add conversion to currency to that). Crafting is simply an input inventory, a required inventory and an output inventory.

I wish my website was still up so I could link you to an article i wrote about this stuff.

That is a really good idea, and I’m pretty sure much more efficient than what I originally planned, but let me ask about the edge case which has me thinking in terms of unreal-centric data classes in the first place: one of the core gameplay mechanics is that in addition to its value for trading/crafting, basically every object has unique functionality that can be accessed by equipping the item and hitting the Use key. Consumables modify whatever stats within the character’s data struct, flashlights spawn a key light and nice emissive material, parabolic microphones register with the audio listener and curate audio-specific UI elements when it hears someone talking, and so forth. This needs some level of functionality specific to Unreal, would your advice be to have some kind of functionality-specific GUID, and make finding and adding that item’s utility code part of the same functionality that would find its model when it drops in-world?

Absolutely yeah. What I would do, would be to have a map of Item ID values to “Constructor” classes. Register each constructor class with the associated Item ID. The constructor would take the Item as input as well as the Actor calling the constructor. The constructor would then be free to do whatever it needed to add functionality (in my system it simply added the required components since I was using a far more component oriented architecture). When I say constructor I don’t mean C++ class constructor btw. Maybe find a better name :slight_smile: but the point is that having a class that handles construction/removal of the parts needed for the item was the way to go. Those classes would register themselves with a central registry such that when you did anything with an item the associated mapped class would deal with the events. I’m a bit hazy on the details here as it was a while back, but I had events for adding/removing from an inventory for instance. I also allowed inventories to hook their own events too, so they could do stuff like recalculate total weight/value.

For the model dropping in world stuff, I simply had that as part of my Inventory component class for convenience. Oh yeah! the other thing is I’d recommend having Inventory components. Allowing multiple InventoryComponents per actor. I had I think maybe three by default? One was the Equipped items, one was the Quickbar items, one was the storage inventory. I had a UI system that could take any inventory from an inventory component and display it in a grid.

That’s a really clean way of doing it, thank you so much for walking me through your logic. :slight_smile:

What I use for item classes that contain data and custom logic is inheriting from UObject. When it comes to UObjects for storing data, they are less resource-heavy than actors though more than structs/data tables. Most importantly, UObjects have support for blueprint graphs, so you can declare an empty Use() function in C++ that you then override in blueprint.

I like zoombapup’s approach too, associating an item ID with another class that contains the logic. Then you can truly store the item data without much overhead and only load the logic class when required. It does add a bit of extra work and more files (every item with custom functionality is two files), so I would make a choice based on how many items have custom functionality and how many items are just data.