Death and Defeat Cleanup Flow¶
Death is not one node. It is the moment your project switches an actor from "can still play" to "cannot keep receiving normal gameplay," then cleans up the systems that still remember it.
The one-minute version¶
- Death should start from one guarded transition, usually the HealthComponent's
first
CurrentHealthchange to0. - Do not let every damage event, animation notify, overlap, or timer run its
own death branch. Use one
bIsDeadorDeathStategate. - Disable gameplay systems before visual cleanup: input, movement, attack hitboxes, collision, AI decisions, active timers, item use, and repeated damage sources.
- Then run presentation: death montage, ragdoll, sound, camera, UI, score message, or respawn prompt.
- Destroying the actor is optional. A corpse, disabled pickup, defeated boss, or respawnable player may stay in the world for a while.
- In multiplayer, the server decides death, score credit, respawn, and replicated removal. Clients show the result.
Death is a state transition¶
The common beginner shortcut is:
Event AnyDamage
-> Health = Health - Damage
-> Branch Health <= 0
-> Destroy Actor
That hides several different decisions inside one branch:
- Is the actor already dead?
- Should it stop receiving input or AI commands?
- Should collision stay for a corpse or turn off for gameplay?
- Should attack windows, poison ticks, or timers stop?
- Should a montage play before the actor is destroyed?
- Should the player respawn or should the whole match end?
- Should score be awarded once?
Use a named death transition instead:
flowchart TD
Damage[Damage accepted] --> Health[Health reaches 0]
Health --> Gate{Already dead?}
Gate -->|yes| Stop[Ignore duplicate death]
Gate -->|no| Begin[BeginDeath]
Begin --> Disable[Disable gameplay systems]
Disable --> Present[Play death presentation]
Present --> Outcome{Outcome}
Outcome --> Respawn[Respawn / restart]
Outcome --> Corpse[Corpse / defeated state]
Outcome --> Destroy[Destroy Actor / Set Life Span]
The key is that BeginDeath runs once.
What runs once¶
Put the one-shot transition behind a gate:
pseudocode - project logic, not engine source
function BeginDeath(DeathContext):
if bIsDead:
return
bIsDead = true
DeathContextLastHit = DeathContext
DisableGameplayForDeath()
OnDeathStarted(DeathContext)
PlayDeathPresentation(DeathContext)
ChooseDeathOutcome()
DeathContext is your own small bundle if you need it: instigator controller,
damage causer, damage type, hit location, or score reason. Do not overbuild it
on day one; add fields when score, UI, or effects actually need them.
This one function is the place to award score, increment kill/death counters,
start respawn delay, and stop looping damage. If another graph notices
Health <= 0, it should call BeginDeath, not repeat the death work.
What the flow gives other systems¶
Death flow does not usually return a value like an API node. It gives the rest of the project stable state and one event to listen to:
bIsDead = true
DeathState = Dying / Dead / Respawning / Defeated
OnDeathStarted(DeathContext)
OnDeathFinished or OnRespawnReady if your project needs it
That is the value of the pattern: widgets, AI, weapons, objectives, and respawn logic can ask or listen for the same state instead of each one re-checking health and running its own death branch.
Disable gameplay before presentation¶
The order matters because visuals can take time. A death montage might last two seconds. During those two seconds, normal gameplay should not keep running as if the actor is alive.
Common cleanup:
| System | Death cleanup |
|---|---|
| Player input | Disable movement/fire/use input, or switch to a death/menu input mode. |
| Character Movement | Stop movement, clear pending movement input, or change movement mode for ragdoll/custom flow. |
| AI | Stop Behavior Tree/brain logic, clear target, stop movement, or tell AIController the pawn is dead. |
| Collision | Disable attack hitboxes and interaction triggers; decide whether capsule/body blocks as a corpse. |
| Damage intake | Reject future damage with bIsDead or CanReceiveDamage rules. |
| Timers | Clear poison, burn, attack cooldowns, scan loops, delayed ability callbacks, and respawn timers owned by the dying actor. |
| Inventory/use | Block item use, pickup, equip, reload, interact, and fire requests while dead. |
| UI/camera | Show defeat UI, death cam, spectator view, or respawn prompt from the player controller/HUD path. |
You do not need every row for every actor. A destructible barrel has no input or AI. An enemy might need AI and attack timers. A player needs input, camera, UI, and respawn.
Collision choices¶
Death collision is a design decision, not an automatic side effect.
Common choices:
| Death result | Collision rule |
|---|---|
| Corpse blocks the world | Keep a body collider or ragdoll collision, but disable attack/interaction hitboxes. |
| Corpse is cosmetic | Disable gameplay collision and let the mesh play animation/ragdoll only if desired. |
| Player respawns elsewhere | Disable old pawn collision before respawn or destruction so it does not keep blocking. |
| Pickup is temporarily defeated/used | Hide visuals and set pickup collision to NoCollision until respawn. |
| Boss weak point is disabled | Disable only that weak point's collision, not the whole boss actor. |
Hiding the actor does not disable collision. Destroying the actor removes it, but that may be too early if a montage, camera shot, loot drop, or corpse timer still matters.
AI death¶
For an AI-controlled enemy, the pawn body and AIController are separate objects. Death usually touches both:
Enemy BeginDeath
-> set bIsDead true
-> stop weapon / attack hitbox timers
-> Capsule Set Collision Enabled NoCollision or corpse-specific mode
-> CharacterMovement Stop Movement Immediately
-> AIController Stop Movement
-> BrainComponent Stop Logic or Behavior Tree death state
-> clear Blackboard Target / state as needed
-> play death montage or ragdoll
-> Set Life Span 5.0
The AIController may keep existing after the pawn dies. Do not let it continue issuing Move To or attack requests into a dead pawn.
Player death and respawn¶
For a player, the PlayerController is the stable player identity. The pawn is the body that can die and be replaced.
PlayerCharacter BeginDeath
-> disable character input/actions
-> disable movement and attack collision
-> broadcast OnPlayerDied with controller / checkpoint context
-> PlayerController switches to death camera or defeat UI
-> GameMode starts respawn delay
-> GameMode restarts controller at checkpoint
-> PlayerController/HUD rebinds to new pawn HealthComponent
Do not put the only respawn logic in the dying pawn if that pawn will be destroyed. The dying pawn can announce death. GameMode and PlayerController should own restart and player-facing presentation.
Destroy, lifespan, or defeated state¶
The actor does not always have to disappear immediately.
| Outcome | Use when |
|---|---|
Destroy Actor |
The actor should leave now, and no delayed presentation or corpse is needed. |
Set Life Span |
The actor should remain for a montage, corpse, dropped loot setup, or impact cleanup, then remove itself. |
| Hidden/disabled state | The same actor should return later, such as a pickup, training target, or wave spawner. |
| Respawn through GameMode | A player controller needs a new pawn at a PlayerStart/checkpoint. |
| Defeated actor stays alive | A boss, door, puzzle object, or objective manager remains but changes state. |
Delay -> Destroy Actor is the quick path. Its concrete cost is that the delay
lives in one event graph branch with no actor-level lifetime meaning. Set Life
Span is easier to inspect, replace, clear, and reason about because the actor
itself owns the pending cleanup.
Score, credit, and objectives¶
Score and objective progress should change once. Use the same death gate.
BeginDeath(DeathContext)
-> if bScoreAwarded is false:
bScoreAwarded = true
GameMode / objective owner awards credit
Where credit lives depends on the project:
- PlayerState for public per-player score that survives pawn death;
- GameState for public match score or objective progress;
- GameMode for server-only win/loss decisions;
- a placed manager actor for a room, wave, door, or puzzle-specific objective.
Do not store the only score in the pawn if destroying the pawn destroys the score with it.
Multiplayer death¶
In a networked game, the server should run the authoritative death transition:
Server applies damage
-> HealthComponent reaches 0
-> server BeginDeath
-> server updates replicated bIsDead / Health / score state
-> server destroys replicated actor or starts server respawn
-> clients run local presentation from replicated state or multicast cosmetic events
Common rules:
- clients should not decide that an enemy is dead because their local trace looked good;
- replicated health/death state is usually better than trying to replicate a widget or local-only effect;
- a multicast can play a shared death effect, but the durable fact should still be replicated state or server actor removal;
- client-only destruction removes only that client's copy and can be corrected by the server later;
- late-joining clients need replicated current state, not just a past death event they missed.
For a single-player game, following the same ownership split still keeps the flow clean.
Stale references after death¶
Death creates stale-reference bugs because other systems remember the old actor:
- HUD bound to old HealthComponent;
- AI target still points at the dead pawn;
- overlap set still contains the actor;
- inventory/interact prompt still points at a destroyed pickup;
- camera view target stays on a corpse after respawn;
- timer callback fires after the actor should be done.
Make cleanup explicit:
OnDeathStarted
-> broadcast to interested systems
-> clear "current target" / overlap / interact references where owned
-> unbind long-lived listeners if this actor/component bound to them
Blueprint object references usually become invalid after destruction, but invalid is not the same as "your game state has been corrected." Clear or rebind the variables that represent current gameplay choices.
Failure modes¶
| Symptom | Likely cause |
|---|---|
| Enemy awards score twice | Damage, overlap, or timer paths each ran death logic without one gate. |
| Dead player can still shoot | Input/action path never checks death state or was not disabled. |
| Dead enemy keeps chasing | AIController/Behavior Tree kept running after the pawn's death. |
| Invisible corpse blocks the player | Visibility changed, but collision did not. |
| Death montage plays but actor vanishes immediately | Destroy Actor ran before presentation had time to finish. |
| Respawned HUD shows old health | Widget stayed bound to old pawn/component. |
| Poison keeps damaging a corpse | Looping timer or status effect did not clear on death/EndPlay. |
| Client sees death but server does not | Client ran local health/death instead of sending a server-owned damage request. |
| Pickup or objective still targets dead actor | Current reference arrays/sets were not cleared or rebuilt. |
Print the actor name, authority, old health, new health, bIsDead, and the
death function name while debugging. If the print appears twice, fix the gate
before adding more cleanup.
The pattern everyone actually uses¶
Enemy:
HealthComponent OnDeath
-> BP_Enemy BeginDeath
BeginDeath
-> Branch bIsDead false
-> set bIsDead true
-> disable attack hitbox collision
-> clear attack/status timers
-> stop AI movement/logic
-> disable capsule collision or choose corpse collision
-> play death montage / sound / VFX
-> notify score/objective owner once
-> Set Life Span 5.0
Player:
HealthComponent OnDeath
-> Character BeginDeath
-> disable gameplay input and movement
-> PlayerController shows death UI / camera
-> GameMode starts respawn timer
-> Restart Player at Player Start
-> HUD rebinds to new HealthComponent
Destructible barrel:
HealthComponent OnDeath
-> disable collision
-> spawn explosion VFX/sound
-> Apply Radial Damage
-> Destroy Actor
Lookalikes - which one do I want?¶
| Tool / state | Use when | It does not |
|---|---|---|
bIsDead / DeathState |
You need one gate for alive, dying, dead, respawning, or defeated. | Clean up systems by itself. |
HealthComponent OnDeath |
Reusable health detects the first transition to zero. | Know how every owner should die. |
| Destroy Actor | The actor should leave the world now. | Play death flow, clear external references, or wait for a montage. |
| Set Life Span | The actor should clean itself up after a delay. | Disable gameplay systems while waiting. |
| Set Collision Enabled | The dead/defeated object should stop blocking, overlapping, or tracing. | Hide or destroy the actor. |
| Set View Target with Blend | The player should see a death camera or spectator shot. | Change possession or respawn the pawn. |
| Restart Player at Player Start | GameMode should give a controller a new pawn. | Clean up the old pawn's death state for you. |
| Open Level | The whole map should reset. | Preserve per-player state unless you saved it elsewhere. |
Rule of thumb: death starts once, disables gameplay first, plays presentation second, then either respawns, persists, or destroys.
Going deeper¶
- HealthComponent and damage response pattern
- the component-side gate that should usually start death.
- Destroy Actor / Set Life Span
- actor destruction timing, lifespan cleanup, and stale references.
- Set Collision Enabled / Response / Profile Name
- making dead or hidden actors stop colliding when appropriate.
- Player Start, Restart Player, and checkpoints
- replacing a player pawn after death.
- Set View Target with Blend
- death cameras without changing possession.
- Set Timer by Event / Clear Timer by Handle
- delayed respawn, corpse cleanup, and clearing looping effects.
- Status effects and damage-over-time with timers
- clearing poison, burn, slow, stun, and buff timers when death starts.
- GameState and PlayerState in practice
- score and state that should survive pawn replacement.
- Replication basics - server-owned death and replicated removal.
- Official docs: Destroy Actor, Set Life Span, Set Collision Enabled, Player Start Actor, and AActor::TakeDamage.
- Engine source (requires engine access - we do not reproduce it here):
AActor::TakeDamage,AActor::ReceiveAnyDamage,AActor::Destroy,AActor::EndPlay,AActor::SetLifeSpan, andAActor::LifeSpanExpiredinEngine/Source/Runtime/Engine/Private/Actor.cpp;AGameModeBase::RestartPlayer,AGameModeBase::RestartPlayerAtPlayerStart, andAGameModeBase::SpawnDefaultPawnAtTransforminEngine/Source/Runtime/Engine/Private/GameModeBase.cpp;UPrimitiveComponent::SetCollisionEnabledinEngine/Source/Runtime/Engine/Private/Components/PrimitiveComponent.cpp.