Health and Damage in UE5: Landing Hits with Apply Damage and Event AnyDamage

Created: 2026-07-20

A visual guide to UE's standard damage pipeline. Covers the three Apply nodes (Damage / Point / Radial), the receiving Event AnyDamage, the difference between Damage Causer and Instigator, splitting attack types with Damage Type, and building the invincibility window that stops enemies from killing you instantly on contact.

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.

Arrows extending from an attacker and reaching an enemy, a barrel, and a trap in exactly the same shape

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

Sponsored

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.

On the left, Cast branches fanning out from the attacker to an enemy, a barrel, and a trap; on the right, a single Apply Damage line

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.

Sponsored

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.

NodeTargetTypical use
Apply DamageOne specified ActorContact damage, a sword connecting, poison damage over time
Apply Point DamageOne specified Actor (with hit location)Gunfire, per-body-part damage multipliers
Apply Radial DamageEverything in rangeExplosions, area spells, falling rocks
Apply Damage, Point Damage, and Radial Damage compared as a single target, a single target with an impact point, and a spherical area

Apply Damage

This is the basic form. Its input pins are:

PinTypeMeaning
Damaged ActorActorThe target taking damage
Base DamagefloatHow much to subtract
Event InstigatorControllerWho is responsible for this attack (more below)
Damage CauserActorWhat physically hit (more below)
Damage Type ClassclassThe 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.

PinMeaning
OriginThe center of the blast
Damage RadiusThe radius of effect
Ignore ActorsArray of Actors to exclude (yourself, for example)
Do Full DamageOn: 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 ChannelThe 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.

Sponsored

The Receiving Side: Event AnyDamage

Right-click in the event graph of any Actor that should take damage and add the matching event.

Receiving eventWhen it fires
Event AnyDamageAll damage (all three kinds)
Event PointDamageOnly for Apply Point Damage
Event RadialDamageOnly for Apply Radial Damage

Event AnyDamage has four pins.

PinTypeMeaning
DamagefloatAmount received
Damage TypeDamageTypeThe kind of attack (an object)
Instigated ByControllerWho is responsible
Damage CauserActorWhat hit

Warning: Event AnyDamage fires for Point and Radial damage too. So if you subtract HP in both Event AnyDamage and Event 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.

Comparison showing a single Apply Point Damage reaching both Event AnyDamage and Event PointDamage, subtracting HP in both for a wrong total of -40, versus the correct form where only AnyDamage subtracts

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.

Arrows running from the player's controller to the character, from the character to the projectile, and from the projectile to the enemy, showing Instigator at the top of the chain and Damage Causer at the end
TypeWhat it points toExample
Instigator (Instigated By)ControllerWhose attack this is. Where responsibility liesThe player's PlayerController
Damage CauserActorWhat made contact. The physical sourceThe 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.

  1. Create a Blueprint Class in the Content Browser and pick DamageType as the parent
  2. Name it something like DT_Fire or DT_Poison
  3. 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.

Top row: arrows striking every frame and emptying the health bar instantly. Bottom row: only the first hit landing and the rest being rejected

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.

  1. Add a bool variable named Is Invincible
  2. Put a Branch at the top of Event AnyDamage and finish immediately if Is Invincible is true
  3. After damage goes through, set Is Invincible to true
  4. Use Set Timer by Event to set it back to false after 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.

Sponsored

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.

HP readouts dropping 80 → 60 → 40 → 20 → 0 each time the player punches the Cube, with mashed inputs being rejected

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 checkValue
StaticMesh Collision PresetsBlockAll (a setting that Blocks Visibility)
Can Be Damaged in Class Defaultstrue (on by default)
Placement in the levelWithin 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)
VariableTypeDefaultPurpose
MaxHealthFloat100.0Maximum health
CurrentHealthFloat100.0Current health
IsInvincibleBooleanfalseWhether invincibility is active
InvincibleTimeFloat1.0How 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.

Blueprint node graph running from Event AnyDamage through a Branch, HP subtraction, the Dispatcher, the timer, and the death check
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 press80 appears (the initial 100 is never printed unless you print it separately)
  • Five presses, at least a second apart80 → 60 → 40 → 20 → 0, then Destroyed right after the fifth and the Cube disappears
  • Three rapid presses → only one lands, printing 80 once

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 Type to For Duration and look at the line (the Line Trace article)
  • The line hits but HP doesn't dropEvent AnyDamage is in the Level Blueprint instead of BP_Damageable
  • It dies in one hitBase Damage is too large, or CurrentHealth still defaults to 0
  • Mashing goes right through → the invincibility Branch sits after the HP subtraction instead of at the entry point
  • You hit yourselfIgnore Self is off on Line 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 OnHealthChanged and 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.

Sponsored

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.

HealthComponent binding to the Owner's OnTakeAnyDamage during BeginPlay, after which damage flows into the Component's logic
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 when Base Damage is 0, and your armor calculations and invincibility checks would all apply as well. A separate Heal function 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 Damage you get in Event AnyDamage is the right place for it
  • Death is "stop" before "delete": Calling Destroy Actor immediately 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 Landed to check the height difference or the Z of Velocity, and send yourself an Apply Damage when it crosses your threshold (see the Character Movement article)
  • Apply Point Damage does not do hit detection: The name suggests it finds the hit point for you, but you determine the hit yourself. You pass the Hit Result and direction from a Line Trace in as arguments
  • Bone Name checks need groundwork: The trace has to hit a Skeletal Mesh correctly and a Physics Asset has to be set up. Comparing the string head alone 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 Damage and you'll see whether the damage pipeline ran at all. If it returns 0, suspect Can 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.