You want to land a hit on an enemy. Casting to the target and decreasing its HP variable directly works fine at first. Then you add three enemy types, a breakable barrel, and a spike trap, and the attacker's Blueprint turns into a pile of Cast branches.
UE ships with a standard damage pipeline called Apply Damage. The attacker says only "20 damage to this Actor" without knowing what the target is, and the receiver handles it however it likes with Event AnyDamage. This article explains when to use each of the three Apply nodes, the receiving events, the difference between Damage Causer and Instigator, and how to build the invincibility window that stops repeated hits from killing you instantly.
What You'll Learn
- Why decreasing an HP variable directly falls apart later
- When to use Apply Damage / Apply Point Damage / Apply Radial Damage
- The receiving Event AnyDamage, and the classic double-subtraction bug
- Who Damage Causer and Instigator actually point to
- Splitting fire, poison, and other attack types with the Damage Type class
- Hands-on: land a hit, drain HP, destroy at zero, and go invincible right after being hit
- Why Decreasing HP Directly Backfires
- The Three Damage-Dealing Nodes
- The Receiving Side: Event AnyDamage
- Damage Causer vs. Instigator
- Splitting Attack Types with Damage Type
- Invincibility Frames
- Hands-On: Landing Hits and Destroying an Enemy
- Moving Health into a Component
- Bonus: Good to Know for Later
- Summary
Why Decreasing HP Directly Backfires
The first idea that comes to mind is to Cast To BP_Enemy on whatever you hit and overwrite its Health variable. With a single enemy type, that works.
The trouble starts when the number of things that can take damage grows.

Every time you add BP_Enemy, BP_Boss, BP_Barrel, BP_Spike, you add another Cast branch to the attacker. And if your attacks grow to sword, bow, and bomb, all of them need the same set of branches. The rewrite cost scales with number of targets × number of attacks.
With Apply Damage, the attacker only needs to know three things:
- Who was hit (Actor)
- How much to subtract (float)
- What kind of attack it was (Damage Type class)
The receiver's class never comes up. To make something new take damage, you add one Event AnyDamage to its Blueprint. The attacker stays untouched.
This is the same idea as loose coupling with Blueprint Interfaces, except that for damage the engine already built it for you.
The Three Damage-Dealing Nodes
There are three nodes for sending damage. All of them are GameplayStatics nodes, so you can call them from any Blueprint.
| Node | Target | Typical use |
|---|---|---|
| Apply Damage | One specified Actor | Contact damage, a sword connecting, poison damage over time |
| Apply Point Damage | One specified Actor (with hit location) | Gunfire, per-body-part damage multipliers |
| Apply Radial Damage | Everything in range | Explosions, area spells, falling rocks |

Apply Damage
This is the basic form. Its input pins are:
| Pin | Type | Meaning |
|---|---|---|
| Damaged Actor | Actor | The target taking damage |
| Base Damage | float | How much to subtract |
| Event Instigator | Controller | Who is responsible for this attack (more below) |
| Damage Causer | Actor | What physically hit (more below) |
| Damage Type Class | class | The kind of attack. Defaults to the standard DamageType |
The return value is the amount that passed through TakeDamage (float). Worth noting: even if you skip the HP subtraction or bail out inside Event AnyDamage, the return value does not change. Normally you get back exactly what you sent.
You get 0 when the damage pipeline itself never ran, such as when the target's Can Be Damaged is false. It is not "0 because the receiver rejected it."
Also, only Apply Radial Damage returns a bool, telling you whether it applied to at least one Actor. Assuming all three return a number is a good way to confuse yourself.
One more thing: if you pass 0 to Base Damage, the receiving event is never called at all. You cannot use it to "deal 0 damage but still play the hit effect."
Apply Radial Damage
Radial damage works differently. Instead of naming a target, you give an origin and a radius, and the engine gathers the Actors in that area for you.
| Pin | Meaning |
|---|---|
| Origin | The center of the blast |
| Damage Radius | The radius of effect |
| Ignore Actors | Array of Actors to exclude (yourself, for example) |
| Do Full Damage | On: everything inside the radius takes full Base Damage regardless of distance. Off (default): falls off linearly from Base Damage at the origin to 0 at the edge |
| Damage Prevention Channel | The channel used for the occlusion check. Defaults to Visibility |
Damage Prevention Channel traces a line from the blast origin toward each target so that anything behind a wall is spared. Leave it at the default and targets on the far side of a wall take nothing.
If your explosions feel too wide or never connect at all, check how the target's collision preset treats that channel.
This is the biggest practical win of using the built-in system. Writing explosion damage yourself means an overlap sphere, a loop, an occlusion check, and a Cast per target: dozens of nodes. Apply Radial Damage does it in one, and it automatically reaches destructible objects you add later.
The Receiving Side: Event AnyDamage
Right-click in the event graph of any Actor that should take damage and add the matching event.
| Receiving event | When it fires |
|---|---|
| Event AnyDamage | All damage (all three kinds) |
| Event PointDamage | Only for Apply Point Damage |
| Event RadialDamage | Only for Apply Radial Damage |
Event AnyDamage has four pins.
| Pin | Type | Meaning |
|---|---|---|
| Damage | float | Amount received |
| Damage Type | DamageType | The kind of attack (an object) |
| Instigated By | Controller | Who is responsible |
| Damage Causer | Actor | What hit |
Warning: Event AnyDamage fires for Point and Radial damage too. So if you subtract HP in both
Event AnyDamageandEvent PointDamage, gunfire subtracts twice. This is a classic bug that is hard to track down. Keep HP subtraction in Event AnyDamage only, and use PointDamage purely for the impact location and the body part that was hit.

Event PointDamage gives you Hit Location, Hit Normal, Bone Name, and Shot From Direction. The easiest way to detect a headshot is to check whether Bone Name is head.
Damage Causer vs. Instigator
These two are easy to mix up, but they point at different things.

| Type | What it points to | Example | |
|---|---|---|---|
| Instigator (Instigated By) | Controller | Whose attack this is. Where responsibility lies | The player's PlayerController |
| Damage Causer | Actor | What made contact. The physical source | The projectile Actor that was fired |
When a player's bullet hits an enemy:
- Instigator = the player's PlayerController
- Damage Causer =
BP_Projectile(the bullet)
The differing types are the point. Instigator is a Controller so that responsibility is tracked at the level of whoever is driving the pawn. Even if the character dies, the Controller still tells you whose attack it was.
That said, a Controller is not guaranteed to stick around. AIControllers can be destroyed depending on your settings. If you need reliable tallies, record the score at the moment of the kill.
In practice, you split them like this:
- Score and kill logs → look at Instigator (who gets credit)
- Knockback direction → look at Damage Causer (where it came from)
- Preventing self-damage → check whether Damage Causer is yourself, or whether Instigator is your own Controller
When you spawn a projectile, pass Self to the Instigator pin of Spawn Actor from Class. That pin's type is Pawn, which is separate from the Controller on the damage nodes. On the projectile side, Get Instigator Controller gets you back to the responsible Controller. Leave it empty and explosion kills will credit nobody.
Splitting Attack Types with Damage Type
To distinguish fire, poison, falling, and explosion damage, use the DamageType class.
- Create a Blueprint Class in the Content Browser and pick DamageType as the parent
- Name it something like
DT_FireorDT_Poison - Set it on the Damage Type Class pin of Apply Damage
On the receiving side, branch on the Damage Type pin of Event AnyDamage.
Event AnyDamage
└─ Damage Type ─→ Cast To DT_Fire
├─ Success → play fire effect / double damage for wooden enemies
└─ Failed → normal handling
You can add variables to a DamageType Blueprint. Give it something like ElementIcon (an icon for display) or bIgnoreArmor, and the receiver can just read those values instead of testing each type one by one.
Note: Once you pass ten or so attack types and start wanting combinations like "fire and area," adding more DamageType classes becomes harder to manage than classifying with Gameplay Tags.
Invincibility Frames
Build damage handling and you will almost certainly hit the "touch an enemy and your entire health bar vanishes" problem.
The cause is simple: while attack volumes overlap, damage lands every frame. At 60 FPS that is 60 hits per second. Brushing against an enemy that deals 10 damage wipes out 600 HP.

The fix is an invincibility window (i-frames). It is the period where the character flashes after being hit in fighting and action games.
You only need two things: a flag and a timer.
- Add a
boolvariable namedIs Invincible - Put a Branch at the top of
Event AnyDamageand finish immediately ifIs Invincibleistrue - After damage goes through, set
Is Invincibletotrue - Use Set Timer by Event to set it back to
falseafter a delay
Set Timer by Event calls a custom event once after the number of seconds you specify. A Delay node can do the same thing, but if execution reaches a Delay that is still waiting, the later call is ignored. For something that runs repeatedly like taking hits, a Timer that fires exactly as many times as you asked is easier to reason about. (If you want the wait to restart each time, use Retriggerable Delay.)
Note: Actors have a Can Be Damaged flag that you toggle with
Set Can Be Damaged. All three Apply nodes respect it, so setting it to false alone makes the Actor invincible. If you simply want to disable everything, this is the quick route.If you need finer control, though, such as "immune to fire only," "still take knockback while invincible," or "still show the hit effect," it is easier to check your own flag on the receiving side as described above.
Pick the length of the window based on how it feels. As a starting point, 0.5 to 1.5 seconds works well for taking a hit in an action game.
Hands-On: Landing Hits and Destroying an Enemy
Enemies in a Metroidvania, mooks in a beat-em-up, melee attacks in a top-down ARPG. The chain of land a hit → HP drops → destroyed at zero → mashing does not stack is required in every genre. Let's build the whole thing.
The Finished Result
Press E to punch in front of you, and the Cube ahead drops from 100 HP in steps of 20, reaching 0 and disappearing on the fifth hit. Mashing the key only lands one hit per second.

Setup
Create a new project from the Third Person template and prepare these two Blueprints.
Receiver: Create a Blueprint with Actor as the parent class and name it BP_Damageable. Add a StaticMesh component and assign Cube.
Check these three things in the Details panel. If any of them is off, the Line Trace misses and no damage goes through.
| Where to check | Value |
|---|---|
| StaticMesh Collision Presets | BlockAll (a setting that Blocks Visibility) |
Can Be Damaged in Class Defaults | true (on by default) |
| Placement in the level | Within 500 cm in front of the player and at the character's waist height (the Line Trace fires horizontally from Get Actor Location, so a Cube sitting on the floor is passed straight over) |
| Variable | Type | Default | Purpose |
|---|---|---|---|
MaxHealth | Float | 100.0 | Maximum health |
CurrentHealth | Float | 100.0 | Current health |
IsInvincible | Boolean | false | Whether invincibility is active |
InvincibleTime | Float | 1.0 | How long invincibility lasts, in seconds |
Also add one Event Dispatcher named OnHealthChanged, with two inputs: NewHealth (Float) and MaxHealth (Float).
Attacker: Open BP_ThirdPersonCharacter. Use a Line Trace for hit detection, firing 500 cm forward.
For input, keep it simple and use a direct E key event (right-click in the event graph → "Keyboard Events > E"). If you prefer Enhanced Input, create IA_Attack and bind it to E in IMC_Default (see the Enhanced Input article).
The Attacker's Graph
Build this in the event graph of BP_ThirdPersonCharacter.
E (Pressed)
→ Line Trace By Channel
Start = Get Actor Location
End = Get Actor Location + (Get Actor Forward Vector × 500)
Channel = Visibility
Ignore Self = true
→ Branch (Return Value)
True → Break Hit Result → Hit Actor
→ Apply Damage
Damaged Actor = Hit Actor
Base Damage = 20.0
Event Instigator = Get Controller
Damage Causer = Self
Don't forget to feed Get Controller into Event Instigator. Leave it empty and you will have no way to tell whose attack it was when you add kill logs or scoring later.
The Receiver's Graph
Build the whole path from taking a hit to dying in BP_Damageable's event graph.

Event AnyDamage (Damage / Damage Type / Instigated By / Damage Causer)
→ Branch (Condition = IsInvincible)
True → (do nothing, end here)
False ↓
→ Set CurrentHealth = Clamp(CurrentHealth - Damage, 0.0, MaxHealth)
→ Call OnHealthChanged (NewHealth = CurrentHealth, MaxHealth = MaxHealth)
→ Set IsInvincible = true
→ Set Timer by Event
Event = custom event "EndInvincible"
Time = InvincibleTime
Looping = false
→ Branch (Condition = CurrentHealth <= 0.0)
True → Print String "Destroyed" → Destroy Actor
Custom Event EndInvincible
→ Set IsInvincible = false
The Clamp is there to keep HP from going negative. HP bars compute their ratio as CurrentHealth / MaxHealth, so a negative value breaks the display.
Verifying It
Press Play, stand in front of the Cube, and press E.
- One press →
80appears (the initial 100 is never printed unless you print it separately) - Five presses, at least a second apart →
80 → 60 → 40 → 20 → 0, thenDestroyedright after the fifth and the Cube disappears - Three rapid presses → only one lands, printing
80once
Since HP is invisible otherwise, hook a Print String to CurrentHealth right after Set CurrentHealth.
If it doesn't work:
- Nothing happens → the Line Trace is missing. Set
Draw Debug TypetoFor Durationand look at the line (the Line Trace article) - The line hits but HP doesn't drop →
Event AnyDamageis in the Level Blueprint instead ofBP_Damageable - It dies in one hit →
Base Damageis too large, orCurrentHealthstill defaults to 0 - Mashing goes right through → the invincibility
Branchsits after the HP subtraction instead of at the entry point - You hit yourself →
Ignore Selfis off onLine Trace By Channel - HP goes negative → there is no
Clamp
Once this works, duplicate the Cube and place a second one. Damage lands on both without changing a single thing on the attacker. That is exactly the benefit described in the first section.
Two things to take away.
- Always put the invincibility check on the receiver: If the attacker decides "I hit recently, skip this one," every new attack needs the same check. One check on the receiver works the same for swords, explosions, and spikes
- Announce health changes with a Dispatcher: Fire
OnHealthChangedand you can add an HP bar, damage numbers, and hit effects later without touching the receiver (see the Event Dispatcher article)
Putting an HP bar on screen continues in the UMG article. All you do is Bind to OnHealthChanged from the Widget.
Moving Health into a Component
Write the same logic for enemies, the player, and breakable barrels and you notice something: they are all identical.
So move the health logic into an Actor Component. Create BP_HealthComponent and shift the variables and logic there.
One catch: you cannot place Event AnyDamage in an Actor Component. Damage events are received by the Actor. Instead, use the Component's Event BeginPlay to subscribe to the Owner's event.

Event BeginPlay (BP_HealthComponent)
→ Get Owner
→ Bind Event to On Take Any Damage
Event = custom event "HandleDamage"
Custom Event HandleDamage (Damaged Actor / Damage / Damage Type / Instigated By / Damage Causer)
→ (same logic as the previous section)
On Take Any Damage is the Actor's Dispatcher for damage. It fires at the same moment as Event AnyDamage.
Now any Actor that needs health just adds BP_HealthComponent. No copying when you make a new enemy, or a new barrel. Splitting logic into Components is covered in detail in the Component reuse article.
Bonus: Good to Know for Later
- Don't route healing through the same path: Avoid passing a negative value to
Apply Damage. The event is not called whenBase Damageis 0, and your armor calculations and invincibility checks would all apply as well. A separateHealfunction is far cleaner - Subtract armor on the receiver: If the attacker computes "damage after armor," adding equipment means fixing every attacker. Subtracting your own armor from the
Damageyou get inEvent AnyDamageis the right place for it - Death is "stop" before "delete": Calling
Destroy Actorimmediately means no death animation and no effects play. Disable input and collision first, play the presentation, then Destroy or return to a pool. For humanoid enemies, letting physics handle that presentation is what Physics Assets and ragdolls is about - Fall damage is yours to send: UE Characters take no damage from falling by default. Use
Event On Landedto check the height difference or theZofVelocity, and send yourself anApply Damagewhen it crosses your threshold (see the Character Movement article) Apply Point Damagedoes not do hit detection: The name suggests it finds the hit point for you, but you determine the hit yourself. You pass theHit Resultand direction from a Line Trace in as argumentsBone Namechecks need groundwork: The trace has to hit a Skeletal Mesh correctly and a Physics Asset has to be set up. Comparing the stringheadalone won't do it- Apply nodes only run on the server: When you expand to multiplayer, damage is applied on the server and HP and effects are synced separately. You can ignore this while building single-player
- Damage Type is a shared settings sheet: It is not instantiated per attack; you are just reading class defaults. It is a place for values that never change, like icons and multipliers, not for state such as remaining duration or attacker
- When the numbers don't add up, check the return value: Print the return value of
Apply Damageand you'll see whether the damage pipeline ran at all. If it returns 0, suspectCan Be Damaged(see the debugging article)
Summary
- Decreasing an enemy's HP variable directly costs you a rewrite for every target × attack combination
- The sending side has three nodes: Apply Damage / Point / Radial. For area attacks, the engine gathers the targets
- The receiving side should be consolidated into Event AnyDamage. Subtracting in PointDamage too costs you double damage
- Instigator is a Controller (whose attack), Damage Causer is an Actor (what hit). Different types
- Reject repeated hits with a flag and a Timer on the receiver. Never put the invincibility check on the attacker
- Once you find yourself writing the same logic repeatedly, split it into an Actor Component and Bind to the Owner's On Take Any Damage
How many kinds of things take damage in the game you're building right now? If even one more is planned, it is worth putting them all on this pipeline.