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

PropertyTypeDefaultNotes
bRespawnDelaybooltrueIf false, RestartPlayer fires instantly with no countdown.
RespawnDelayfloat5.0Seconds before the player respawns. Only used when bRespawnDelay is true.
DeathAnimationTimeoutfloat8.0How long the server waits after death before starting the respawn countdown. Set to match the death animation length. If 0, respawn starts immediately.
RagdollDestroyDelayfloat0.0Seconds 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 bRespawnDelay is false: calls PC->UnPossess() then RestartPlayer immediately, no countdown sent.
  • If bRespawnDelay is true: sends Client_OnRespawnBegin(RespawnDelay, KillerName), calls PC->UnPossess(), then starts a per-controller FTimerHandle that calls RestartPlayer after RespawnDelay seconds.
  • PC->UnPossess() is required before RestartPlayer — if the PC still possesses a pawn, AGameModeBase::RestartPlayer reuses/teleports the existing pawn instead of spawning a new one.
  • Stores active timers in TMap<TObjectPtr<APlayerController>, FTimerHandle> RespawnTimerHandles.
  • Logout override clears the timer for any disconnecting player.

#Character — ASLCharacterBase

#Death Timeout

StartServerDeathTimeout() (public, authority only)

  • Called from USLHealthAttributeSet::PostGameplayEffectExecute immediately when health hits 0.
  • Reads DeathAnimationTimeout from ASLGameModeBase (falls back to 8s if GameMode is not an ASLGameModeBase).
  • If timeout is 0: calls RequestRespawn directly (SetTimer with rate 0 clears the timer rather than firing).
  • Otherwise: sets ServerDeathTimeoutHandle to fire RequestRespawn after DeathAnimationTimeout seconds.
  • Debug prints gated behind bDebug = true on 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 by IsCharacterDead() checks in each ASLPlayerController handler instead (see Controller section below).
  • Overridden in ASLPlayerCharacter to also attach the camera boom to pelvis, 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 + Destroy on 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:

  1. Camera boom re-attached to the pelvis bone (follows ragdoll, not the fallen capsule).
  1. TargetArmLength extended to DeathCameraArmLength (default 150).
  1. bEnableCameraLag enabled with CameraLagSpeed = DeathCameraLagSpeed (default 6) to smooth ragdoll jitter.
  1. bUsePawnControlRotation disabled.

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.

PropertyDefaultNotes
DeathCameraArmLength150Arm length during death camera.
DeathCameraLagSpeed6Camera lag speed to smooth ragdoll physics jitter.

#Respawn Initialization — PossessedBy / Client_PawnInitialized

On the server in PossessedBy, after InitAbilityActorInfo:

  1. Detect respawn: check if ASC already has States.Character.Dead tag.
  1. If respawning: RemoveActiveEffectsWithGrantedTags(Dead, ShieldRechargingDelay, ShieldRecharging, Overshield) — removes all persistent GEs from the shared ASC. Also removes InventoryLoaded loose tag if present.
  1. TakeFromASC on PlayerState.CombatantGrantedHandles + PlayerState.LoadoutGrantedHandles — clears old specs before re-granting. Handles live on PlayerState so they survive pawn destruction.
  1. Re-grant DefaultCombatantAbilitySet and loadout set into PlayerState handles.
  1. If respawning: apply RespawnEffectClass (Instant, overrides CurrentHealth and CurrentShield to their Max values).
  1. Listen server host (locally controlled authority): calls InitializeLocalPlayerHUD(ASC) then fires OnPawnInitialized(bIsRespawn) directly.
  1. Dedicated server / remote client: sends Client_PawnInitialized(bIsRespawn) RPC.
  1. LoadDefaultLoadout() — spawns new weapon actors from ASLGameModeBase::DefaultLoadout, adds them to CarriedWeapons on the new pawn.
  1. Listen server host: AddLooseGameplayTag(InventoryLoaded) directly on the local ASC.
  1. Remote clients: sends Client_OnLoadoutReady() RPC.

Client_PawnInitialized(bool bIsRespawn) (Client, Reliable) on ASLPlayerCharacter:

  • Removes stale InventoryLoaded tag from the ASC before calling OnPawnInitialized. The ASC lives on PlayerState and persists across respawns — the tag from the previous pawn is still present and would cause GA_SL_EquipDefaultLoadout to fire immediately against an empty CarriedWeapons.
  • If ASC is ready: calls InitializeLocalPlayerHUD(ASC) + OnPawnInitialized(bIsRespawn).
  • If PlayerState has not yet replicated (ASC is null): sets bPendingPawnInit = true and waits for OnRep_PlayerState to fire the same path.

Client_OnLoadoutReady() (Client, Reliable) on ASLPlayerCharacter:

  • Adds InventoryLoaded loose tag to the client's ASC.
  • Sent by the server after LoadDefaultLoadout() completes. Using an explicit RPC instead of OnRep_CarriedWeapons because OnRep fires before the new pawn's PlayerState has replicated, making GetAbilitySystemComponent() 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 bPendingInventoryLoaded and OnRep_PlayerState applies 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") because GA_SL_EquipDefaultLoadout waits on InventoryLoaded forever and never calls HideRespawnOverlay. Worse with 3+ players (more replication contention).
OnRep_PlayerState must NOT remove InventoryLoaded based on CarriedWeapons.Num(). An earlier version removed the tag whenever CarriedWeapons hadn't replicated yet, which raced against the authoritative Client_OnLoadoutReady RPC and could permanently strip a validly-set tag → stuck respawn. It now only ever adds the tag (driven by bPendingInventoryLoaded, with CarriedWeapons.Num() > 0 as a secondary fallback). The stale-tag clear still happens in Client_PawnInitialized's immediate path.

InitializeLocalPlayerHUD(ASC) (public, local only):

  • Restores HUD visibility (SetVisibility(SelfHitTestInvisible)).
  • Calls InitializeHealthWidget(ASC) on USLHealthWidget.
  • Calls InitializeDamageOverlayWidget(ASC) on USLDamageOverlayWidget.
  • Calls BindWeaponDelegates(WeaponsComponent) on USLHUDWidget — binds OnEquippedWeaponChanged and OnAmmoChanged before 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::BeginPlay if the pawn is already possessed — necessary because on first spawn, PossessedBy fires before the HUD widget exists, so GetHUDWidget() returns null during PossessedBy. ASLPlayerHUD::BeginPlay catches 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:

  1. Wait Gameplay Tag Query — waits for BOTH SLTags.States.Character.InventoryLoaded AND PlayerReady to be present simultaneously. Only Trigger Once = true.
  1. Call Server_RequestEquipFirst — equips the first weapon in CarriedWeapons server-side. Does not require CarriedWeapons to be populated on the client.
  1. Hide the respawn overlay via the PlayerController reference.
  1. End Ability.
Note: HUD delegate binding (OnEquippedWeaponChanged, OnAmmoChanged) is NOT done in this ability. It is done in C++ via USLHUDWidget::BindWeaponDelegates called from InitializeLocalPlayerHUD, 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 calls WeaponsComponent->SetPendingEquipWeapon.
  • The client does not pass a weapon reference — avoids the race condition where CarriedWeapons may 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:

HandleGranted byUsed in
CombatantGrantedHandlesDefaultCombatantAbilitySet in PossessedByPossessedBy (TakeFromASC before re-grant)
LoadoutGrantedHandlesPlayerLoadoutAbilitySet in PossessedByPossessedBy (TakeFromASC before re-grant)
WeaponFireGrantedHandlesPer fire mode in GrantWeaponAbilitiesGrantWeaponAbilities (TakeFromASC before re-grant)
EquipAbilityHandlePer-weapon TP equip ability in SetPendingEquipWeaponSetPendingEquipWeapon + UnequipWeapon
FPEquipAbilityHandlePer-weapon FP equip ability in SetPendingEquipWeaponSetPendingEquipWeapon + 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).

HandlerGated on dead?Reason
Move, Jump, StopJumping, Crouch, StopCrouchYesNo movement while dead
StartPrimaryFire, StartSecondaryFireYesGAS also blocks via ActivationBlockedTags, but gated for consistency
StopPrimaryFire, StopSecondaryFireNoAbility cancellation should still run to clean up any in-flight fire state
Interact, DropWeaponYesCan't interact or drop while dead
LookNoDeath 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) — creates WBP_SL_RespawnOverlay, stores the reference on the PC, calls Add to Player Screen. Called from OnClientDeathStarted on the character.
  • HideRespawnOverlay() — calls Remove from Parent on the stored widget, clears the reference. Called from OnPawnInitialized on the new character.
Do not use Push to Game Stack for the respawn overlay. CommonUI activatable widgets capture input when pushed onto a stack, causing the cursor to appear as a side effect. Use Add to Player Screen for 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 → call Client On Death Started.

#Anim BP — Death State

  • Add USLAnimNotify_DeathFinished at 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_Respawn in defaults.
  • Implement OnClientDeathStarted():
    • Set Visibility → Collapsed on HUD widget.
    • Get WeaponsComponent → UnbindAllDelegates.
    • Get Owning Player Controller → ShowRespawnOverlay(Delay, KillerName).
  • Implement OnPawnInitialized(bIsRespawn):
    • Cache PlayerController, PlayerState, HUD Widget references (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) — pass Delay + KillerName into the respawn overlay.

#GA_SL_EquipDefaultLoadout (Blueprint GAS Ability)

  • Activation: Called directly from OnPawnInitialized on the character BP.
  • ActivationBlockedTags: None — do not block on Dead tag (respawn cleanup removes it server-side but it may not have replicated to the client before OnPawnInitialized fires).
  • Nodes:
    1. Wait Gameplay Tag Query — Query: InventoryLoaded AND PlayerReady. Only Trigger Once = true.
    1. Bind WeaponsComponent → OnEquippedWeaponChanged and OnAmmoChanged to HUD handlers.
    1. Server_RequestEquipFirst — equips CarriedWeapons[0] server-side (no client-side weapon reference needed).
    1. If bIsRespawn: Get Owning Player Controller → HideRespawnOverlay.
    1. End Ability.

#BP_SL_GameMode

  • bRespawnDelay = true, RespawnDelay = 5.0, DeathAnimationTimeout = 8.0 in defaults.
  • Set bRespawnDelay = false for instant-respawn match configs.

#Respawn Countdown Widget

  • WBP_SL_RespawnOverlay ✅ — exists at Content/SystemLink/UI/HUD/HealthAndShield/.
  • Added to screen via Add to Player Screen (not Push to Game Stack) — no input capture, no cursor side effect.
  • Reference stored on ASLPlayerController (persists across pawn swap).
  • Drive countdown from Delay param passed into OnRespawnBegin.
  • Removed via HideRespawnOverlay called from OnPawnInitialized(bIsRespawn=true) on the new character.

#What Is Not Yet Built

  • Respawn spawn point selection strategy (currently uses UE default PlayerStart selection).
  • "Corpse lingers" mode — RagdollDestroyDelay > 0 support exists in GameMode but the dead pawn detach-before-respawn path is not implemented.
  • Kill feed / scoreboard integration.
  • WBP_SL_RespawnOverlay widget — Blueprint work.