[UE5] Data Table Basics: Tuning Enemy Strength in CSV and Reading It from Blueprint

Created: 2025-12-12Last updated: 2026-09-07

Gather enemy HP and attack into a table and bring CSV changes into Unreal Engine, with diagrams. Covers the roles of Structs and Row Names, wiring Get Data Table Row's found and not-found paths, and an experiment changing only the goblin's HP.

The goblin is a bit weak, so you want to raise its HP from 60 to 90. Opening each enemy's Blueprint to hunt for the number also makes it hard to compare balance across enemies.

With a Data Table you can gather enemy names, HP, and attack into one table. You tell the Blueprint "which row to read" and do the number tuning in the table. Since you can import CSV edited in Excel and the like, you can tune while surveying the numbers even as enemies multiply.

In other engines: since it is tabular data of rows, the closest usage in Unity is a database built with ScriptableObject or reading CSV/JSON, and in Godot a CSV or an array of Resource.

In this article we build a table of slime, goblin, and ogre, and display the values loaded at game start as text. At the end we change one spot in the CSV and confirm that only the goblin's HP changes.

Changing the goblin's HP in CSV and the same Blueprint reading the updated value

What You'll Learn

  • Deciding the table's fields with a Struct and building a Data Table from CSV
  • Specifying a row name to read one kind's data in Blueprint
  • Confirming the not-found case separately
  • Re-importing CSV to change only the numbers

It assumes you have created variables and nodes in Blueprint. If Structs are new to you, the Struct introduction is a useful reference.

Sponsored

How it works: the Struct holds fields, the Data Table holds values

First, look at the table we are about to build.

NameDisplayNameMaxHealthAttack
SlimeSlime305
GoblinGoblin6012
OgreOgre20030

A Struct is a type for bundling one item's worth of information. Here we decide the fields that go in one row: "display name is text; max HP and attack are integers."

A Data Table lines up many rows of actual values in that shape. The goblin and the ogre have the same fields with different numbers inside.

Making the Struct's three fields into columns and lining up three enemies' values in the Data Table

The leftmost Name is the name used to designate the row you want to read. UE calls it the Row Name. Specify Goblin and you pull "Goblin, 60, 12" out as one unit. There is no need to remember the row order.

Name and DisplayName have different roles. The former is the name the program searches by; the latter is the name shown to the player. Change the display to "Forest Goblin" and, as long as the row name stays Goblin, the Blueprint's designation needs no change.

Preparation: building a table of three enemies

1. Decide the columns with a Struct

Right-click in the content browser, choose "Blueprints" → "Structure," and create S_EnemyStats. Open it and add these three fields.

Field nameTypeMeaning
DisplayNameTextThe enemy name shown on screen
MaxHealthIntegerMax HP. We use no decimals here
AttackIntegerAttack power

Delete the unnecessary field that exists initially and save. Do not add Name to the Struct. The row name is held by the Data Table side.

2. Prepare the CSV

CSV is a file format writing one table row with comma separators. You can edit it in Excel or Google Sheets, but for now you can also create it by pasting the following into a text editor.

Name,DisplayName,MaxHealth,Attack
Slime,Slime,30,5
Goblin,Goblin,60,12
Ogre,Ogre,200,30

Create a SourceData folder inside the project folder and save it as EnemyStats.csv. That puts your editing source outside Content. Choose UTF-8 as the encoding. In Excel, save in "CSV UTF-8 (Comma delimited)" format.

The first line is the header. After Name, line up the field names exactly as you named them in the Struct. Line two onward is the actual data, one enemy kind per row.

The CSV's first column is the name used to find a row, and the remaining three correspond to the Struct's fields

3. Import the CSV

  1. Open the folder in the content browser where you want to save the table.
  2. Drag and drop EnemyStats.csv onto that folder.
  3. Set "Import As" to DataTable.
  4. Choose S_EnemyStats in "Data Table Row Type" and import.
  5. Rename the created asset to DT_EnemyStats and save.

Row Type designates "which Struct's shape one row of this table is read as." Leave the "Import Key Field" box empty if it appears. With that setting, the CSV's first column is used as the row name. Do not enable options to ignore missing or extra fields; if a warning appears, compare the header against the Struct.

Importing the CSV, choosing S_EnemyStats as the row type, and creating a Data Table

Open DT_EnemyStats and confirm there are three rows. If the Goblin row shows Goblin as its display name with MaxHealth 60 and Attack 12, you are ready. If characters look corrupted, check the CSV's encoding and save it again.

The Data Table you made here is the asset the game reads. The CSV is its editing source, and the Blueprint we build next does not open the CSV directly during Play. Epic's CSV import procedure

Sponsored

Hands-On: reading a specified row and displaying the result

We place three of the same Blueprint and change only which row each reads. On success, each enemy's values are displayed, as in "Slime: HP 30 / ATK 5." Try one goblin first, then add more.

Even with the same Blueprint, the displayed values change depending on whether it reads the Slime, Goblin, or Ogre row

The figure in the diagram represents the reading Actor. In this exercise we add no mesh or movement and confirm through text output.

1. Create an Actor whose row you can choose

  1. Create a Blueprint BP_EnemyDataDemo with "Actor" as parent class.
  2. Create a variable EnemyRowName of type Name.
  3. Enable the variable's "Instance Editable" and Compile.
  4. Place one BP_EnemyDataDemo in the level.
  5. Select the placed Actor and enter Goblin in EnemyRowName in the details panel.

Name is the type for identifiers such as row names. Enabling "Instance Editable" lets you specify a value per Actor placed in the level. Even using the same Blueprint, one can be Slime and another Goblin.

2. Split the found and not-found cases

In BP_EnemyDataDemo's event graph, connect Get Data Table Row from Event BeginPlay. BeginPlay is the event called when this Actor starts running in the game.

Choose DT_EnemyStats in the node's "Data Table" and connect a Get of the variable EnemyRowName to "Row Name."

Getting the row from BeginPlay and connecting Row Found and Row Not Found to separate Print Texts

The node has these outputs.

OutputRole
Row FoundThe white exec pin taken when the row is found
Row Not FoundThe white exec pin taken when the row is not found
Out RowThe one row's worth of values. Here that is S_EnemyStats

Place two Print Text nodes that output text to the screen. Connect a white line from "Row Found" to the success one and from "Row Not Found" to the failure one, and enter Row not found in the failure one's "In Text." We build the success display's content in the next step.

Success and failure split on those two exec lines. Use Out Row's value on the found path. Epic's Get Data Table Row API

3. Turn one row's values into a readable sentence

Drag from "Out Row" and create Break S_EnemyStats. Break is the node that pulls each field out of a Struct bundle. "Display Name," "Max Health," and "Attack" become readable.

Next place a Format Text and enter the following in "Format."

{Name}: HP {HP} / ATK {ATK}

The { } parts are where values go. Entering it adds "Name," "HP," and "ATK" pins, so connect the Break's three fields to each. This diagram extracts the value connections. The Get node at the left is the one you placed in the previous step.

Breaking Out Row and feeding display name, max HP, and attack into Format Text's three slots
Break outputFormat Text input
Display NameName
Max HealthHP
AttackATK

Finally connect Format Text's "Result" to the success Print Text's "In Text." For the goblin row, the sentence becomes Goblin: HP 60 / ATK 12. The Name here is the display slot's name, separate from the EnemyRowName that chooses the row.

Passing Format Text's Result to the success Print Text and displaying it on the Row Found exec line

Break and Format Text have no white exec pins. They are nodes computed when the values to display are needed. The order of operations is decided by the white lines from BeginPlay → get the row → Print Text, and colored lines pass the display content. Epic's Format Text API

On both Print Texts, enable "Print to Screen" and "Print to Log" and set "Duration" to 10. Expand the details pin if it is collapsed. Leave "Key" unspecified (None).

4. Try both a valid row and a missing one

Compile and Play. Goblin: HP 60 / ATK 12 on screen means you read one row of the table. To check the display later, open the editor's "Output Log." Print Text's "Print to Log" is the setting that also leaves the sentence in that log. Epic's Print Text API

Stop Play once and change the placed Actor's EnemyRowName to MissingEnemy, which is not in the table. This time Row not found appears, confirming the failure path too. Restore Goblin afterwards.

Then duplicate the Actor twice and set the three EnemyRowName values to Slime, Goblin, and Ogre. Play again and confirm all three names and numbers appear. Output order does not matter; verify by each row's contents.

Tuning: changing only the goblin's HP in CSV

Now for the number tuning. Leave the Blueprint's nodes alone and change only the goblin's row in EnemyStats.csv.

Name,DisplayName,MaxHealth,Attack
Slime,Slime,30,5
Goblin,Goblin,90,12
Ogre,Ogre,200,30
  1. Stop Play.
  2. Change Goblin's MaxHealth from 60 to 90 in the CSV and save to the same file.
  3. Right-click DT_EnemyStats in the content browser and choose "Reimport."
  4. Open the table and confirm Goblin's MaxHealth is now 90.
  5. Save the Data Table and Play again.

Reimport re-reads the file at the original import location and updates the asset's contents. Pair saving the CSV with updating the table. Epic's reimport procedure

Saving the CSV, reimporting the Data Table, and playing shows the goblin's HP at 90

Success is Goblin: HP 90 / ATK 12 displayed while the slime stays at HP 30 and the ogre at HP 200. With the reading logic in place, balance tuning no longer requires editing nodes.

If it still shows 60, check the Data Table first. If the table also shows 60, check where the CSV was saved and whether you reimported; if the table shows 90, check whether you restarted Play. Since we read in BeginPlay, editing the CSV while running does not make the display logic run again automatically.

Sponsored

Bonus: Good to Know Up Front

Keep max HP separate from remaining HP

What we displayed is the per-kind initial settings written in the table. In real combat, put the loaded MaxHealth into something like the enemy Actor's CurrentHealth and reduce that Actor's current HP on damage.

Consider the pre-tuning max HP of 60. Spawn two goblins and damage one for 10, and their current HP is 50 and 60. You do not want to change the goblin kind's max HP to 50.

The kind's max HP lives in the table, while the current HP reduced by damage belongs to each Actor

With that split you can handle changing every goblin's strength and damaging the one in front of you without confusing them. Logic putting current HP into a variable and handling damage is added on the combat side.

Keep row names stable, separate from display names

Change Goblin to a different row name and the Actors specifying that name need fixing too. When you only want to change the name shown to players, edit DisplayName.

The Name type used for Row Names compares case-insensitively. You cannot use Goblin and goblin as different row names. To reduce misreadings while working, aligning the notation between the table and Blueprint keeps it easy to follow. Epic's documentation on the Name type

Decide whether CSV or the editor is your source

Data Tables can be edited inside the editor too. But re-importing a CSV updates the table with that file's contents. Set HP to 90 in the editor and then reimport an old CSV and you unintentionally revert to 60.

In this article's workflow, the CSV is the source. If you move the file, also check the import source the Data Table references. When you want to move an existing table into CSV, use the Data Table's "Export as CSV" to produce a template and reduce mistakes copying field names by hand.

Separate changing a table's fields from tuning values

HP 60 → 90 is changing an existing field's value. Adding a new field to the Struct, or changing a name or type, also affects the CSV header and Break nodes. Keep the pre-change file, and check the import warnings, the table's values, and the Blueprints using it.

Data Tables suit lining numbers up vertically for comparison. When you want to pick meshes and sound effects per item in the editor, looking at how to use Data Assets alongside makes choosing where to put your data easier.

Summary

  • The Struct holds "the table's fields" and the Data Table holds "their values"
  • Specify a row name to read, and handle the not-found case separately
  • Re-import the CSV and you can swap just the numbers

The question before using it is "will many rows of the same shape line up?" If they will, a table; if each item differs in nature, a Data Asset suits better.

To give each item its own settings, go to Data Assets and data-driven design; to carry the values you read around, go to Structs.

Unreal Engine Notes in this section98