Weapons · Updated May 14, 2026
Weapon Fire Ability
Weapon Fire Ability
#Overview
USLGameplayAbility_Fire is the C++ base class for all fire abilities. It handles the full shot loop — timing, tracing, damage, recoil, and cue dispatch. BP subclasses implement the two cosmetic events and configure the ability tags.
Net execution policy: Local Predicted
The ability runs immediately on the owning client without waiting for the server. The server also runs ExecuteFire (listen-server host) or receives the shot via Server_ProcessShots (dedicated server / remote clients).
#Creating a Fire Ability BP
- Create a new Blueprint, parent class
SLGameplayAbility_Fire
- Name it
BP_GA_SL_<WeaponName>_PrimaryFire(orSecondaryFire)
- In Class Defaults, set:
| Setting | Value | Notes |
|---|---|---|
bIsPrimaryFire | true for primary, false for secondary | |
Required Weapon Class | BP_WeaponActor_<WeaponName> | Required. Prevents all granted fire abilities from activating on the same event — only the equipped weapon's ability runs. |
| Net Execution Policy | Local Predicted | |
| Instancing Policy | Instanced Per Actor | |
| Ability Tags | SLTags.Abilities.PrimaryFire | |
| Activation Owned Tags | SLTags.States.Weapon.Firing | Applied while the ability is active. bIsFiring reads this tag — drive fire and pump-cycle animations from it. |
| Activation Blocked Tags | SLTags.States.Weapon.Firing | Prevents re-activation while the tag is on the ASC — blocks rapid-fire during PostFireDelay on single-shot weapons. |
| Block Abilities with Tag | (leave empty) | Do NOT use this for States.Weapon.Firing. In UE5.7, BlockAbilitiesWithTag bleeds into the main tag container and causes CommitAbility to fail on unrelated abilities. Use Activation Blocked Tags instead. |
| Triggers[0] → Tag | SLTags.Events.Weapon.PrimaryFire | |
| Triggers[0] → Source | Gameplay Event |
- Set
FireAbilityClasson the weapon data asset'sPrimaryFireModeto this BP class — the weapons component grants it when the weapon is equipped.
#Single-shot pump/burst delay
Set PostFireDelay (seconds) on FSLWeaponFireMode in the weapon data asset. The fire ability stays alive for that duration after the shot fires — SLTags.States.Weapon.Firing remains on the ASC and bIsFiring is true, giving the anim blueprint time to play the pump or bolt-cycle animation.
Activation Blocked Tags = SLTags.States.Weapon.Firing is the only thing preventing re-fire during the pump window. Without it the player can spam the trigger and the ability re-activates each press, restarting the timer and breaking the pump animation. The Activation Owned Tags row above grants the tag; this row blocks re-entry on it. Both are required. No cooldown GE needed.
#Execution Flow
Input fires → SendGameplayEvent(PrimaryFire tag)
│
└── ActivateAbility
│
├── CommitAbility (cost/cooldown check) → fail → EndAbility
│
├── ExecuteFire ← first shot immediately
│
└── [FullAuto] SetTimer(FireInterval) → ExecuteFire each interval
[SingleShot] PostFireDelay > 0 → SetTimer(PostFireDelay) → EndAbility
PostFireDelay == 0 → EndAbility immediately
Releasing the fire input cancels the ability, which clears the timer via EndAbility.
#ExecuteFire — per-shot logic
ExecuteFire
│
├── GetPlayerViewPoint → CamLoc, CamRot, AimDir
│
├── MuzzleSocketName → MuzzleLoc (FP weapon mesh socket, falls back to CamLoc)
│
├── [IsLocallyControlled] DispatchLocalCosmetics:
│ OnLocallyPredictedShotFired(MuzzleLoc, AimDir)
│ WeaponsComponent::ApplyRecoilKick(FireMode)
│ WeaponActor::OnWeaponFired(MuzzleLoc, AimDir)
│ (Skipped on the dedicated server when running a remote client's predicted activation.)
│
├── [HasAuthority && FireCueTag valid]
│ ASC::ExecuteGameplayCue(FireCueTag, {Location=MuzzleLoc, Normal=AimDir})
│ (GAS multicasts to all clients — remote players see muzzle flash / hear fire sound)
│
├── [HasAuthority] ApplyAmmoDecrement(FireMode) ← once per trigger pull
│
└── Pellet loop (1× for bullets, N× for shotgun via PelletCount)
│ PelletDir = ComputeFireDirection(AimDir, cone)
│ TraceEnd = CamLoc + PelletDir * MaxRange
│
├── [HasAuthority && IsLocallyControlled] ── Listen-server host path ──
│ Hitscan → LineTrace(ECC_Visibility)
│ → ApplyGameplayEffectSpecToTarget (BaseDamage via SetByCaller)
│ → ExecuteGameplayCue(ImpactCueTag) on hit
│ → OnProjectileHitPredicted(Hit) — host's only local impact path
│ Projectile → SpawnActor(ProjectileClass)
│
└── [!HasAuthority] ── Remote-client path ──
TracePredictedHit:
LineTrace(ECC_Visibility) → OnProjectileHitPredicted(Hit) ← predicted hit FX
PendingPellets.Add(shot info)
(After the loop, the remote client flushes all pellets via WC->QueueShotsForValidation(PendingPellets) — one RPC per trigger pull regardless of pellet count.)
Both authority and predicted traces use ECC_Visibility so they agree on whether a wall blocks the shot.
#BP Events to Implement
#Weapon Actor Events
The fire ability also calls two BlueprintImplementableEvents directly on the equipped ASLWeaponActor:
| Event | When | Use |
|---|---|---|
OnWeaponFired(MuzzleLoc, FireDir) | Every shot, local client only | Material pulse, muzzle FX, per-shot sounds owned by the weapon actor |
OnWeaponStoppedFiring() | EndAbility, local client only | Stop looping effects, reset material state |
Implement these in the weapon actor BP subclass (e.g. BP_WeaponActor_AssaultRifle). They fire on the same frame as OnLocallyPredictedShotFired.
#OnLocallyPredictedShotFired(MuzzleLocation, FireDirection)
Called every shot, owning client only, with zero latency.
Use this for effects the local player sees regardless of view — it fires for both FP and TP perspectives.
Event OnLocallyPredictedShotFired (MuzzleLocation, FireDirection)
│
├── [FP] Play Montage on FP character mesh / FP weapon mesh (local, not replicated)
│
├── [FP] Spawn Niagara muzzle flash at MuzzleLocation
│
└── [FP] Play fire sound (local 2D sound, not positional)
Do NOT duplicate effects theFireCueTagcue already handles. The cue covers all remote clients. The local player is intentionally excluded from the cue (CueParams.Instigatorcheck in the GC Blueprint) to prevent double-playing.
#OnProjectileHitPredicted(Hit)
Called when the local prediction trace hits something. Use this for zero-latency impact feedback before the server confirms. Fires on both non-authority clients AND the listen server host — the ImpactCueTag Gameplay Cue excludes locally controlled pawns, so this is the only impact path for the host player.
See WeaponHitFX.md for the full two-path impact system.
Event OnProjectileHitPredicted (Hit)
│
├── Spawn Niagara impact system at Hit.ImpactPoint
│ Rotation: MakeRotFromZ(Hit.ImpactNormal)
│
└── Play local impact sound at Hit.ImpactPoint
TheImpactCueTagGameplay Cue (fired byServer_ProcessShots) handles confirmed impact FX on all other clients. The owning client uses this predicted path instead.
#Fire Mode Data Asset Setup
| Field | Value |
|---|---|
bEnabled | true |
FireType | Hitscan or Projectile |
FireMode | FullAuto or SingleShot |
FireAbilityClass | BP_GA_SL_<WeaponName>_PrimaryFire |
RoundsPerMinute | e.g. 600 |
MaxAmmo | e.g. 32 — weapon actor initializes CurrentAmmo to this on spawn |
AmmoDecrementEffect | GE_SL_AmmoDecrement — applied per shot on authority to decrement USLAmmoAttributeSet.CurrentAmmo |
BaseDamage | e.g. 20 |
MaxRange | e.g. 15000 |
MuzzleSocketName | Socket on the FP weapon mesh, e.g. Muzzle |
FireCueTag | GameplayCues.Weapon.<WeaponName>.PrimaryFire |
ImpactCueTag | GameplayCues.Weapon.<WeaponName>.Impact |
#Full-Auto vs Single-Shot
FireMode | Behavior |
|---|---|
FullAuto | Timer fires ExecuteFire every 60 / RoundsPerMinute seconds while the ability is active. Releasing fire cancels the ability, clearing the timer. |
SingleShot | After the first shot, if PostFireDelay > 0, the ability stays alive for that many seconds (driving bIsFiring for pump/cycle animations), then ends. If PostFireDelay == 0, EndAbility is called immediately. |
#Shot Validation (Hitscan, Remote Clients)
After the full pellet loop, remote clients call QueueShotsForValidation once per trigger pull with every pellet from that shot. The weapons component batches across trigger pulls too and sends them to the server via Server_ProcessShots — so a single shotgun blast is one RPC, not eight.
The server re-traces each shot, applies damage, fills HitLocation / HitNormal / bHitPawn into FSLShotInfo, then multicasts confirmed hits via Multicast_OnShotsConfirmed → OnShotsConfirmed BP event on all clients.
MaxUnvalidatedShots and ShotValidationInterval on USLWeaponsComponent control batching behavior. Default is flush every shot.
See WeaponHitFX.md for the impact cue flow that runs inside Server_ProcessShots.
#Replication Summary
| Effect | Who | Mechanism |
|---|---|---|
| FP fire anim / muzzle flash (local) | Owning client only | OnLocallyPredictedShotFired — direct spawn |
| TP fire anim | All clients | PlayMontageAndWait on the TP mesh (GAS replication) |
| Muzzle flash / fire sound (remote) | All other clients | FireCueTag Gameplay Cue — GAS multicast |
| Predicted impact (pre-confirm) | Owning client only | OnProjectileHitPredicted — direct spawn |
| Confirmed impact | All other clients | ImpactCueTag Gameplay Cue — GAS multicast from Server_ProcessShots |
| Damage | Server only | ApplyPointDamage (listen host) or Server_ProcessShots (clients) |
#Adding a Fire Ability for a New Weapon
- Create
BP_GA_SL_<WeaponName>_PrimaryFire(parent:SLGameplayAbility_Fire)
- Set Class Defaults per the table above
- Implement
OnLocallyPredictedShotFired— FP muzzle flash, local sound, FP fire montage
- Implement
OnProjectileHitPredicted— predicted impact VFX/SFX
- Add
GameplayCues.Weapon.<WeaponName>.PrimaryFiretag in Project Settings → GameplayTags
- Create
GC_SL_<WeaponName>_PrimaryFire(GameplayCueNotify_Burst) — set its tag, wire muzzle flash + TP fire sound withIsLocallyControlledguard to skip the owning client
- Set
FireAbilityClass,FireCueTag, andImpactCueTagon the weapon data asset'sPrimaryFireMode