Weapons · Updated Mar 19, 2026
Weapon Equip System
Weapon Equip System
#Architecture Overview
The equip system is intentionally simple:
- C++ (
SetPendingEquipWeapon) — server-only. Takes anASLWeaponActor*. Reads config fromWeaponActor->WeaponData, grants the per-weapon equip ability, finalizes all gameplay state immediately, then fires a GAS event to activate the per-weapon ability for animation.
- Per-weapon equip ability (e.g.
BP_GA_SL_AR_Equip) — one per weapon type.Server Initiated. Plays the TP and FP montages. Purely cosmetic — all gameplay state is already committed before it runs.
OnRep_EquippedWeapon— broadcastsOnEquippedWeaponChanged(ASLWeaponActor*)on every machine whenEquippedWeaponreplicates. Also immediately links anim layers and attaches the TP mesh for simulated proxies (no ability running on them).
There is no coordinator ability. There is no PendingEquipWeapon replicated property. State is finalized on the server the moment equip is requested.
#Replication Model
| Machine | State | Animation | Mesh Attach + Anim Layer |
|---|---|---|---|
| Server (host) | SetPendingEquipWeapon → FinalizeEquip sets EquippedWeapon | Plays TP montage via PlayMontageAndWait (GAS replicates it) | Per-weapon ability BP → LinkEquipAnimLayers before montage, AttachThirdPersonWeapon / AttachFirstPersonWeapon at chosen frames |
| Owning Client | EquippedWeapon replicates | Runs per-weapon ability locally (Server Initiated) → plays FP montage | Same as server — ability BP controls all attachment timing |
| Simulated Proxy | EquippedWeapon replicates | TP montage replicated automatically by GAS via RepAnimMontageInfo | OnRep_EquippedWeapon → LinkEquipAnimLayers + AttachThirdPersonWeapon immediately |
#Full Equip Flow
RequestEquip(WeaponActor) [Server RPC — ASLCharacterBase]
└── WeaponsComponent::SetPendingEquipWeapon(WeaponActor) [authority only]
├── Reads WeaponActor->WeaponData for all config
├── Revokes previous EquipAbilityHandle + FPEquipAbilityHandle if valid
├── Grants WeaponData.EquipAbilityClass → EquipAbilityHandle
├── Grants WeaponData.FPEquipAbilityClass → FPEquipAbilityHandle
├── FinalizeEquip(WeaponActor)
│ ├── Sets EquippedWeapon = WeaponActor (replicated → triggers OnRep on remote clients)
│ ├── GrantWeaponAbilities (grants PrimaryFire + SecondaryFire abilities)
│ └── OnRep_EquippedWeapon() [manual call for listen server host — skipped
│ because IsLocallyControlled() is true; per-weapon ability handles it]
│
└── HandleGameplayEvent(EquipAbilityTriggerTag, Payload{WeaponData})
└── Activates per-weapon TP equip ability on server + owning client
Per-weapon TP equip ability (e.g. BP_GA_SL_AR_EquipTP) — Server Initiated
├── GetEquippedWeapon → WeaponActor → WeaponActor->WeaponData (use this, not OptionalObject)
├── [Owning client only] Sends gameplay event to trigger FP equip ability (Local Only)
├── [Server] PlayMontageAndWait on TP character mesh
│ └── GAS replicates TP montage to ALL clients via RepAnimMontageInfo
├── WaitGameplayEvent(Equip_Attach) → LinkEquipAnimLayers + CompleteEquip
└── EndAbility when montage completes
Per-weapon FP equip ability (e.g. BP_GA_SL_AR_EquipFP) — Local Only
├── Plays FP montage on owning client independently
└── EndAbility on its own timeline
OnRep_EquippedWeapon fires on all clients when EquippedWeapon replicates
├── Broadcasts OnEquippedWeaponChanged(ASLWeaponActor*) — HUD reticle swap; call ->WeaponData for config
└── [!IsLocallyControlled() — simulated proxies only]
├── LinkEquipAnimLayers(WeaponActor)
└── AttachThirdPersonWeapon(WeaponActor)
#Data Asset Setup
| Field | Value |
|---|---|
EquipAbilityClass | TP equip ability (Server Initiated). e.g. BP_GA_SL_AR_EquipTP |
FPEquipAbilityClass | FP equip ability (Local Only). e.g. BP_GA_SL_AR_EquipFP |
EquipCueTag | Optional. Gameplay Cue tag for replicated equip cosmetics (sounds, VFX) |
The TP trigger tag is read directly from the EquipAbilityClass CDO's AbilityTriggers[0].TriggerTag — no separate tag field on the data asset. The FP trigger tag is read the same way via GetEquipTagFromAbilityClass(FPEquipAbilityClass) inside the TP ability BP.
#Blueprint — TP Equip Ability (e.g. BP_GA_SL_AR_EquipTP)
Handles the third-person montage and all authoritative equip timing. Also responsible for triggering the FP equip ability on the owning client.
#Class Defaults
| Property | Value |
|---|---|
| Parent class | SLGameplayAbility_Equip |
| Net Execution Policy | Server Initiated |
| Ability Tags | SLTags.Abilities.Equip.<WeaponName> |
| Activation Owned Tags | SLTags.States.Weapon.Equipping — drives USLWeaponsComponent::IsEquipping() |
| Triggers | One entry: TriggerTag = SLTags.Abilities.Equip.<WeaponName>, Trigger Source = Gameplay Event |
#Event Graph
Event ActivateAbilityFromEvent (EventData)
│
├── GetAvatarActorFromActorInfo → Cast SLCharacterBase → GetWeaponsComponent → WeaponsComponent
├── WeaponsComponent → GetEquippedWeapon → WeaponActor
│ (Do NOT use OptionalObject from EventData — it is a TWeakObjectPtr resolved via
│ network GUID and may be null on the owning client if the asset isn't resident yet.
│ EquippedWeapon is a replicated ASLWeaponActor* and is guaranteed valid before the ability fires.)
│ WeaponActor → WeaponData (for config — reticle class, FP ability class, etc.)
│
├── [Trigger FP equip ability on owning client only]
│ Branch: WeaponsComponent → IsLocallyControlled
│ True →
│ GetEquipTagFromAbilityClass(WeaponData → FPEquipAbilityClass) → FPTag
│ Send Gameplay Event To Actor
│ Actor: GetAvatarActorFromActorInfo
│ EventTag: FPTag
│ Payload: (OptionalObject = WeaponData)
│
├── [Link anim layers BEFORE the montage so arms are in the correct pose from frame 0]
│ WeaponsComponent → LinkEquipAnimLayers(WeaponActor)
│
├── [TP Montage — server plays, GAS replicates to all clients]
│ PlayMontageAndWait
│ Override Mesh: WeaponsComponent → GetThirdPersonCharacterMesh
│ Montage: <TP equip montage, hardcoded in this BP>
│ OnCompleted / OnInterrupted / OnCancelled → EndAbility
│
├── [TP weapon attach — at the frame where the hand grabs the weapon]
│ WaitGameplayEvent (SLTags.Events.Weapon.Equip_Attach, Only Trigger Once = true)
│ └── EventReceived →
│ ├── WeaponsComponent → AttachThirdPersonWeapon(WeaponActor)
│ └── [Optional] ExecuteEquipCue(WeaponData)
│ Simulated proxies get LinkEquipAnimLayers + AttachThirdPersonWeapon
│ automatically via OnRep — no wiring needed.
│
└── EndAbility (from PlayMontageAndWait completion pins only)
#Blueprint — FP Equip Ability (e.g. BP_GA_SL_AR_EquipFP)
Handles the first-person montage only. Runs entirely on the owning client. Lifecycle is independent of the TP ability.
#Class Defaults
| Property | Value |
|---|---|
| Parent class | SLGameplayAbility_Equip |
| Net Execution Policy | Local Only |
| Net Security Policy | ClientOrServer — ability is activated client-side via gameplay event |
| Ability Tags | SLTags.Abilities.EquipFP.<WeaponName> |
| Activation Owned Tags | Leave empty — do NOT add SLTags.States.Weapon.Equipping here. Only the TP equip ability owns that tag. Adding it to both causes a GAS prediction collision warning. |
| Triggers | One entry: TriggerTag = SLTags.Abilities.EquipFP.<WeaponName>, Trigger Source = Gameplay Event |
#Event Graph
Event ActivateAbilityFromEvent (EventData)
│
├── GetAvatarActorFromActorInfo → Cast SLCharacterBase → GetWeaponsComponent → WeaponsComponent
├── WeaponsComponent → GetEquippedWeapon → WeaponActor
│
├── [FP Montage]
│ Play Montage (or PlayMontageAndWait if you need to react to its completion)
│ Target: WeaponsComponent → GetFirstPersonCharacterMesh
│ Montage: <FP equip montage, hardcoded in this BP>
│
├── [FP weapon attach — independent timing from TP]
│ WaitGameplayEvent (SLTags.Events.Weapon.Equip_Attach, Only Trigger Once = true)
│ └── EventReceived →
│ └── WeaponsComponent → AttachFirstPersonWeapon(WeaponActor)
│
└── EndAbility
TheEquip_Attachnotify is in the TP montage. It fires on all machines including the owning client (via GAS montage replication), soWaitGameplayEventworks here too. If the FP montage has a different attach frame, place a second notify with the same or a different tag.
Montage assets are hardcoded directly in both Blueprints.
#Dynamic Material Instances
Material setup now lives in the weapon actor BP, not in the equip abilities. USLWeaponsComponent calls OnAttachedToCharacter(Character) on the weapon actor after meshes are attached, and OnDetachedFromCharacter() on unequip.
In the weapon actor BP subclass (e.g. BP_WeaponActor_AssaultRifle):
Event OnAttachedToCharacter (Character)
│
├── Character → GetWeaponsComponent → WeaponsComponent
├── WeaponsComponent → GetFirstPersonCharacterWeaponMesh → FPMesh
├── FPMesh → Create Dynamic Material Instance (slot index) → store as variable
├── WeaponsComponent → OnAmmoChanged → Bind → update material params
└── [any other per-equip visual setup]
Event OnDetachedFromCharacter
└── Set DMI variable = None
The equip abilities no longer call CacheFPWeaponDynamicMaterial or CacheTPWeaponDynamicMaterial.
#Equip Cue (Optional)
If EquipCueTag is set on the data asset, call ExecuteEquipCue(WeaponData) from the per-weapon equip ability BP at whatever point in the animation feels right. ExecuteEquipCue fires on authority only — GAS multicasts it to all clients.
Use this for replicated sounds and VFX that all players should see when someone equips a weapon. Local-only cosmetics go in a branch guarded by IsLocallyControlled.
#Adding a New Weapon's Equip Ability
- Create
BP_GA_SL_<WeaponName>_EquipTP(TP ability) following the TP ability setup above
- Create
BP_GA_SL_<WeaponName>_EquipFP(FP ability) following the FP ability setup above
- Add weapon-specific tags to Project Settings → GameplayTags:
SLTags.Abilities.Equip.<WeaponName>(TP)SLTags.Abilities.EquipFP.<WeaponName>(FP)
- On each BP's CDO, set Ability Tags and Triggers[0].TriggerTag to their respective tag
- Set Net Execution Policy:
Server Initiatedon the TP ability,Local Onlyon the FP ability
- Assign the TP and FP equip montages directly in their respective BPs
- Open the weapon's
USLWeaponDataAssetand setEquipAbilityClassandFPEquipAbilityClass
- Optionally set
EquipCueTagfor replicated equip sounds/VFX
No coordinator ability. No changes to C++ needed.