What is the proper way to handle static data?

I’m a bit confused about the proper way to handle static data in a game. By “static data,” I mean data that doesn’t change throughout the lifetime of the game: the stats of various weapons, how much HP each enemy has, etc. DataTables seem like the right place to start, but I can’t figure out how to actually use data from a data table in game.

As an example, let’s say I was making a CCG with a set of static, built-in decks that players could use. In normal (non-Unreal) C++, my data structures might look something like this:



struct CardDefinition
{
    const char* Name;
    const Texture2D* Art;
    // Some other members
};

static const CardDefinition] gAllCards =
{
    { Name = "FirstCard", Art = GetAsset("some/path") },
    { Name = "SecondCard, Art = GetAsset("some/other/path") },
    // etc.
}

struct CardEntry
{
    const CardDefinition* Card;
    const int Quantity;
};

struct Deck
{
    const CardEntry] Cards;
};

static const Deck] gAllDecks =
{
    {
        { Card = gAllCards[0], Quantity = 2 },
        { Card = gAllCards[1], Quantity = 3 },
        // etc.
    }
}

// Then, somewhere in gameplay code...

class InGameDeck
{
    //...
    void Initialize(const DeckDefinition& deck);
    const CardDefinition& DrawCard();
    //...
}


One problem with this is that it is completely opaque to designers. This isn’t necessarily a huge issue in my case, but I’d like to learn the “correct” way of doing this, which means making as much of this as possible blueprint-accessible. I’ve tried a handful of different approaches, but haven’t found anything that entirely works yet:

I can create a Blueprint Struct for CardDefinition and then a DataTable of this type for AllCards. However, after doing so, I can’t find any way to create a Blueprint Struct for CardEntry that references a row in this table. It doesn’t look like FDataTableRowHandle is visible to blueprints.

If I define my structs in C++ and am willing to throw type safety to the wind, I can have CardEntry include a member of type FDataTableRowHandle. However, this makes any functionality involving this inaccessible from blueprints, as I can’t find a way in blueprints to go from a FDataTableRowHandle to the underlying CardDefinition. This means that I can’t call InGameDeck::Initialize() from a blueprint.

In addition, I can’t seem to find a way to have InGameDeck::DrawCard() return a const reference to a CardDefinition. The engine seems to think that const reference types should only be used on input pins, so I get an error complaining about direction. I could have it return a non-const pointer, but that makes me somewhat uncomfortable as the it’s referring to definitely should not be mutated at run time. I suppose I could make both Initialize() and DrawCard() deal with FDataTableRowHandles instead of CardDefinitions, but once again, I’m throwing away type safety if I do that. I’d really prefer to read the data out of the tables once at game start and then pass around references to the underlying types everywhere else.

This all makes me think I’m going about static data the wrong way. Can anybody point me at a way to handle static data that allows me to define and manipulate data from blueprints in a type-safe, const-correct manner? Is there an example project I could look at that demonstrates a good way to manage this?

The first part of this may help you set up a static blueprint editable data set.

DataTables are one way I know of, but I don’t have any experience with that so hopefully someone else can fill you in on that option.

You can use blueprints for this. Say you have prepared a struct to contain your card data. You can then:

  • Make a data asset class UCardInfo that inherits from UDataAsset. Then in the editor your designers can create data assets of type UCardInfo to set the values. In C++ if you have a UCardInfo* UPROPERTY, in the editor you can select an existing data asset from a dropdown. Very neat!
  • Make a very simple blueprintable UObject class UCardInfo. Then in the editor, designers can make blueprints of UCardInfo to fill in the data. A blueprint class of this type can be stored as a C++ variable as TSubclassOf<UCardInfo>. In the editor, when you have a TSubclassOf<UCardInfo> you can select compatible classes from a dropdown. To retrieve the data from the blueprint class you can call CardClass->GetDefaultObject() and then treat this as any UCardInfo*. But its values are that specific blueprint class’ values, of course. Use this approach if you want to add blueprint logic to a card as well.

In both cases, make sure your struct is a USTRUCT(BlueprintType) and your class is a UCLASS(Blueprintable)

Making a class that derives from UDataAsset does exactly what I’m looking for. Thanks a lot for the help!