Gameplay · Updated May 29, 2026
Respawn System
Respawn System
Halo-style player respawn built on top of the existing death ability (BP_GA_SL_Death) and UE's RestartPlayer flow. Death triggers a server-side timeout that starts the respawn countdown independently of any animation notify, shows a countdown HUD with the killer's name, and re-initializes the character's ASC on the new pawn.
#Flow Overview
USLHealthAttributeSet::PostGameplayEffectExecute — CurrentHealth hits 0 (authority)
→ Character->PendingKiller = EffectCauser
→ ASC->HandleGameplayEvent(Events.Character.Death) — activates GA_Death
→ Character->StartServerDeathTimeout() — server timer starts immediately
GA_Death activates (authority + locally predicted on owner)
→ Client_OnDeathStarted() — calls OnClientDeathStarted() BP event (no DisableInput)
→ Death animation plays (AnimBP reads bIsDead from AnimStateSnapshot)
→ USLAnimNotify_DeathFinished at end of death anim state
→ Character->EnableRagdoll() — physics on mesh, capsule collision off (cosmetic only)
Server death timeout fires (after DeathAnimationTimeout seconds)
→ ASLGameModeBase::RequestRespawn(PC, PendingKiller)
→ Client_OnRespawnBegin(RespawnDelay, KillerName) — start countdown HUD
→ PC->UnPossess()
→ [timer: RespawnDelay seconds]
→ RestartPlayer(PC)
→ PossessedBy() on new pawn (server)
→ Remove Dead + shield + InventoryLoaded tags / GEs
→ TakeFromASC + re-grant CombatantAbilitySet + LoadoutAbilitySet
→ Apply GE_Respawn (reset health + shield)
→ IsLocallyControlled: InitializeLocalPlayerHUD() + OnPawnInitialized(true)
→ else: Client_PawnInitialized(true) RPC
→ LoadDefaultLoadout() — spawns weapons, adds to CarriedWeapons
→ IsLocallyControlled: AddLooseGameplayTag(InventoryLoaded) directly
→ else: Client_OnLoadoutReady() RPC → client adds InventoryLoaded to ASC
→ Client_PawnInitialized arrives on client
→ Remove stale InventoryLoaded from ASC (persists from previous pawn via PlayerState)
→ InitializeLocalPlayerHUD() — restores HUD visibility, re-inits health/damage widgets
→ OnPawnInitialized(bIsRespawn=true) BP event
→ GA_SL_EquipDefaultLoadout activates
→ Client_OnLoadoutReady arrives on client
→ AddLooseGameplayTag(InventoryLoaded) on ASC
→ GA_SL_EquipDefaultLoadout
→ Wait Gameplay Tag Query: InventoryLoaded AND PlayerReady
→ Bind HUD delegates to new WeaponsComponent
→ Server_RequestEquipFirst() → server equips CarriedWeapons[0]
→ Remove respawn overlay (via PlayerController)
→ End Ability
Key point: Respawn is driven entirely by the server-side timeout, not by the AnimNotify. The AnimNotify only activates ragdoll physics. If the notify is missing from the Anim BP, the ragdoll won't play — but the player will still respawn at the correct time.
#Game Mode — ASLGameModeBase
#Properties
| Property | Type | Default | Notes |
|---|---|---|---|
bRespawnDelay | bool | true | If false, RestartPlayer fires instantly with no countdown. |
RespawnDelay | float | 5.0 | Seconds before the player respawns. Only used when bRespawnDelay is true. |
DeathAnimationTimeout | float | 8.0 | How long the server waits after death before starting the respawn countdown. Set to match the death animation length. If 0, respawn starts immediately. |
RagdollDestroyDelay | float | 0.0 | Seconds after respawn before the dead pawn is destroyed. 0 = destroy immediately. |
#Functions
**RequestRespawn(APlayerController PC, AActor Killer)**
- Authority only.
- Extracts killer name from
Killer->GetPlayerState()->GetPlayerName(). Falls back to"Unknown".
- If
bRespawnDelayis false: callsPC->UnPossess()thenRestartPlayerimmediately, no countdown sent.
- If
bRespawnDelayis true: sendsClient_OnRespawnBegin(RespawnDelay, KillerName), callsPC->UnPossess(), then starts a per-controllerFTimerHandlethat callsRestartPlayerafterRespawnDelayseconds.
PC->UnPossess()is required beforeRestartPlayer— if the PC still possesses a pawn,AGameModeBase::RestartPlayerreuses/teleports the existing pawn instead of spawning a new one.
- Stores active timers in
TMap<TObjectPtr<APlayerController>, FTimerHandle> RespawnTimerHandles.
Logoutoverride clears the timer for any disconnecting player.
#Character — ASLCharacterBase
#Death Timeout
StartServerDeathTimeout() (public, authority only)
- Called from
USLHealthAttributeSet::PostGameplayEffectExecuteimmediately when health hits 0.
- Reads
DeathAnimationTimeoutfromASLGameModeBase(falls back to 8s if GameMode is not anASLGameModeBase).
- If timeout is 0: calls
RequestRespawndirectly (SetTimer with rate 0 clears the timer rather than firing).
- Otherwise: sets
ServerDeathTimeoutHandleto fireRequestRespawnafterDeathAnimationTimeoutseconds.
- Debug prints gated behind
bDebug = trueon the character defaults.
#RPCs
Client_OnDeathStarted() (Client, Reliable)
- Calls
OnClientDeathStarted()BlueprintImplementableEvent for BP-side effects (hide HUD, camera effects).
- Does not call
DisableInput— input is gated byIsCharacterDead()checks in eachASLPlayerControllerhandler instead (see Controller section below).
- Overridden in
ASLPlayerCharacterto also attach the camera boom topelvis, extend the arm, enable camera lag, and disable pawn control rotation.
Client_OnRespawnBegin(float Delay, const FString& KillerName) (Client, Reliable)
- Fires
OnRespawnBegin(Delay, KillerName)BlueprintImplementableEvent.
- BP drives the countdown widget and killer message.
#BlueprintImplementableEvents
OnClientDeathStarted() — fires on the owning client immediately when death begins. Implement in BP to: hide the HUD (Set Visibility → Collapsed), call WeaponsComponent → UnbindAllDelegates, play camera shake, show respawn overlay via ShowRespawnOverlay on the PlayerController.
OnRespawnBegin(float Delay, FText KillerName) — implement in BP to show the respawn countdown overlay with the killer name.
OnPawnInitialized(bool bIsRespawn) — fires on the owning client when the new pawn is fully initialized (controller + ASC both valid). Fires on first spawn (bIsRespawn = false) and on every respawn (bIsRespawn = true). Use this instead of BeginPlay for any initialization that requires a valid controller or ASC. In BP, use this to: update HUD character/weapons references, rebind WeaponsComponent delegates, remove the respawn overlay (via PlayerController reference), cast and cache PlayerController / PlayerState references.
#BlueprintPure
IsDead() — returns true if ragdoll is active OR if the ASC has the States.Character.Dead tag. Safe to call on all machines. Ragdoll latch prevents the shared ASC tag removal (on new pawn possession) from flipping dead characters back to alive mid-ragdoll.
#Properties
RespawnEffectClass (EditDefaultsOnly) — set to GE_Respawn in BP defaults.
bDebug (EditDefaultsOnly, default false) — when true, prints on-screen messages showing when the server death timeout starts, fires, and if it fails.
LastHitDirectionReplicated (Replicated, ESLHitDirection) — set by USLHealthAttributeSet on authority whenever damage lands. All machines read this as a fallback in BuildAnimSnapshots when PendingHitDirection is None (e.g. simulated proxies who don't receive Client_OnTookDamage).
#Debug
Server_DebugForceRespawn() (BlueprintCallable, Server, Reliable — stripped in Shipping)
- Immediately calls
UnPossess+RestartPlayer+Destroyon the caller's pawn.
- Use from a keyboard input in BP to test the respawn flow without dying.
#Death Camera — ASLPlayerCharacter
When Client_OnDeathStarted fires on the locally controlled player:
- Camera boom re-attached to the
pelvisbone (follows ragdoll, not the fallen capsule).
TargetArmLengthextended toDeathCameraArmLength(default 150).
bEnableCameraLagenabled withCameraLagSpeed = DeathCameraLagSpeed(default 6) to smooth ragdoll jitter.
bUsePawnControlRotationdisabled.
TickDeathCamera() runs every frame when ragdoll is active + locally controlled. It rotates the camera to always face the spine_03 bone so the camera orbits the body naturally.
| Property | Default | Notes |
|---|---|---|
DeathCameraArmLength | 150 | Arm length during death camera. |
DeathCameraLagSpeed | 6 | Camera lag speed to smooth ragdoll physics jitter. |
#Respawn Initialization — PossessedBy / Client_PawnInitialized
On the server in PossessedBy, after InitAbilityActorInfo:
- Detect respawn: check if ASC already has
States.Character.Deadtag.
- If respawning:
RemoveActiveEffectsWithGrantedTags(Dead, ShieldRechargingDelay, ShieldRecharging, Overshield)— removes all persistent GEs from the shared ASC. Also removesInventoryLoadedloose tag if present.
TakeFromASConPlayerState.CombatantGrantedHandles+PlayerState.LoadoutGrantedHandles— clears old specs before re-granting. Handles live on PlayerState so they survive pawn destruction.
- Re-grant
DefaultCombatantAbilitySetand loadout set into PlayerState handles.
- If respawning: apply
RespawnEffectClass(Instant, overrides CurrentHealth and CurrentShield to their Max values).
- Listen server host (locally controlled authority): calls
InitializeLocalPlayerHUD(ASC)then firesOnPawnInitialized(bIsRespawn)directly.
- Dedicated server / remote client: sends
Client_PawnInitialized(bIsRespawn)RPC.
LoadDefaultLoadout()— spawns new weapon actors fromASLGameModeBase::DefaultLoadout, adds them toCarriedWeaponson the new pawn.
- Listen server host:
AddLooseGameplayTag(InventoryLoaded)directly on the local ASC.
- Remote clients: sends
Client_OnLoadoutReady()RPC.
Client_PawnInitialized(bool bIsRespawn) (Client, Reliable) on ASLPlayerCharacter:
- Removes stale
InventoryLoadedtag from the ASC before callingOnPawnInitialized. The ASC lives on PlayerState and persists across respawns — the tag from the previous pawn is still present and would causeGA_SL_EquipDefaultLoadoutto fire immediately against an emptyCarriedWeapons.
- If
ASCis ready: callsInitializeLocalPlayerHUD(ASC)+OnPawnInitialized(bIsRespawn).
- If
PlayerStatehas not yet replicated (ASCis null): setsbPendingPawnInit = trueand waits forOnRep_PlayerStateto fire the same path.
Client_OnLoadoutReady() (Client, Reliable) on ASLPlayerCharacter:
- Adds
InventoryLoadedloose tag to the client's ASC.
- Sent by the server after
LoadDefaultLoadout()completes. Using an explicit RPC instead ofOnRep_CarriedWeaponsbecauseOnRepfires before the new pawn's PlayerState has replicated, makingGetAbilitySystemComponent()return null and silently dropping the tag.
- If the ASC is null when this RPC arrives (new pawn's PlayerState not yet replicated), it sets
bPendingInventoryLoadedandOnRep_PlayerStateapplies the tag once the ASC is valid. Earlier this case silently dropped the tag, which — combined with the bug below — caused intermittent stuck respawns (overlay frozen at "RESPAWN IN 1") becauseGA_SL_EquipDefaultLoadoutwaits onInventoryLoadedforever and never callsHideRespawnOverlay. Worse with 3+ players (more replication contention).
OnRep_PlayerStatemust NOT removeInventoryLoadedbased onCarriedWeapons.Num(). An earlier version removed the tag wheneverCarriedWeaponshadn't replicated yet, which raced against the authoritativeClient_OnLoadoutReadyRPC and could permanently strip a validly-set tag → stuck respawn. It now only ever adds the tag (driven bybPendingInventoryLoaded, withCarriedWeapons.Num() > 0as a secondary fallback). The stale-tag clear still happens inClient_PawnInitialized's immediate path.
InitializeLocalPlayerHUD(ASC) (public, local only):
- Restores HUD visibility (
SetVisibility(SelfHitTestInvisible)).
- Calls
InitializeHealthWidget(ASC)onUSLHealthWidget.
- Calls
InitializeDamageOverlayWidget(ASC)onUSLDamageOverlayWidget.
- Calls
BindWeaponDelegates(WeaponsComponent)onUSLHUDWidget— bindsOnEquippedWeaponChangedandOnAmmoChangedbefore any equip fires.
- Called from all three possible arrival orderings:
PossessedBy(listen server host),Client_PawnInitialized(client, PlayerState ready),OnRep_PlayerState(client, RPC arrived first).
- Also called from
ASLPlayerHUD::BeginPlayif the pawn is already possessed — necessary because on first spawn,PossessedByfires before the HUD widget exists, soGetHUDWidget()returns null duringPossessedBy.ASLPlayerHUD::BeginPlaycatches this and re-runs initialization after the widget is constructed.
#Loadout & Default Equip — GA_SL_EquipDefaultLoadout
A Blueprint GAS ability activated from OnPawnInitialized on both first spawn and respawn.
Trigger: Activated directly in the OnPawnInitialized BP event on BP_SL_MasterChief (and any other player character BP). Not a passive ability — activated explicitly each spawn.
Flow inside the ability:
Wait Gameplay Tag Query— waits for BOTHSLTags.States.Character.InventoryLoadedANDPlayerReadyto be present simultaneously.Only Trigger Once = true.
- Call
Server_RequestEquipFirst— equips the first weapon inCarriedWeaponsserver-side. Does not requireCarriedWeaponsto be populated on the client.
- Hide the respawn overlay via the PlayerController reference.
- End Ability.
Note: HUD delegate binding (OnEquippedWeaponChanged,OnAmmoChanged) is NOT done in this ability. It is done in C++ viaUSLHUDWidget::BindWeaponDelegatescalled fromInitializeLocalPlayerHUD, which fires before any equip. The ability only needs to trigger the equip.
PlayerReady tag: A loose gameplay tag added by Blueprint when the player's intro sequence or spawn animation completes. The tag is added on the owning client only (loose tags don't replicate). It persists on the ASC for the lifetime of the play session — the ability requires both tags simultaneously so it will not fire until the intro is done even if InventoryLoaded arrives first.
Server_RequestEquipFirst() (Server, Reliable, BlueprintCallable) on ASLCharacterBase:
- Looks up
CarriedWeapons[0]server-side and callsWeaponsComponent->SetPendingEquipWeapon.
- The client does not pass a weapon reference — avoids the race condition where
CarriedWeaponsmay not have replicated to the client when the ability fires.
#Handle Persistence — Why All Handles Live on ASLPlayerState
The ASC lives on PlayerState and persists across respawns. The pawn (and all its components) is destroyed and recreated by RestartPlayer on each respawn.
Any FSLAbilitySet_GrantedHandles or FGameplayAbilitySpecHandle stored on the pawn or WeaponsComponent is destroyed with the pawn. On the new pawn, those handles are empty, so TakeFromASC/ClearAbility are no-ops — old ability specs accumulate on the shared ASC. Each respawn adds another copy.
All handles are stored on ASLPlayerState:
| Handle | Granted by | Used in |
|---|---|---|
CombatantGrantedHandles | DefaultCombatantAbilitySet in PossessedBy | PossessedBy (TakeFromASC before re-grant) |
LoadoutGrantedHandles | PlayerLoadoutAbilitySet in PossessedBy | PossessedBy (TakeFromASC before re-grant) |
WeaponFireGrantedHandles | Per fire mode in GrantWeaponAbilities | GrantWeaponAbilities (TakeFromASC before re-grant) |
EquipAbilityHandle | Per-weapon TP equip ability in SetPendingEquipWeapon | SetPendingEquipWeapon + UnequipWeapon |
FPEquipAbilityHandle | Per-weapon FP equip ability in SetPendingEquipWeapon | SetPendingEquipWeapon + UnequipWeapon |
USLWeaponsComponent uses a fallback pattern for bot characters (no PlayerState):
ASLPlayerState* SLPS = Pawn->GetPlayerState<ASLPlayerState>();
FSLAbilitySet_GrantedHandles& FireHandles = SLPS ? SLPS->WeaponFireGrantedHandles : WeaponFireGrantedHandles;
#Input During Death — ASLPlayerController
Input is never disabled on death. Instead, each input handler checks IsCharacterDead() at the top and early-returns if the character is dead. This avoids all DisableInput / EnableInput / SetInputMode state management and eliminates PIE context bleeding bugs.
IsCharacterDead() (private) — casts GetPawn() to ASLCharacterBase, returns IsDead(). Returns false if pawn is null (safe during transitions).
| Handler | Gated on dead? | Reason |
|---|---|---|
Move, Jump, StopJumping, Crouch, StopCrouch | Yes | No movement while dead |
StartPrimaryFire, StartSecondaryFire | Yes | GAS also blocks via ActivationBlockedTags, but gated for consistency |
StopPrimaryFire, StopSecondaryFire | No | Ability cancellation should still run to clean up any in-flight fire state |
Interact, DropWeapon | Yes | Can't interact or drop while dead |
Look | No | Death camera responds to look input |
#Respawn Overlay — ASLPlayerController
The respawn overlay widget is stored on the PlayerController (not the character) because it must survive the pawn swap. The character BP calls methods on the PC to show/hide it.
ShowRespawnOverlay(Delay, Message)— createsWBP_SL_RespawnOverlay, stores the reference on the PC, callsAdd to Player Screen. Called fromOnClientDeathStartedon the character.
HideRespawnOverlay()— callsRemove from Parenton the stored widget, clears the reference. Called fromOnPawnInitializedon the new character.
Do not usePush to Game Stackfor the respawn overlay. CommonUI activatable widgets capture input when pushed onto a stack, causing the cursor to appear as a side effect. UseAdd to Player Screenfor any widget that is purely visual and does not need input focus.
#AnimNotify — USLAnimNotify_DeathFinished
Placed at the end of the death animation state in the Anim BP. Only enables ragdoll physics. Has no effect on respawn timing.
Notify fires → EnableRagdoll() on the owning ASLCharacterBase
Respawn is driven by the server-side timeout started at PostGameplayEffectExecute time, completely independent of this notify.
#Blueprint Work Required
#GE_Respawn ✅
- Duration Policy: Instant
- Modifier 1:
CurrentHealth— Override — Attribute Based —MaxHealth(Base Value, coefficient 1.0, source Target)
- Modifier 2:
CurrentShield— Override — Attribute Based —MaxShield(Base Value, coefficient 1.0, source Target)
#BP_GA_SL_Death
- On ability activation: cast avatar to
ASLCharacterBase→ callClient On Death Started.
#Anim BP — Death State
- Add
USLAnimNotify_DeathFinishedat the end of the death animation state.
- This enables ragdoll. It no longer has any involvement in respawn timing.
#BP_SL_MasterChief
- Set
RespawnEffectClass = GE_Respawnin defaults.
- Implement
OnClientDeathStarted(): Set Visibility → Collapsedon HUD widget.Get WeaponsComponent → UnbindAllDelegates.Get Owning Player Controller → ShowRespawnOverlay(Delay, KillerName).
- Implement
OnPawnInitialized(bIsRespawn): - Cache
PlayerController,PlayerState,HUD Widgetreferences (safe to cast here — controller and state are guaranteed valid). - Activate
GA_SL_EquipDefaultLoadout— this ability handles HUD delegate binding, weapon equip, and respawn overlay removal.
- Implement
OnRespawnBegin(Delay, KillerName)— passDelay+KillerNameinto the respawn overlay.
#GA_SL_EquipDefaultLoadout (Blueprint GAS Ability)
- Activation: Called directly from
OnPawnInitializedon the character BP.
ActivationBlockedTags: None — do not block onDeadtag (respawn cleanup removes it server-side but it may not have replicated to the client beforeOnPawnInitializedfires).
- Nodes:
Wait Gameplay Tag Query— Query:InventoryLoaded AND PlayerReady.Only Trigger Once = true.- Bind
WeaponsComponent → OnEquippedWeaponChangedandOnAmmoChangedto HUD handlers. Server_RequestEquipFirst— equipsCarriedWeapons[0]server-side (no client-side weapon reference needed).- If
bIsRespawn:Get Owning Player Controller → HideRespawnOverlay. End Ability.
#BP_SL_GameMode
bRespawnDelay = true,RespawnDelay = 5.0,DeathAnimationTimeout = 8.0in defaults.
- Set
bRespawnDelay = falsefor instant-respawn match configs.
#Respawn Countdown Widget
WBP_SL_RespawnOverlay✅ — exists atContent/SystemLink/UI/HUD/HealthAndShield/.
- Added to screen via
Add to Player Screen(notPush to Game Stack) — no input capture, no cursor side effect.
- Reference stored on
ASLPlayerController(persists across pawn swap).
- Drive countdown from
Delayparam passed intoOnRespawnBegin.
- Removed via
HideRespawnOverlaycalled fromOnPawnInitialized(bIsRespawn=true)on the new character.
#What Is Not Yet Built
- Respawn spawn point selection strategy (currently uses UE default
PlayerStartselection).
- "Corpse lingers" mode —
RagdollDestroyDelay > 0support exists in GameMode but the dead pawn detach-before-respawn path is not implemented.
- Kill feed / scoreboard integration.
WBP_SL_RespawnOverlaywidget — Blueprint work.