Hey guys,
We are going to continue exploring Unreal Engine C++ Fundamentals by taking a look at data driven development using DataTables.
As usual I have created a GitHub project which I will be going over in the video.
What is a data table ?
A DataTable is a grouping of records that all share common properties which can then be accessed by row references or row keys.
Another way of putting it it’s a collection of rows, like in a spreadsheet, that all have shared columns.
And yet another way is to say it’s a simple key value store.
Or a database.
Enough with your damn words, show me pictures !
Let’s take a look at the example below.
We have two rows, that contain the following attributes / columns:
- Montage – a reference to a previously made animation montage.
- Total Animations – the amount of animations we are expecting to start during an attack.
- Description – a quick blurb about what is it we are storing here.
Great we went from games to spreadsheets ? What gives ?
Well as much as we all want to do fun stuff when it comes to game development, at the end of the day it is just software development. Most software development jobs require you to use a database of some sort at least once. There is SQL Server and Postgres and MongoDB and even Excel. All those things end up powering applications and games alike, as we all need to retrieve and store large sets of common data.
In the game dev space it’s just used for a bit more fun than in the other sectors.
What all this allows us to do is have common spots to enter data that represents a lot of “stuff” in our game and then be able to query or retrieve that data on a whim.
Let’s look at the example below.
If you had an RPG that had static definitions around experience and how much of that experience you need to reach a specific level. You could do all this by hand and add it to your class definitions but then if you want to make changes you have to stop everything, recompile, retest and try again. It would be much easier to just have a listing of those values and tweak them as needed.
Now if you have a data table, a database, a container object that you can manipulate outside of your game that will allow you to adjust to values. All of a sudden you become a wizard, as changes to those data points become trivial. Update a file, save, and re run your game.
Ok I am sold, so how do I make one ?
DataTables are essentially USTRUCTS
that derive from FTableRowBase
. The inheritance from FTableRowBase
is what allows them to be brought in as DataTable definitions into Unreal Engine, without that they are just plain old structs.
POINT OF NOTE:
To utilize DataTables you will need to ensure you are including the DataTables header in your code.
Like so: #include "Engine/DataTable.h"
Now let’s see what a DataTable looks like in action.
// Header #include "Engine/DataTable.h" USTRUCT(BlueprintType) struct FPlayerAttackMontage : public FTableRowBase { GENERATED_BODY() public: /** montage **/ UPROPERTY(EditAnywhere, BlueprintReadWrite) UAnimMontage* Montage; /** count of animations in montage **/ UPROPERTY(EditAnywhere, BlueprintReadWrite) int32 AnimSectionCount; /** description **/ UPROPERTY(EditAnywhere, BlueprintReadWrite) FString Description; };
That was pretty simple, now what ?!
Well now you have a few options:
- You can read data from the data table directly in C++, as well as write records.
- You can import the data that goes into a data table from a CSV or JSON file to save you some time.
- You can construct them on the fly and do whatever you need to do with them.
So let’s take a look at reading, as that will be the primary function.
// Header UPROPERTY(EditDefaultsOnly, BlueprintReadOnly, Category = Animation, meta = (AllowPrivateAccess = "true")) class UDataTable* PlayerAttackMontageData; // Source - constructor static ConstructorHelpers::FObjectFinder<UDataTable> PlayerAttackMontageObject(TEXT("DataTable'/Game/TUTORIAL_RESOURCES/DataTables/PlayerAttackMontage.PlayerAttackMontage'")); if (PlayerAttackMontageObject.Succeeded()) { PlayerAttackMontageData = PlayerAttackMontageObject.Object; } // Source - calling data table static const FString ContextString(TEXT("Player Attack Montage")); FPlayerAttackMontage* AttackMontage = PlayerAttackMontageData->FindRow<FPlayerAttackMontage>(FName(TEXT("Punch1")), ContextString, true); if (AttackMontage) { // generate number between 1 and whatever the data table contains for animation counts: int MontageSectionIndex = rand() % AttackMontage->AnimSectionCount + 1; FString MontageSection = "start_" + FString::FromInt(MontageSectionIndex); PlayAnimMontage(AttackMontage->Montage, 1.f, FName(*MontageSection)); }
Reading is easy, creation is hard !
In addition to reading we can also write to our data tables from within our game.
// define our data table struct FPlayerAttackMontage NewAttackMontage; NewAttackMontage.AnimSectionCount = 10; NewAttackMontage.Montage = NULL; NewAttackMontage.Description = "Newly added row"; // call AddRow to insert the record PlayerAttackMontageData->AddRow(FName(TEXT("New Row")), NewAttackMontage);
Not bad, eh folks ? They are pretty easy to get used to and provide a ton of benefits when developing complex game systems.
For more details on these various topics take a look at the links below: