[UE5] Gameplay Tag Basics: Using Enemy Classification to Build Holy Water That Hurts Undead

Created: 2026-02-07Last updated: 2026-09-07

Understand Gameplay Tag hierarchies and Containers through holy water that hurts undead. Covers registering tags, tagging enemies, Has Tag's Exact Match, the Blueprint wiring, and checking the resulting HP.

You want holy water that hurts only the undead. Test enemies one by one — skeleton, zombie, ghoul — and every new enemy means editing the holy water's conditions too.

Gameplay Tags let you attach a label saying "this enemy is one of the undead". The holy water just checks the Enemy.Undead classification. Add another enemy of the same classification later and the holy water needs no new checks.

Undead and Beast under Enemy, with the Undead branch selected as a group

What You'll Learn

  • Using a tag hierarchy to test a whole group at once
  • The difference between the tag dictionary and the tags an enemy actually has
  • Has Tag's "Exact Match", and choosing between Any and All
  • An experiment where undead end at HP 0 and a wolf ends at HP 80

You can do this article entirely in Blueprint. If you can create variables and connect nodes, you can follow along. No C++ or Gameplay Ability System (GAS) required.

Sponsored

Test the classification instead of listing types

Writing "if Skeleton then bonus, if Zombie then bonus" per enemy type scatters the same classification decision across holy water, spells, and everything else. The problem is not only that there are many enemy types, but that you re-list the same group every time .

Comparing per-name enemy checks against checking the Enemy.Undead classification

With Gameplay Tags the enemy carries its classification and the user side asks for the classification it needs. Adding "an enemy that counts as undead" becomes mostly a matter of enemy setup.

An Enum is a type for choosing one option from a prepared list. You can also add a separate "species" field with an Enum, or gather the classification into one function. It is not that Enums cannot classify.

What you want to expressExample choice
Currently one of idle, chase, or attackChoose the current state with an Enum
This enemy is undead and also poisonedHold multiple traits with a Gameplay Tag Container
You only need to remember on or offA bool is sometimes enough

Designs that narrow to one state are covered in Enum and Switch state machines. Before switching to tags, ask "are these traits something held at the same time?" and the roles separate cleanly.

A Gameplay Tag is a label with a hierarchy

Tags use . (a dot) to go from broad classification to fine.

Enemy.Undead.Skeleton means "Skeleton" inside "Undead" inside "Enemy". The left is the broad classification, and it gets more specific toward the right.

A hierarchy branching Enemy into Undead and Beast, with Skeleton and Zombie under Undead and Wolf under Beast

Testing with a parent classification also matches its child tags. Give a skeleton Enemy.Undead.Skeleton and it is found by Enemy.Undead and by the broader Enemy too.

The reverse does not hold. Knowing only Enemy.Undead cannot decide whether the target is a skeleton or a zombie.

Testing Skeleton against Undead matches; testing Undead against Skeleton does not; Exact Match on rejects parent-only matches

In Blueprint's Has Tag , turning Exact Match off tests including parent tags, and turning it on tests only whether it has that exact tag. A match returns true and a mismatch false. Our holy water affects undead in general, so we leave it off.

Note that putting dots in a Name type used for identification does not add this parent-child matching. Gameplay Tags provide the dictionary and hierarchy-based testing.

Register tags in the dictionary and use them in variables

Preparing tags splits into registering the tags you can use and actually attaching them to enemies . Registering a skeleton tag in the dictionary does not put that tag on a placed enemy.

Register in Project Settings

  1. Open "Edit → Project Settings" and go to "Project" → "GameplayTags".
  2. Enable "Import Tags From Config".
  3. Open "Manage Gameplay Tags" in "Gameplay Tag List" and press the "+" to add.
  4. Enter Enemy.Undead.Skeleton for Name and a purpose in Comment. Choose DefaultGameplayTags.ini for Source and confirm with "Add New Tag".
  5. Register Enemy.Undead.Zombie and Enemy.Beast.Wolf the same way.
Two stages: registering tags in Project Settings, then choosing registered tags on the enemy's EnemyTags variable

Typing the full long name up front also creates the intermediate levels Enemy and Enemy.Undead. Expand the tree and confirm the three leaf tags sit in their places.

One tag versus a set of tags

Blueprint variables use these types.

TypeWhat it holdsOur use
Gameplay TagOne tagThe Enemy.Undead the holy water tests
Gameplay Tag ContainerA set of multiple tagsEnemy classification and status

A Container is a container for holding tags together. One enemy can carry Enemy.Undead.Skeleton and Status.Debuff.Poison at the same time. This is different from making a Gameplay Tag variable an array; choose the "Gameplay Tag Container" type.

It is also different from the "Tags" already present in an Actor's Details. Those are Name-type Actor Tags, tested with "Actor Has Tag". This article passes our own EnemyTags variable to Has Tag .

Source decides where it is saved

We register into Config/DefaultGameplayTags.ini . A list like this is saved.

[/Script/GameplayTags.GameplayTagsSettings]
+GameplayTagList=(Tag="Enemy.Undead.Skeleton",DevComment="Skeleton")
+GameplayTagList=(Tag="Enemy.Undead.Zombie",DevComment="Zombie")
+GameplayTagList=(Tag="Enemy.Beast.Wolf",DevComment="Wolf")

To split by feature, add a Source in the GameplayTags settings and register into a separate file under Config/Tags . If you edit the ini directly, restart the editor so it reloads. Registering from the settings screen is enough to start.

Sponsored

Has Tag, Any, All, and Matches Tag

Consider an enemy holding Enemy.Undead.Skeleton and Status.Debuff.Poison . All of the following assume Exact Match is off .

NodeThe questionResult
Has TagDoes it have Enemy.Undead?true
Has TagDoes it have Enemy.Beast?false
Has Any TagsDoes it have either Enemy.Undead or Enemy.Beast?true
Has All TagsDoes it have both Enemy.Undead and Status.Debuff?true
Has All TagsDoes it have both Enemy.Undead and Immune.Fire?false

Any means "one of", All means "every one of". With Has Any Tags and Has All Tags, the thing being tested goes to Tag Container and the condition set goes to Other Container.

To compare one tag against another, use Matches Tag . Pass the target's tag to Tag One and the classification you are testing to Tag Two. Tag One=Enemy.Undead.Skeleton with Tag Two=Enemy.Undead is true, but reversing them is false.

What Has Any Tags, Has All Tags, and Matches Tag each return for the same enemy tags

If you want to add conditions like "only poisoned undead", Has All Tags becomes the candidate. If you also try poison and fire-resist tags, register those names in the dictionary first. In the hands-on below, one Has Tag against registered enemy tags tells us whether something is undead.

Hands-On prep: line up three kinds of enemy

In a Third Person Blueprint project, place cubes standing in for a skeleton, a zombie, and a wolf. Using the holy water deals 100 damage to undead and 20 to everything else — a five-times bonus.

To keep the tag testing easy to watch, the H key applies the effect once to each practice enemy in the level . Throwing motions and range checks are things to add after the basics work.

The finished result: pressing H drops the skeleton and zombie cubes to HP 0 and the wolf cube to HP 80

Create the enemy parent class

Create "Blueprint Class → Actor" from the Content Browser and name it BP_EnemyBase . Add a Static Mesh from Components' "Add" and name it Body .

Set Body's Static Mesh to the engine's basic Cube shape. Enable "Show Engine Content" in the content display settings to find it. Set Collision Presets to NoCollision . We deal damage by calling an enemy event rather than by contact, so no collision is needed.

Create the following variables, compile, and set the defaults.

VariableTypeDefault
EnemyTagsGameplay Tag ContainerEmpty
EnemyNameTextEnemy
HealthFloat100.0
Preparing BP_EnemyBase with a Cube Body and the EnemyTags, EnemyName, and Health variables

EnemyTags and EnemyName get changed in each child BP's Class Defaults. Leave Instance Editable off for now, since we are not doing per-instance overrides.

Give each child class its tag

Right-click BP_EnemyBase and create three child BPs with "Create Child Blueprint Class". Open each and set the following in "Class Defaults".

Child BPTag to choose in EnemyTagsEnemyName
BP_SkeletonEnemy.Undead.SkeletonSkeleton
BP_ZombieEnemy.Undead.ZombieZombie
BP_WolfEnemy.Beast.WolfWolf

Open EnemyTags' edit field and choose the matching leaf tag from the dictionary tree. You do not need to add the parent tags separately. BP_Skeleton, for instance, gets only the Skeleton tag.

Place the three in the level in front of Player Start, spaced out left to right as Skeleton, Zombie, Wolf. Place the child BPs, not BP_EnemyBase itself, which has no tags.

Build the logic that drains and displays enemy HP

Add a Custom Event to BP_EnemyBase's Event Graph and name it ApplyHolyDamage . A Custom Event is an entry point you name and call yourself. The holy water calls it and the enemy drains HP by the damage it receives.

Select the event and add one input in Details → Inputs. Name it Damage , type Float, and compile. The child BPs inherit this event.

Compute the new HP

Feed a Get of Health and Damage into a subtract node. The top input is Health and the bottom is Damage. Pass the result to Clamp (Float) with Min = 0.0 and Max = 100.0.

Subtracting Damage from Health and clamping between 0 and 100

Clamp brings a value up to the minimum or down to the maximum. Taking 100 damage at 20 HP gives 0, not -80.

Connect ApplyHolyDamage's white exec wire to Set Health and feed the computed value in. Run Print String after that.

Running Set Health from ApplyHolyDamage and printing the updated HP

In the diagram, M is the Damage value, N is Clamp's result, and L marks the display string we build next. In practice you wire them in the same graph. Do not create nodes for the symbols themselves.

Display which enemy's HP it is

Create a Format Text node and enter {Enemy}: HP {HP} for Format. Connect a Get of EnemyName to the added Enemy pin and a Get of Health to the HP pin.

Passing EnemyName and the updated Health into Format Text and converting it to a string for display

Format Text fills values into the braces. Convert its output with To String (Text) and connect it to Print String's In String. Set Duration to 5.0 and enable Print to Screen and Print to Log.

Since Print String runs after Set Health, it reads the updated value, like Skeleton: HP 0 . In this exercise we leave the HP 0 cubes in place so the numbers can be compared.

Have the holy water inspect enemy tags

Create another Actor BP and name it BP_HolyWater . Create a TargetTag variable of type Gameplay Tag and, after compiling, choose Enemy.Undead as its default. Turn Instance Editable on so a placed holy water can change its target.

In the Event Graph, create a Custom Event UseHolyWater with no arguments.

Take enemies one at a time

Wire UseHolyWater → Get All Actors of Class → For Each Loop with white wires. Actor Class is BP_EnemyBase. Pass the Out Actors array to For Each Loop's Array.

Gathering BP_EnemyBase enemies from UseHolyWater and taking them one at a time with For Each Loop

An array is the list of enemies found. For Each Loop takes one at a time from that list and processes it. The Skeleton, Zombie, and Wolf children of BP_EnemyBase are all gathered.

In the diagram, A is Loop Body's exec wire and B is the Array Element reference. A reference specifies which individual in the level you are handling. B represents "the one we test and send damage to this iteration". Completed and Array Index go unused here.

Test whether that enemy is undead

Drag from Array Element and create a Get of EnemyTags. Target receives that enemy's reference. Create Has Tag from EnemyTags' output and pass the TargetTag variable to Tag. Uncheck Exact Match.

Passing the fetched enemy's EnemyTags and the holy water's TargetTag to Has Tag with Exact Match off

Has Tag's result is a bool : true means the condition matched, false means it did not. Connect that red result to a Branch's Condition and Loop Body's white wire to the Branch's exec input.

Running a Branch from Loop Body and splitting into True and False on Has Tag's result

In the diagram, C is Has Tag's result, D is the Branch's True side, and E is the False side. Has Tag returns a value, so it takes no white exec wire.

Send 100 to matches and 20 to everything else

Place one Apply Holy Damage call on the True side and one on the False side. Connect the same Array Element reference to both Targets. True's Damage is 100.0 and False's is 20.0.

Sending 100 damage to the same enemy from the Branch's True side and 20 from the False side

Create the call nodes by dragging from Array Element and searching for Apply Holy Damage . That calls the Custom Event defined on the enemy. You do not create a same-named Custom Event on BP_HolyWater.

Now the holy water picks its power by whether an enemy falls under Enemy.Undead, without listing a single enemy name. The enemy side just drains HP by the Damage it receives.

Sponsored

Use it with the H key and check the result

Place one BP_HolyWater in the level. It needs no visuals. All practice enemies in the level are targets here, so where you place it does not change the power.

Select that BP_HolyWater in the Outliner and open the level editor's "Blueprints → Open Level Blueprint". Create a reference to the placed individual with right-click "Create a Reference to BP_HolyWater" or by dragging from the Outliner.

Add an H key event and call Use Holy Water from Pressed. Connect the BP_HolyWater reference to Target. Leave Released unconnected.

Calling the placed BP_HolyWater's Use Holy Water from H's Pressed

Compile and save each BP, then Play. Click the game window and press H once .

EnemyMatches the bonusDamageRemaining HP
Skeletontrue1000
Zombietrue1000
Wolffalse2080

The order enemies are fetched in is not fixed, so read the names and values rather than the log order. Once the on-screen text fades, you can re-read it in the Output Log.

Press H again and Wolf becomes 60. Stop and Play again between comparison experiments and everyone starts from 100.

If the result looks wrongWhere to check
No log lines at allH's Pressed, the placed holy water reference, UseHolyWater's wire, Actor Class
Everyone lands at HP 80The child BPs' EnemyTags, TargetTag = Enemy.Undead, whether Exact Match is off
Everyone lands at HP 0Whether TargetTag is the too-broad Enemy, and whether False's Damage is 20
Only one enemy keeps droppingWhether both EnemyTags' Target and Apply Holy Damage's Target are Array Element
The display shows pre-damage HPWhether Set Health runs before Print String

Try a new enemy and a different affinity

Add a wraith without changing the holy water

Add Enemy.Undead.Wraith to the dictionary. Create BP_Wraith as a child of BP_EnemyBase and set that tag in EnemyTags and Wraith in EnemyName. Add one more cube to the level, Play again, and press H.

If Wraith also reaches HP 0, it worked. BP_HolyWater has always been "gather BP_EnemyBase relatives and test Enemy.Undead", so you never write the new type into the holy water .

Change only the target tag

Now Stop and try changing the placed BP_HolyWater's TargetTag. Play again each time and press H once.

TargetTagSkeletonZombieWolf
Enemy.UndeadHP 0HP 0HP 80
Enemy.BeastHP 80HP 80HP 0
Enemy.Undead.SkeletonHP 0HP 80HP 80

Asking with a parent tag hits a broad group; asking with a fine tag hits a narrow classification. Because "the tags an enemy has" and "the tag an attack tests" are separate, the same BP works as a different affinity.

Changing only TargetTag changes who is affected: Skeleton alone, all undead, or all beasts

Bonus: Good to Know Up Front

Attaching a tag does not produce an effect

Attaching a poison tag does not drain HP by itself. A tag is data representing a state; what happens in that state is decided by logic.

To add and remove at runtime, use Add Gameplay Tag and Remove Gameplay Tag on EnemyTags. Whether it is right to remove the tag when one of several stacked poisons expires is a separate question. Tag presence alone does not track stack counts or remaining duration.

Calling All with an empty condition returns true

If Has All Tags' Other Container is empty, "there is no required tag", so it returns true. Has Any Tags returns false, because there is no candidate to find. If you load conditions from data, decide whether empty is allowed.

Check usage sites for renames and C++ definitions too

You can rename registered tags from the management screen. After renaming, check the assets and saved data that reference them. Do not assume tag names written as strings in code are all fixed automatically.

In C++ you can define tags with the macros in NativeGameplayTags.h and reference them as variables from code. Compilation catches misspelled variable names, but it does not guarantee the meaning or spelling of the tag string in the definition. None of this is needed for our Blueprint hands-on.

Extending to attack ranges and skills

The Get All Actors of Class here is a fetch method for comparing tag differences. When extending to area attacks, select targets with collision or Line Trace and inspect those targets' EnemyTags the same way. Tags do not change collision settings.

To combine complex abilities and effects, tags are also used in the Gameplay Ability System. Confirming in a small experiment which classification changes what, as we did here, makes that easier to understand.

When building a tag hierarchy, use "is there a classification I want to handle as a group?" as your criterion. Narrowing to the groups you actually test, rather than cramming appearance and material into long names, keeps the consuming logic readable too.

Summary

  • Tags are hierarchical, so Enemy.Undead tests a whole group
  • The dictionary (registered tags) is separate from the tags actually attached
  • Turning Exact Match off picks up the levels below as well
  • Any and All separate "at least one" from "every one"

The design question to ask is "will this test gain more members later?" If it will, receive it through a tag hierarchy.

To hold it on the data side, go to Data Asset; to build it as an ability, go to GAS Basics.

Reference: Gameplay Tags, Has Tag, Has All Tags, Matches Tag.

Unreal Engine Notes in this section98