08 — Health System (BPC_HealthSystem)
⚡ C++ Status: Stub — Source/PG_Framework/Public/Player/BPC_HealthSystem.h provides the UCLASS shell, MaxHealth/CurrentHealth variables, OnHealthChanged and OnDeath event dispatchers. The C++ stub has NO gameplay logic — it exists so other C++ classes (BPC_StateManager, BPC_DamageReceptionSystem) can forward-reference it. Create a BP child and build ALL logic from this spec: TakeDamage(), Heal(), OnDeath() transition, damage resistance modifiers, health regen tick, I_Damageable interface. See docs/developer/cpp-integration-guide.md for setup steps.
Purpose
Manages the player's health pool, damage application with type-based resistance, health regeneration, death state, and critical-health events. Serves as the central survivability component on the player character.
Dependencies
- Requires:
FL_GameUtilities (for safe interface casts), I_Damageable (interface on the owner), GI_GameFramework (for GamePhase queries)
- Required By:
BPC_StressSystem (health below threshold triggers fear events), BPC_PlayerMetricsTracker (death tracking), WBP_HUD (health bar), AI_EnemyController (player health as awareness trigger), BPC_InteractionDetector (low-health checks)
- Engine/Plugin Requirements: GameplayTags, DeveloperSettings
Class Info
| Property |
Value |
| Parent Class |
ActorComponent |
| Class Type |
Blueprint Component |
| Asset Path |
Content/Framework/Player/BPC_HealthSystem |
| Implements Interfaces |
I_Damageable |
1. Enums
E_DamageType
| Value |
Description |
Physical = 0 |
Bullets, blunt force, falls |
Arcane = 1 |
Magic, curse, psychic damage |
Fire = 2 |
Burning, environmental heat |
Poison = 3 |
Toxins, venom, chemical |
Fear = 4 |
Psychological damage (stress overflow) |
Environmental = 5 |
Traps, crushing, drowning |
True = 6 |
Bypasses all resistances, no reduction |
E_DeathState
| Value |
Description |
Alive = 0 |
Normal gameplay |
Dying = 1 |
Downed state, waiting for recovery or final death |
Dead = 2 |
Character is dead, no further actions possible |
PermaDeath = 3 |
Save-scrubbing locked, forced reload |
2. Structs
S_DamageEvent
| Field |
Type |
Description |
BaseAmount |
Float |
Raw damage before resistance |
DamageType |
E_DamageType |
Category of damage |
Instigator |
Actor |
Who caused the damage |
HitLocation |
Vector |
World location of impact |
DamageTags |
GameplayTagContainer |
Additional contextual tags |
bIsCriticalHit |
Boolean |
True if this is a headshot / weak-point hit |
SourceDescription |
Text |
Human-readable source (for log / UI) |
S_DamageResistance
| Field |
Type |
Description |
DamageType |
E_DamageType |
Type this resistance applies to |
Multiplier |
Float |
1.0 = normal, 0.5 = half damage, 2.0 = double |
bIsStackable |
Boolean |
Can this resistance stack with others? |
3. Variables
Configuration (Instance Editable, Expose On Spawn)
| Variable |
Type |
Default |
Category |
Description |
MaxHealth |
Float |
100.0 |
Health Config |
Maximum hit points |
bRegenEnabled |
Boolean |
true |
Health Config |
Allow passive health regeneration |
RegenDelay |
Float |
3.0 |
Health Config |
Seconds after last damage before regen starts |
RegenRate |
Float |
2.0 |
Health Config |
HP recovered per second during regen |
RegenDelayUntil |
Float |
0.0 |
Health Config |
World time when regen can resume |
CriticalHealthPercent |
Float |
0.25 |
Health Config |
Below this fraction of MaxHealth, fire critical event |
DefaultResistances |
Array<S_DamageResistance> |
[] |
Health Config |
Base damage resistances for this character |
bEnableFriendlyFire |
Boolean |
false |
Health Config |
Can the player damage themselves? |
Internal (Private / Protected, No Expose)
| Variable |
Type |
Default |
Category |
Description |
CurrentHealth |
Float |
100.0 |
Health State |
Current HP, clamped [0..MaxHealth] |
DeathState |
E_DeathState |
Alive |
Health State |
Current death state |
ActiveDamageOverTime |
Map<FName, FTimerHandle> |
{} |
Health State |
Active DoT timers keyed by source name |
InvincibilityTimer |
FTimerHandle |
- |
Health State |
Timer handle for temporary invincibility |
RegenTimerHandle |
FTimerHandle |
- |
Health State |
Timer handle for regen tick |
bIsInvincible |
Boolean |
false |
Health State |
True during invincibility window |
LastDamageInstigator |
Actor |
None |
Health State |
Most recent damage source (for kill credit) |
LastDamageTime |
Float |
0.0 |
Health State |
World time of last damage taken |
Replicated (if multiplayer)
| Variable |
Type |
Condition |
Description |
CurrentHealth |
Float |
RepNotify |
Replicated with OnRep handler for UI updates |
DeathState |
E_DeathState |
RepNotify |
Replicated death state |
4. Functions
Public Functions
ApplyDamage → S_DamageResult (custom struct)
- Description: Applies damage to the health pool after resistance calculation. Returns effective damage and any side effects.
- Parameters:
| Param |
Type |
Description |
DamageEvent |
S_DamageEvent |
The damage event to process |
- Blueprint Authority: Server (if MP), Any (single-player)
- Flow:
- Early-out if DeathState is Dead or bIsInvincible
- Calculate effective damage from BaseAmount * type resistances
- If damage > 0: reset RegenDelay timer, update CurrentHealth
- Fire OnDamageTaken dispatcher
- If CurrentHealth <= 0: call
HandleDeath
- If CurrentHealth <= CriticalHealthPercent: fire OnHealthCritical
- Return damage result struct
ApplyHealing → Float (effective healing)
- Description: Adds health up to MaxHealth. Returns the amount actually healed.
- Parameters:
| Param |
Type |
Description |
HealAmount |
Float |
Raw healing value |
HealTags |
GameplayTagContainer |
Contextual tags |
- Blueprint Authority: Server (if MP), Any (single-player)
- Flow:
- Early-out if DeathState is Dead
- Clamp:
CurrentHealth = FMath::Min(MaxHealth, CurrentHealth + HealAmount)
- Fire OnHealthChanged
- Return actual heal amount
GetHealthNormalised → Float [0.0 - 1.0]
- Description: Returns NormalisedCurrentHealth for UI widgets.
- Flow:
Return CurrentHealth / MaxHealth
SetMaxHealth → void
- Description: Changes MaxHealth and optionally heals to match new ratio or clamps current.
- Parameters:
| Param |
Type |
Description |
NewMax |
Float |
New maximum health value |
bMaintainRatio |
Boolean |
If true, scale CurrentHealth to same ratio |
- Flow:
- Clamp NewMax to >= 1.0
- If bMaintainRatio:
CurrentHealth = (CurrentHealth / MaxHealth) * NewMax
- Else:
CurrentHealth = FMath::Min(CurrentHealth, NewMax)
- Set MaxHealth = NewMax
- Fire OnHealthChanged
KillInstant → void
- Description: Bypasses all resistances, immediately sets health to 0 and triggers death.
- Parameters:
| Param |
Type |
Description |
Instigator |
Actor |
Who caused the kill |
DeathReason |
FText |
Reason displayed on death screen |
- Blueprint Authority: Server (if MP), Any (single-player)
- Flow:
- Set CurrentHealth = 0
- Set DeathState = Dying
- Fire OnHealthChanged with CurrentHealth = 0
- Call HandleDeath with bInstant = true
ApplyDamageOverTime → void
- Description: Applies periodic damage with tick interval and total duration.
- Parameters:
| Param |
Type |
Description |
DamagePerTick |
Float |
Damage applied each interval |
TickInterval |
Float |
Seconds between ticks |
TotalDuration |
Float |
Total seconds the DoT lasts |
DamageType |
E_DamageType |
Type of each damage tick |
SourceName |
FName |
Unique name to prevent stacking same DoT |
Instigator |
Actor |
Who applied the DoT |
- Flow:
- If SourceName already exists in ActiveDamageOverTime, clear existing timer
- Create a Timer by Event with loop = TickInterval
- On each tick: build S_DamageEvent, call ApplyDamage
- After TotalDuration: clear timer, remove from ActiveDamageOverTime
IsAlive → Boolean
- Flow:
Return DeathState == Alive
AddResistance → void
- Parameters:
| Param |
Type |
Description |
NewResistance |
S_DamageResistance |
Resistance to add or update |
- Flow: If resistance for this DamageType exists, update Multiplier; if not, add to array.
RemoveResistance → void
- Parameters:
| Param |
Type |
Description |
DamageType |
E_DamageType |
Type to remove |
- Flow: Remove all entries matching this DamageType from resistance array.
Protected / Private Functions
HandleDeath → void
- Description: Internal death processing. Sets state, fires dispatchers, notifies GameMode.
- Parameters:
| Param |
Type |
Description |
bInstant |
Boolean |
If true, skip dying state go straight to dead |
- Flow:
- Set DeathState = (bInstant ? Dead : Dying)
- Clear all timers (regen, DoT, invincibility)
- Fire OnDeath with DeathState
- Call
I_Damageable::OnOwnerDied on owner if interface exists
- Get
GM_CoreGameMode via GetWorld()->GetAuthGameMode()
- Call
GM_CoreGameMode::HandlePlayerDead(LastDamageInstigator)
RegenTick → void
- Description: Timer callback — applies one tick of regeneration.
- Flow:
- If CurrentHealth >= MaxHealth: stop regen timer, return
CurrentHealth = FMath::Min(MaxHealth, CurrentHealth + RegenRate * TickInterval)
- Fire OnHealthChanged
- If CurrentHealth >= MaxHealth: stop regen timer
5. Event Dispatchers
| Dispatcher |
Parameters |
Bind Access |
Description |
OnHealthChanged |
float OldHealth, float NewHealth, float Delta |
Public |
Fired when health changes by any amount |
OnDamageTaken |
S_DamageEvent DamageEvent, float EffectiveDamage |
Public |
Fired each time damage is applied |
OnHealingReceived |
float HealAmount, GameplayTagContainer HealTags |
Public |
Fired each time healing is applied |
OnHealthCritical |
float CurrentHealth, float MaxHealth |
Public |
Fired when health drops below critical threshold |
OnDeath |
E_DeathState DeathState, Actor Instigator |
Public |
Fired when character dies or enters dying state |
OnDeathStateChanged |
E_DeathState OldState, E_DeathState NewState |
Public |
Fired on any death state transition |
OnInvincibilityChanged |
bool bIsInvincible |
Public |
Fired when invincibility state toggles |
6. Overridden Events / Custom Events
Event: BeginPlay
- Description: Initializes health, loads saved health if persisting, starts regen timer if enabled.
- Flow:
- Set CurrentHealth = MaxHealth
- Check if owner implements
I_Persistable
- If yes, try to load saved health value
- If bRegenEnabled: start regen timer with initial delay = 0
- Bind to any relevant GamePhase change dispatchers
Event: OnComponentDestroyed
- Description: Clean up all active timers.
- Flow:
- Clear all TimerHandles (Invincibility, Regen, all DoT timers)
- Clear ActiveDamageOverTime map
Custom Event: ApplyTemporaryInvincibility
- Parameters:
float Duration
- Description: Grants brief invincibility, often used after respawn or dodge.
- Flow:
- Set bIsInvincible = true
- Fire OnInvincibilityChanged(true)
- Start timer for Duration
- On timer end: set bIsInvincible = false, fire OnInvincibilityChanged(false)
7. Blueprint Graph Logic Flow
8. Communication Matrix
| Who Talks |
How |
What Is Sent |
BPC_HealthSystem |
Dispatcher |
OnHealthChanged -> WBP_HUD, BPC_StressSystem |
BPC_HealthSystem |
Dispatcher |
OnDeath -> GM_CoreGameMode, SS_SaveSystem, BPC_PlayerMetricsTracker |
BPC_HealthSystem |
Dispatcher |
OnDamageTaken -> BPC_StressSystem, BPC_AdaptiveEnvironment |
BPC_HealthSystem |
Interface |
I_Damageable.Execute_ApplyDamage route |
BPC_HealthSystem |
Interface |
I_Persistable for save/load of CurrentHealth |
External (Weapons, Traps) |
Interface |
I_Damageable -> ApplyDamage |
BPC_HealthSystem |
Direct |
GM_CoreGameMode.HandlePlayerDead on death |
BPC_HealthSystem |
Subsystem |
GI_GameFramework.GetGamePhase for phase-restricted actions |
9. Validation / Testing Checklist
10. Reuse Notes
- This component can be used on AI characters and enemy pawns by exposing the same configuration variables.
- For enemies, consider disabling regen (bRegenEnabled = false) unless a specific healing mechanic exists.
- The resistance system supports temporary buffs via AddResistance / RemoveResistance at runtime.
- If implementing a downed / bleeding state, extend E_DeathState with custom values and add a recovery timer check in HandleDeath.
- Damage numbers displayed as floating text should bind to OnDamageTaken.
- For multiplayer: all ApplyDamage logic runs on Server; clients receive repnotified CurrentHealth and fire local cosmetic dispatchers only.
11. Multiplayer Networking (Expanded)
Replicated Variables (Existing + New)
| Variable |
Type |
Condition |
Description |
CurrentHealth |
Float |
Replicated Using OnRep_CurrentHealth |
Clients sync via OnRep → dispatcher |
DeathState |
E_DeathState |
Replicated Using OnRep_DeathState |
Death state synced; OnRep triggers death effects |
bIsInvincible |
Boolean |
Replicated |
Invincibility visible to other players |
MaxHealth |
Float |
Replicated |
Synced for UI percentage calculations |
Server RPCs
| RPC |
Direction |
Description |
Server_ApplyDamage |
Client→Server |
Client reports taking damage. Server validates source, range, damage type. |
Server_ApplyHealing |
Client→Server |
Client requests healing. Server validates heal source, applies. |
Server_KillInstant |
Client→Server |
Only accepted from authoritative sources (GM, traps). Client calls are rejected. |
Authority Gates
Client Prediction
- Health bar: Client predicts damage flash on hit; server-corrected value arrives via
OnRep_CurrentHealth.
- Death: No prediction.
OnRep_DeathState triggers all death effects (screen fade, ragdoll).
- Healing: Client shows green flash immediately + predicted health; server corrects within one RTT.
- Invincibility: Visual effect (shield shimmer) is local cosmetic; state is replicated.
OnRep Handlers
Anti-Cheat
- Server validates all
Server_ApplyDamage calls: check instigator range, weapon fire rate, damage type validity.
- Server never trusts
DamageEvent.BaseAmount from client — always recalculates from weapon data.
Server_KillInstant is only callable from server-authoritative systems (traps, death zones, GM).
Blueprint Spec: Health System. Conforms to TEMPLATE.md v1.0 — part of the UE5 Modular Game Framework.