The following is not meant as a tutorial, nor a best practice, but rather an example to illustrate previous statements.
for what I will often do for an item/Inventory system
USTRUCT(BlueprintType)
struct FSimpleItem
{
GENERATED_BODY()
public:
UPROPERTY(EditAnywhere, BlueprintReadWrite)
FString PublicName;
// name for internal lookup, and sorting
UPROPERTY(EditAnywhere, BlueprintReadWrite)
int32 InternalID = 0;
// how many of the item is held (primarily for consumables, but stacking can be a thing)
UPROPERTY(EditAnywhere, BlueprintReadWrite)
int32 Quantity = 1;
//...
};
then later I define
/**
* Definition for Items
*/
USTRUCT(BlueprintType)
struct FItemDef : public FSimpleItem
{
GENERATED_BODY()
public:
// Blueprint of the item (Mesh, Materials, EffectEmitters)
// holding special logic for the item.
// held as softClass so that it can be instanced multiple times then created and removed on the fly through repositories and asset manager
UPROPERTY(EditAnywhere, BlueprintReadWrite)
TSoftClassPtr<class ASpawnItemBase> ItemBlueprint;
//...
};
this approach to use Data Tables I need to define a
USTRUCT(BlueprintType)
struct FItemDataTableRow : public FTableRowBase
{
GENERATED_USTRUCT_BODY()
public:
UPROPERTY(EditAnywhere, BlueprintReadWrite)
FString PublicName = TEXT("");
UPROPERTY(EditAnywhere, BlueprintReadWrite)
int32 InternalID = 0;
UPROPERTY(EditAnywhere, BlueprintReadWrite)
TSoftClassPtr<class ASpawnItemBase> itemBlueprint;
//...
};
then you declare either in the second one, so the other is in context scope, or like in some static maybe BlueprintLibrary you declare/define some helper functions for conversion and resolution.
the inventory is just a glorified wrapper on TArray<FSimpleItem> and equip slots are ASpawnItemBase* where the ASpawnItemBase “has-a” FItemDef
notice in the FItemDataTableRow I don’t declare what the row name is that is defined in the DataTable itself (either in the editor, or in the CSV/JSon) then whenever I want a DataTable row based on the an InternalID I do an atoi() on internalID convert it to an FName to quarry the Table, and then when I get the Row I do a sanity check to see
if(Item.internalID == Row.InternalID) (if it fails I write error messages to the designers because they are the ones that messed up) then I go to async load the TSoftClassPtr just in case say the player wants to spontaneously equip something.