Driving Gameplay with Data from Excel

Ask any game programmer, and they’ll tell you that it’s best to adopt a data-driven approach that allows game designers to tweak as much about the gameplay as possible without the aid of a programmer. If you talk to the game designers themselves, many of them will tell you that for games that require a lot of tweaking and balancing, it’s best to be able to manipulate this data directly with the very capable set of tools provided in Excel (or other spreadsheet tools).

So how can you get data defined by game designers out of Excel and into a format you can use in UE4? Well, I have some good news for you! This already works in UE4!

Unreal Engine 4 supports importing data from comma separated variable (.csv) files, which is a format to which you can easily export your Excel files. You can even make macros in your excel documents to allow you to export to .csv with the click of a button inside the document itself. There’s an example of a document set up in this fashion :

*Note that you may have to press the “enable content” button to allow the macros to run. This is a security feature of Excel to keep arbitrary code from running on your machine without your permission.

exportCSV-528x245-177573749.png

Types of Tables of Data

We find the following two types of tables of data to be useful:

  • DataTables
  • CurveTables

DataTables give you the freedom to define pretty much any type of data you want for import, whereas CurveTables are customized more for interpolating along a curve defined via floating point numbers.

Data Tables

With data tables you can define custom data structures and edit and maintain their values in Excel. You can define the format of the data by creating a struct that inherits from FTableRowBase, and defines various properties and default values.

As an example, is a definition for level up experience data and the CSV document that supplies the data to the game:


/** Structure that defines a level up table entry */

USTRUCT(BlueprintType)

struct FLevelUpData : public FTableRowBase

{

GENERATED_USTRUCT_BODY()

public:

FLevelUpData()

: XPtoLvl(0)

, AdditionalHP (0)

{}

/** XP to get to the given level from the previous level */

UPROPERTY(EditAnywhere, BlueprintReadWrite, Category=LevelUp)

int32 XPtoLvl;

/** Extra HitPoints gained at this level */

UPROPERTY(EditAnywhere, BlueprintReadWrite, Category=LevelUp)

int32 AdditionalHP;

/** Icon to use for Achivement */

UPROPERTY(EditAnywhere, BlueprintReadWrite, Category=LevelUp)

TAssetPtr<UTexture> AchievementIcon;

};

The corresponding CSV Document would look like this, where the first column is a unique name to identify the row of data. Later when retrieving the data, you refer to the row of data you want by name.


Name,XPtoLvl,XP,Asset

1,0,0,"Texture2d'/Game/Textures/AchievementIcon1'"

2,1000,1000,"Texture2d'/Game/Textures/AchievementIcon2'"

3,1000,2000," Texture2D'/Game/Textures/AchievementIcon3'"

4,1500,3500," Texture2D'/Game/Textures/AchievementIcon4'"

5,2000,5500," Texture2D'/Game/Textures/AchievementIcon5'"

Curve Tables

Curve tables are a little more specific in their use, as floats are the only data they contain. You can use various formulas and tools inside of excel to define curves for XP or some other aspect of your game, and then evaluate these curves in UE4.

For example, you could define curves for how damage increases based on character level or some other factor like so:

Example

is an example table for damage progression curves (as you can see the column numbering need not be sequential integers, so you can include gaps):

[table=“width: 500, class: grid, align: left”]

0
2
3
7


Melee_Damage
15
20
25
30


Melee_KnockBack
1
2
4
8


Melee_KnockBackAngle
10
45
60
65


Melee_StunTime
0
1
5
7

Curves are particularly useful for balancing, item distribution, attributes for abilities and skills, etc. You can also evaluate any point on the curve you defined. For example if you had a monster whose health varied depending on the difficulty setting of the game, you could define a health curve that ranged from 100 to 10,000 in a linear, cubic, or some other fashion. Then you could sample the curve at say 75% of the way from minimum to maximum value to find the amount of health for “Hard” difficulty.

Importing

The importing process is pretty easy. You just export your Excel (or whatever software) spreadsheet to .csv format, and you can drag and drop that onto the Content Browser.

Currently, if you have the .csv file open in Excel when you try to import it into UE4, it fails without a very clear error message, so remember to close the file in Excel before you import. We’re looking into this issue.

You’ll be presented with a dialog to choose whether this is a DataTable (and thus also the Row Type defined earlier in C++) or CurveTable, in which case you have to choose a type of interpolation between the curve points, i.e. Linear or Cubic, etc:

importingDataTable-482x148-1803892003.png

From there you’ll see a Data Table or Curve Table asset in the Content Browser, and you can double click it and open it to inspect the values.

Using the Data

In order to make the Data as easy to set as possible, I recommend making a Blueprint-exposed variable of Type FDataTableRowHandle or FCurveTableRowHandle. Then in a Blueprint a designer would specify the Table and the Row Name.

Once these references are set, you can call FCurveTableRowHandle::GetCurve() to get a FRichCurve, or FDataTableRowHandle::FindRow() to get a reference to an instance of the struct you defined earlier. You shouldn’t hold on to these references any longer than the scope of a function though, because if the data is re-imported, you want the changes to take effect instantly, and you don’t want to access any invalid pointers.

Further Information about DataTables and Curve Tables

See the Data Driven Gameplay Elements documentation.

I hope this post helps you use Excel to improve your design process, but as always feel free to post further questions on AnswerHubor in the Forums!

Nice article, thanks for posting. Though I have one question, that bothers me since I discovered data tables.

How you would go about creating data for ability (spell ?) which:

  1. Damages Health and Mana at the same time (for example).
  2. Health damage = if(Target.Constitution < Causer.Constitution) then Intelligence * 2.5 else Intelligence * 1.
  3. Mana Damage = Health Damage * Wisdom * 0.5 and result * 1.5 is added back to causer.

I honestly couldn’t figure it out nice way to put it in single file, and after that thought it might be better to just put it blueprints.
But I honestly would love to be proved wrong :smiley:

Quick Question:

Is there anything that prevents us from using this type of data to default Class properties?

Like instead of ArmorAmount or AbsorptionPct, can we use data driven value?



AUTArmor::AUTArmor(const FPostConstructInitializeProperties& PCIP)
: Super(PCIP)
{
	ArmorAmount = 50;
	AbsorptionPct = 0.333f;
	bCallModifyDamageTaken = true;
}


Hi,

Thanks for the useful intro into using data. Managed to import a data table correctly but having a little trouble with the next part. Any chance of an example?

Cheers

[]
Using the Data

In order to make the Data as easy to set as possible, I recommend making a Blueprint-exposed variable of Type FDataTableRowHandle or FCurveTableRowHandle. Then in a Blueprint a designer would specify the Table and the Row Name.

Once these references are set, you can call FCurveTableRowHandle::GetCurve() to get a FRichCurve, or FDataTableRowHandle::FindRow() to get a reference to an instance of the struct you defined earlier. You shouldn’t hold on to these references any longer than the scope of a function though, because if the data is re-imported, you want the changes to take effect instantly, and you don’t want to access any invalid pointers.

[/]

[=;82847]
Nice article, thanks for posting. Though I have one question, that bothers me since I discovered data tables.

How you would go about creating data for ability (spell ?) which:

  1. Damages Health and Mana at the same time (for example).
  2. Health damage = if(Target.Constitution < Causer.Constitution) then Intelligence * 2.5 else Intelligence * 1.
  3. Mana Damage = Health Damage * Wisdom * 0.5 and result * 1.5 is added back to causer.

I honestly couldn’t figure it out nice way to put it in single file, and after that thought it might be better to just put it blueprints.
But I honestly would love to be proved wrong :smiley:
[/]

Health, Mana, Wisdom, etc are all just variables. Pull from the data table to set and get them.

Hey, im having the same problems as Morphues. Importing the data is fine but getting a correct reference to it in code has got me stuck.
If anyone has an example of how they set up the construction of their objects in c++ could you please post, it would be greatly appreciated. I found a page of code in the unreal engine wiki but it does not seem to work.

[=;82847]
Nice article, thanks for posting. Though I have one question, that bothers me since I discovered data tables.

How you would go about creating data for ability (spell ?) which:

  1. Damages Health and Mana at the same time (for example).
  2. Health damage = if(Target.Constitution < Causer.Constitution) then Intelligence * 2.5 else Intelligence * 1.
  3. Mana Damage = Health Damage * Wisdom * 0.5 and result * 1.5 is added back to causer.

I honestly couldn’t figure it out nice way to put it in single file, and after that thought it might be better to just put it blueprints.
But I honestly would love to be proved wrong :smiley:
[/]

If you think it’s easier to store the gameplay data for your particular game in Blueprints, then that’s fine too. It’s all about finding the workflow best for you.

You can define however many variables of whatever type you want for a DataTable, so if you wanted to put all of it in one file it would just be a matter of adding more variables of the right type.

As for having these values be the result of some calculation, you can use formulas and macros in excel to calculate these values, then export them, but if you’re more familiar with blueprint logic than excel logic, it might just be easier to go with what you know.

The case where importing data from excel is really handy is when you’re balancing gameplay for a relatively complex game. If you for example had a fighting game with 20 characters and you needed to be able to look at all the data for their attacks, etc in one place and tweak them in relation to each other, then Data and Curve tables could be really handy. Or if you had a lot of varieties of enemies and you needed to balance them to ensure some weren’t too strong or two weak, etc. If you just have 3 variables to define, trying to do it in excel and import it might be more overhead than it’s worth, but it’s just a matter of trying it and seeing what works best for you.

[=ajvnd;84619]
Now please let me do that in blueprints :smiley:
[/]

I added support for at least querying the tables in blueprints, that should release with 4.3:

[=;82997]
Quick Question:

Is there anything that prevents us from using this type of data to default Class properties?

Like instead of ArmorAmount or AbsorptionPct, can we use data driven value?



AUTArmor::AUTArmor(const FPostConstructInitializeProperties& PCIP)
: Super(PCIP)
{
	ArmorAmount = 50;
	AbsorptionPct = 0.333f;
	bCallModifyDamageTaken = true;
}


[/]

I don’t see any reason that shouldn’t work, but if you run into any problems, please let us know!

[=Morpheus;84114]
Hi,

Thanks for the useful intro into using data. Managed to import a data table correctly but having a little trouble with the next part. Any chance of an example?

Cheers
[/]

Say you made a row struct like the following:


/** Structure to store the stats of a melee weapon for use in a UDataTable */
USTRUCT()
struct FMeleeWeaponStats : public FTableRowBase
{
	GENERATED_USTRUCT_BODY()

public:

	/** Max distance melee can hit an enemy. */
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category=WeaponMeleeStats)
	float RangeVSEnemies;

}

I would recommend making a Blueprintable class with a UProperty FDataTableRowHandle so you can set the DataTable reference and RowName in a blueprint. The UProperty definition would look like this:


/** Information about melee attack */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Damage)
FDataTableRowHandle MeleeAttackStatData;

Then when you wanted to use the data, just use the GetRow method (I might have called this “FindRow” in my blog post, whoops) of FDataTableRowHandle like so:


const FMeleeWeaponStats* MeleeWeaponStats = MeleeAttackStatData.GetRow<FMeleeWeaponStats>();
if (MeleeWeaponStats != NULL)
{
	float AttackRange = MeleeWeaponStats->RangeVSEnemies;
	// Do something with this information...
}

Hope that helps!

how do the matching is done between the CSV column and the Struct?
In the documentation it said that the column name should be a variable name, but in your sample they are not the same?

So what’s are the rules?

Thanks,

[= Conley;87073]

You can define however many variables of whatever type you want for a DataTable, so if you wanted to put all of it in one file it would just be a matter of adding more variables of the right type.

[/]

Heh there was my particular problem. I don’t really know how much data I want store. Because each potential ability can have very different mechanics from each other. I could probably just define maximum amount of properties and left fields empty, but that just doesn’t feel like proper design.

It would cool though if we could use JSON data instead.
JSON structures can be nested. So I could define each ability as single entity in json add only properties that are needed in nested entity in JSON.
Of course in that case we would need to still either calculcate the final results inside blueprint from data provided in json, or create file similar to Curve Table or Data Table, just no flat, but with nested tree structure.

[]
The case where importing data from excel is really handy is when you’re balancing gameplay for a relatively complex game. If you for example had a fighting game with 20 characters and you needed to be able to look at all the data for their attacks, etc in one place and tweak them in relation to each other, then Data and Curve tables could be really handy.
[/]

I’m not sure about it. At least to me it looks pretty similar, at least in case of abilities. You still define formulas that calculate something. Whether it is done in excel per ability and X levels is calculated and then you just poll that data or in blueprint, where the result is calculated based on input data from other source (for example owner attributes and target attributes), the result in case of change is still the same. You must go back and forth and adjust your formula.

But I can definitely see merit to in case of balancing NPC, especially when if you a lot of them and they all have different attributes. Tweaking them in one place is definitely more handy than tweaking them in 7182 different files :D.

[=;87239]
how do the matching is done between the CSV column and the Struct?
In the documentation it said that the column name should be a variable name, but in your sample they are not the same?

So what’s are the rules?

Thanks,
[/]

I second that question. Naively, looking at the API (FDataTableRowHandle | Unreal Engine Documentation), I would assume that a pointer coming from the blueprint data (with the help of an FObjectFinder ?) should be stored in the .DataTable variable while we also store a search key in the .RowName variable.



LevelUpDataHandle.DataTable = (from the Blueprint)
LevelUpDataHandle.RowName = "2";


EDIT:

After reading this post - Attempting to reference a DataTable in c++ - Programming & Scripting - Epic Developer Community Forums - I think I found the solution.



// IMPORTANT : Do this part of the code in a constructor.
ConstructorHelpers::FObjectFinder<UDataTable> ExcelTable_BP(TEXT("DataTable'/Game/Data/levelup.levelup'")); // A reference to my Blueprint data table.

if (ExcelTable_BP.Object)
{
     LevelUpDataHandle.DataTable = ExcelTable_BP.Object;
}


Later on …



if (LevelUpDataHandle.DataTable)
{
	LevelUpDataHandle.RowName = "3";
	const FLevelUpData* LevelUpData = LevelUpDataHandle.GetRow<FLevelUpData>();

	if (LevelUpData)
	{
             (...)
	}
}


It worked. There must be a more elegant method to query the desired row. Tell me what you think …

@Jon Thanks, it does help, for some reason I thought id be doing all the work in Blueprint, no wonder I could find the GetRow function :slight_smile: It would be great if that functionality were there of course :wink:

I’m running into problems trying to implement this. I keep getting “Too few rows”. Has someone else run into this?

This is the code (pretty much straight out of the example):

/** Structure that defines a level up table entry */
USTRUCT(BlueprintType)
struct FCharacterData : public FTableRowBase
{
GENERATED_USTRUCT_BODY()

public:

FCharacterData()
	: XPtoLvl(0)
	, XP(0)
{}

/** The 'Name' column is the same as the XP Level */

/** XP to get to the given level from the previous level */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = LevelUp)
	int32 XPtoLvl;

/** This was the old property name (represented total XP) */
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = LevelUp)
	int32 XP;

UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = LevelUp)
	TAssetPtr&lt;UTexture&gt; Asset;

};

And the contents of the .csv file:

Name,XPtoLvl,XP,Asset
1,0,0,“Texture2d’/Game/UI/HUD/Actions/Barrel’”
2,1000,1000,“Texture2d’/Game/UI/HUD/Actions/Barrel’”
3,1000,2000,“Texture2d’/Game/UI/HUD/Actions/Barrel’”
4,1500,3500,“Texture2d’/Game/UI/HUD/Actions/Barrel’”
5,2000,5500,“Texture2d’/Game/UI/HUD/Actions/Barrel’”
6,2500,8000,“Texture2d’/Game/UI/HUD/Actions/Barrel’”
7,3000,11000,“Texture2d’/Game/UI/HUD/Actions/Barrel’”
8,3500,14500,“Texture2d’/Game/UI/HUD/Actions/Barrel’”
9,4000,18500,“Texture2d’/Game/UI/HUD/Actions/Barrel’”
10,4500,23000,“Texture2d’/Game/UI/HUD/Actions/Barrel’”
11,5000,28000,“Texture2d’/Game/UI/HUD/Actions/Barrel’”
12,5500,33500,“Texture2d’/Game/UI/HUD/Actions/Barrel’”
13,6000,39500,“Texture2d’/Game/UI/HUD/Actions/Barrel’”
14,6500,46000,“Texture2d’/Game/UI/HUD/Actions/Barrel’”
15,7000,53000,“Texture2d’/Game/UI/HUD/Actions/Barrel’”
16,7500,60500,“Texture2d’/Game/UI/HUD/Actions/Barrel’”
17,8000,68500,“Texture2d’/Game/UI/HUD/Actions/Barrel’”
18,8500,77000,“Texture2d’/Game/UI/HUD/Actions/Barrel’”
19,9000,86000,“Texture2d’/Game/UI/HUD/Actions/Barrel’”
20,9500,95500,"Texture2d’/Game/UI/HUD/Actions/Barrel’

Any help would be appreciated!

Hi,

I played a little bit with it yesterday and are my feedback that can help for improvment on the tool:

1 - Is it possible to select the separator and decimal one? In Excel (FR), by default csv file are separted by “;” and number by “,”. It could be great to be able to setup this during the import so we don’t have to tweak the file or create a Macro to export the data.
2 -Is there another way out of “drag & drop” to create the datatable? It will be more friendly if we can create it like any other asset (Right Click)

Out of this 2 tiny things, I find it easy to import and setup my Row definitions. I found also that the import is very defensive in term of coding and the error messages that we can face are clear.

I confirm that the Column name must match the Variable name.

I need to test the FindRow now.

thanks,

Hello,

when importing a DataTable, and opening the asset in the editor, all the data is nicely displayed in a table.

But when I import a curve data table, there is no display. I can only see the raw data. Is it intended? That would be nice to have a graph which would show how the data is computed, to better grasp the differences between the settings Constant / Linear / Cubic.

BTW, when importing a CSV file, with the import options DataTable and CurveTable, there are the options “Float Curve” and “Vector Curve” (for which there is a graph display ^^). What are they used for?

Thanks

Just out of curiosity, will we ever have support for utilizing this entirely from Blueprints (aside from the CSV data itself)?

Couple of questions, can this work with ENUMS and FTexts (Such as the Name of the Monster might need to be localized)? If so how do I make it work?

Looking in the code, I see a check for supported types and see Text and Byte in there, so If I had these as my properties:

UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "BaseMonster Struct")
FText Description;

UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Enum)
TEnumAsByte&lt;EElementalEnum::Type&gt; PrimaryElementalType;

I figure, for the ENUM, as long as the column as the corresponding Byte value for the ENUM it should work? But I’m not sure about FText since a namespace is needed?

[=;87814]
Just out of curiosity, will we ever have support for utilizing this entirely from Blueprints (aside from the CSV data itself)?
[/]

It would be most excellent if the editor could handle everything regarding the CSV data as well!