[UE5] Building Enemies and Items Data-Driven with Data Assets

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

An illustrated guide to creating Data Assets and when to use them: the Blueprint-only workflow, how they compare with Data Tables, a hands-on build where one BP_Enemy covers every enemy type, and async loading with Primary Data Assets.

Every time you add an enemy type, you duplicate BP_Enemy , swap the mesh, and retype the HP. With three types it's fine. At twenty, a single shared fix means doing the same edit twenty times.

Moving numbers out into a Data Table is the first step, but an enemy definition holds more than numbers: a skeletal mesh, an idle animation, an array of drop items. That's heavy information to edit in a spreadsheet. This is where Data Assets come in. This article covers the Blueprint-only creation workflow, a hands-on build where one BP_Enemy drives every enemy type, and async loading with Primary Data Assets.

A single blueprint card giving rise to a goblin, an orc, and a dragon, illustrating the idea of a Data Asset

What You'll Learn

  • What a Data Asset really is: a data-only asset whose fields you define yourself
  • How to create one without C++ (pick Primary Data Asset as the parent class)
  • The Data Table decision comes down to "open one at a time, or line them up in a table"
  • Hands-on: make one BP_Enemy cover 20 enemy types
  • Load only when needed with a Primary Data Asset

Sponsored

What a Data Asset Is: A Container You Define

A Data Asset is an asset that holds nothing but data . It isn't placed like an Actor and it doesn't run; it simply holds values as a single file sitting in the Content Browser.

What sets it apart from ordinary assets is that you define its fields yourself . A texture is always "an image", but a Data Asset lets you lay out exactly the fields you need, such as HP, attack power, the mesh to use, and a drop list, to create your own type. Once the type exists, you can create as many concrete entries as you like: DA_Goblin , DA_Orc , and so on.

Data AssetBlueprint Class (Actor, etc.)
What it holdsData onlyLogic and data
Placed in the worldNoYes (placed or spawned)
Cost of adding oneOne asset (just entering values)One class (which duplicates the graph too)
Good forEnemy definitions, item definitions, config valuesPlayer, doors, widgets

Building 20 enemy types as Blueprint Classes means you now have 20 copies of the logic too . Fixing shared behavior means opening all 20. With Data Assets, the logic stays as the single BP_Enemy and the only thing that grows is the data.

Left: duplicating BPs multiplies the same logic into 20 copies. Right: one piece of logic, with only data cards multiplying
Sponsored

Creating One in Blueprint Only

Data Assets are usually explained as a C++ feature, but you can do the whole thing in Blueprint . It's two stages: create the type, then create the data.

Step 1: Create the type (pick Primary Data Asset as the parent)

Right-click in the Content Browser and choose Blueprint Class . In the parent class dialog, open "All Classes", search for PrimaryDataAsset , and select it .

This is where people first get stuck. Typing DataAsset into the search box also shows Data Asset , but you can't select that one . UDataAsset is a class that isn't allowed to be turned into a Blueprint. When creating the type in Blueprint, use its derived class PrimaryDataAsset as the parent.

Name it something that reads as a type, like BPDA_EnemyDefinition . Open it and add the variables you need.

Variable nameTypePurpose
DisplayNameTextName shown on screen
MaxHealthFloatMaximum HP
AttackPowerFloatAttack power
EnemyMeshSkeletal Mesh (object reference)Appearance
AnimClassAnim Instance (class reference)Movement
DropItemIDsName (array)IDs of dropped items

For every variable, open the eye icon and turn on "Instance Editable" . If it's off, you won't be able to edit the values when you create the data. Finally, don't forget to Compile and Save . An unsaved type won't appear in the list in the next step.

Note: the bottom three rows are exactly what Data Assets are for. References to meshes and animation Blueprints, plus arrays , sit right there in a single definition.

Step 2: Create the data

Right-click in the Content Browser and choose Create Advanced Asset > Miscellaneous > Data Asset (depending on the UI, Create Advanced Asset may already be expanded, showing Miscellaneous > Data Asset ). A class picker appears; choose the BPDA_EnemyDefinition you made and you get one piece of data of that type.

Name them DA_Goblin , DA_Orc , DA_Dragon and double-click each to fill in values. From here it's just typing numbers into fields , so non-programmers can carry it forward.

Creating one type and then producing three data entries from it: DA_Goblin, DA_Orc, and DA_Dragon
Sponsored

Data Asset vs Data Table

Both move data outside your Blueprints, so it's easy to hesitate. The deciding factor is whether you want to open and edit one entry at a time, or line them all up in a table .

Data AssetData Table
Unit per assetOne entry ( DA_Goblin is one enemy)Many rows (100 rows in one asset)
Asset references / arraysStrong (they sit naturally as fields)Possible, but awkward to edit inside a row
Comparing at a glanceHard (open one at a time)Strong (laid out as a table)
Bulk editing in CSVNoYes
Good forEnemy, boss, and skill definitionsStat tables, text, per-level coefficients

Enemy definitions in Data Assets, per-level stat tables in Data Tables. The two don't compete; you can combine them. A common design is giving DA_Goblin "the row name of the stat table to reference" and pulling the actual numbers from a Data Table.

Note that a Data Table's row struct can also define asset references and arrays. It's not that they can't hold them; the difference is that editing information that doesn't fit in one cell is painful in a table format .

When you're unsure, ask yourself whether opening it in CSV would be a pleasure . If you want to line up 100 rows and stare at the numbers, use a Data Table. If you want to attach meshes and effects to each entry, use a Data Asset.

Sponsored

Hands-On: 20 Enemy Types from One BP_Enemy

Let's solve the "duplicate the BP every time you add an enemy" problem from the intro with Data Assets. RPG trash mobs, roguelike monsters per floor, tower defense invasion units. "The same system with different visuals and numbers" exists in every genre.

The end result

There's only one BP_Enemy . When you place it in the level, you specify which Data Asset it uses and it becomes that enemy.

Three BP_Enemy actors placed in a level, each assigned DA_Goblin, DA_Orc, or DA_Dragon, changing both appearance and HP

Setup to follow along

Give BP_Enemy (parented to Character) the following variables.

Variable nameTypeDefault valueSetting
EnemyDataBPDA_EnemyDefinition (object reference)NoneInstance Editable on
CurrentHealthFloat0.0

Instance Editable on EnemyData is the crucial part. With it on, you can swap the Data Asset from the details panel per instance placed in the level . Leave it off and every instance becomes the same enemy.

On the Data Asset side, set up three Skeletal Meshes and their corresponding Animation Blueprints. The mesh and the AnimBP must share the same Skeleton . Point at a different Skeleton and you get a visible mesh that never animates.

Assembling itself on BeginPlay

Once placed, read EnemyData on BeginPlay and set your own appearance and stats.

Blueprint node graph branching on Is Valid from BeginPlay, then setting mesh, AnimClass, and HP in order when valid
BP_Enemy
Event BeginPlay
  → Is Valid (Input Object: EnemyData)
      Is Valid pin →
        Set Skeletal Mesh Asset (Target: Mesh, New Mesh: EnemyData → Enemy Mesh)
        → Set Anim Instance Class (Target: Mesh, New Class: EnemyData → Anim Class)
        → Set Current Health (Value: EnemyData → Max Health)
      Is Not Valid pin →
        Print String (Append: "EnemyData not set → " + Get Display Name (Self))

You run it through Is Valid because any instance where you forgot to assign the Data Asset will throw an Accessed None error downstream. Wire Get Display Name (Self) into the Print String so the log tells you which instance is unassigned (→ the debugging article).

Also note that Set Skeletal Mesh Asset was called Set Skeletal Mesh before UE5.0. When older tutorials disagree with what you see, remember this rename.

Verifying it

Place three of them, assign a different Data Asset to each, and Play. You've succeeded if you see three distinct appearances and CurrentHealth matches each one's MaxHealth (say 50 / 120 / 800).

HP isn't visible on screen, so either wire a Print String right after Set Current Health to print the number, or select the Actor during PIE and check the details panel. The appearance only changes after Play starts; swapping the Data Asset in the editor won't change how it looks while stopped.

If the appearance doesn't change, the likely cause is that EnemyData still has Instance Editable off, so every instance is looking at the same value. If HP stays at 0, either Set Current Health isn't connected or MaxHealth is blank on the Data Asset.

Two things to take away.

  • Logic in the BP, values in the Data Asset: BP_Enemy only contains the procedure "read the Data Asset and configure myself". Adding the 21st type becomes the act of creating one data file
  • Use Instance Editable to get per-instance variety: making the Data Asset reference Instance Editable lets you swap it in the level. Without that one small step, the data you carefully separated can't be assigned per instance

The same idea works beyond enemies: weapons, skills, stage settings. Whenever you spot "the thing that keeps multiplying", ask whether logic and data can be split apart.

Sponsored

Primary Data Assets and Async Loading

The approach so far has one issue that bites at scale. Referencing a Data Asset means the meshes and animations it points to get loaded into memory along with it . Register 20 enemy types and you can easily end up loading the dragon's mesh in the opening grassland map.

Holding everything from the start is heavy; load only the enemies you actually need

The PrimaryDataAsset you picked as the parent in step 1 is the foundation for controlling this. Primary Data Assets are a form the Asset Manager can recognize individually , so the engine knows which Data Assets exist.

To enable it, register your type under Edit > Project Settings > Game > Asset Manager > Primary Asset Types to Scan . Specify the target Base Class and the Directories to scan. When registering a type created in Blueprint, turn on Has Blueprint Classes .

Delaying the load

Registration alone doesn't change loading. Only when you change how the references are held to soft references do you get "don't load until it's needed".

VariableBeforeAfter
EnemyMeshSkeletal Mesh (object reference)Skeletal Mesh ( Soft Object Reference )
AnimClassAnim Instance (class reference)Anim Instance ( Soft Class Reference )

A soft reference holds not the object itself but the asset's location (its path) . That's why you can't wire it straight into Set Skeletal Mesh Asset . Insert an Async Load Asset node and pass along the object returned from Completed .

Event BeginPlay
  → Async Load Asset (Asset: EnemyData → Enemy Mesh)
      Completed →
        Set Skeletal Mesh Asset (New Mesh: cast the Loaded Asset)

It's extra work, so adopting it gradually is fine. A realistic order is to get the design right with the approach above and switch to soft references once load times or memory actually become a problem . The mechanics of soft references and Async Load Asset themselves are covered in detail in Soft References and Async Loading.

Bonus: Good to Know for Later

  • Standardize on the DA_ prefix: separating types as BPDA_ and data as DA_ keeps you oriented in the Content Browser (→ the asset management article)
  • Don't put logic in Data Assets: this isn't an engine restriction, it's a working policy. You can add Functions to a Data Asset, but once it starts holding checks and calculations, where things live gets scattered again. Keeping it as a container for data makes it last
  • They aren't created at runtime: Data Assets are assets you create and save in the editor. If you need to generate values at runtime and pass them around, use a Struct or a regular Object
  • Defining the type in C++ is also an option: if your team has a programmer, you can split the work by defining the type as a C++ class derived from UDataAsset while the data is authored in the editor. For how to write it, see Creating Your First C++ Class
// EnemyDefinition.h - defining the type in C++
// BlueprintType: usable as a variable type in BP / Blueprintable: can be subclassed in BP
UCLASS(BlueprintType, Blueprintable)
class MYPROJECT_API UEnemyDefinition : public UDataAsset
{
    GENERATED_BODY()

public:
    UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Enemy")
    FText DisplayName;

    UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Stats")
    float MaxHealth = 100.0f;

    UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Visual")
    TObjectPtr<USkeletalMesh> EnemyMesh;
};

Summary

  • A Data Asset is a data-only asset whose fields you define yourself . Keep logic out of it as a matter of policy
  • You can create one entirely in Blueprint . The parent class is Primary Data Asset , not Data Asset . Turn Instance Editable on for your variables
  • The dividing line with Data Tables is open one at a time, or line them up in a table . Enemy definitions in Data Assets, stat tables in Data Tables
  • The core of the hands-on is one logic BP, many value Data Assets . Adding a type becomes adding data
  • When loading gets heavy, use Asset Manager registration plus soft references so assets come in only when needed

Which Blueprint in your project keeps getting duplicated right now? That class's variable list is already the blueprint for your Data Asset.