# Item Example — Pistol (Weapon) **Item Type:** `Weapon` **Complexity:** High **Systems Used:** `DA_ItemData`, `BP_ItemPickup`, `BP_WeaponBase`, `BPC_FirearmSystem`, `BPC_AmmoComponent`, `BPC_DamageReceptionSystem`, `BPC_RecoilSystem`, `BPC_ReloadSystem`, `BPC_EquipmentSlotSystem`, `BPC_InventorySystem`, `I_UsableItem`, `I_Damageable` **What You Learn:** Full weapon pipeline — Data Asset with EquipmentData, pickup-to-equip flow, frame-driven fire with hitscan, ammo management, damage application, recoil --- ## 1. Create the DA_ItemData ### 1.1 — Create the Data Asset ``` Content Browser → Game/Items/ (or Game/Weapons/) Right-click → Miscellaneous → Data Asset Class: DA_ItemData Name: DA_Item_Pistol ``` ### 1.2 — Fill Core Properties | Property | Value | Notes | |----------|-------|-------| | `Item Tag` | `Framework.Item.Weapon.Pistol` | Registered in `DA_GameTagRegistry` | | `Display Name` | "9mm Pistol" | | | `Description` | "A standard-issue 9mm semi-automatic pistol." | | | `Icon` | `T_Pistol_Icon` | | | `World Mesh` | `SM_Pistol` | Soft reference | | `Weight` | `3.0` | Heavier than consumables | | `Stack Limit` | `1` | Cannot stack weapons | | `Item Type` | `Weapon` | Triggers `Equipment Data` panel | ### 1.3 — Fill Equipment Data When `ItemType == Weapon`, the `Equipment Data` panel appears: | Field | Value | Notes | |-------|-------|-------| | `Slot` | `Framework.Equipment.Slot.PrimaryWeapon` | Occupies player's primary weapon slot | | `Damage` | `15.0` | Base damage per shot | | `Fire Rate` | `0.15` | Seconds between shots (400 RPM) | | `Range` | `5000.0` | Hitscan range in Unreal units | | `Magazine Size` | `12` | Rounds per magazine | | `Reload Time` | `2.0` | Seconds to reload | ### 1.4 — Save --- ## 2. Create the BP_Pickup_Pistol (World Pickup) Same as other pickups, with weapon mesh: ### 2.1 — Blueprint + Components ``` Content Browser → Game/Pickups/ Right-click → Blueprint Class → Actor Name: BP_Pickup_Pistol Components: ├── StaticMeshComponent "Mesh" └── SphereComponent "PickupTrigger" (radius 200, OverlapOnlyPawn) ``` ### 2.2 — Variable & Construction Script Same pattern as flashlight — `ItemData` → Get World Mesh → Load Synchronous → Set Static Mesh. ### 2.3 — I_Interactable Wiring Same as other pickups, but **after adding item to inventory**, also auto-equip it: ``` Event Interact │ ├─ Interactor → Get BPC_InventorySystem ├─ CanAddItem(ItemData, 1) → True → AddItem → store result │ │ │ ├─ Interactor → Get BPC_EquipmentSlotSystem │ │ └─ Equip To Slot(ItemData, PrimaryWeapon slot tag) │ │ └─ This spawns the BP_Pistol actor at the player's hand socket │ │ │ ├─ Destroy Actor │ └─ Return True │ └─ False → Print "Inventory Full" → Return False ``` --- ## 3. Create the BP_Pistol (Held Weapon Actor) This is the actor that appears in the player's hand when equipped. ### 3.1 — Create Blueprint ``` Content Browser → Game/Actors/ (or Game/Weapons/) Right-click → Blueprint Class → Actor (or BP_WeaponBase from framework) Name: BP_Pistol ``` ### 3.2 — Add Components | # | Component | Name | Purpose | |---|-----------|------|---------| | 1 | `SkeletalMeshComponent` | `WeaponMesh` | Animated weapon (slide, magazine) | | 2 | `SceneComponent` | `MuzzleSocket` | Where the bullet trace starts (attach to muzzle bone) | | 3 | `AudioComponent` | `FireSound` | Plays fire sound | | 4 | `AudioComponent` | `ReloadSound` | Plays reload sound | | 5 | `ParticleSystemComponent` | `MuzzleFlash` | Fire particle effect | | 6 | *(Optional)* `PointLightComponent` | `MuzzleLight` | Brief flash on fire | Parent `MuzzleSocket` to the muzzle bone on `WeaponMesh`, or set its relative location to where the barrel ends. ### 3.3 — Variables | Variable | Type | Default | Purpose | |----------|------|---------|---------| | `CurrentAmmoInMag` | Integer | `12` | Rounds currently loaded | | `MaxMagazineSize` | Integer | `12` | From EquipmentData | | `bIsFiring` | Boolean | `false` | Prevents re-trigger during fire rate cooldown | | `FireRateTimer` | Float | `0.15` | Seconds between shots | | `WeaponData` | DA_ItemData* | — | Reference to the pistol Data Asset | | `FireRange` | Float | `5000.0` | Hitscan distance | ### 3.4 — Interfaces Class Settings → Interfaces → Add: - `UUsableItem` — so active item system can route fire input ### 3.5 — Fire Logic (Hitscan) ``` Event Use Item (Interface -> UUsableItem) │ User: AActor*, Target: AActor* │ Return Boolean │ ├─ bIsFiring? → Branch │ ├─ True → Return False (fire rate lockout) │ └─ False: │ │ │ ├─ CurrentAmmoInMag > 0? → Branch │ │ ├─ True: │ │ │ ├─ Set bIsFiring = true │ │ │ ├─ CurrentAmmoInMag = CurrentAmmoInMag - 1 │ │ │ │ │ │ │ ├─ [Play Effects] │ │ │ │ ├─ MuzzleFlash → Activate │ │ │ │ ├─ FireSound → Play │ │ │ │ └─ MuzzleLight → Set Visibility (true) │ │ │ │ └─ Delay 0.05 → Set Visibility (false) │ │ │ │ │ │ │ ├─ [Line Trace — Hitscan] │ │ │ │ │ │ │ │ │ ├─ Get Player Controller → Get Camera Location/Direction │ │ │ │ ├─ Start: MuzzleSocket → Get World Location │ │ │ │ ├─ End: Start + (CameraForward × FireRange) │ │ │ │ │ │ │ │ │ └─ Line Trace By Channel (Visibility) │ │ │ │ ├─ Trace Complex: false │ │ │ │ └─ Ignore Self, Ignore Owning Pawn │ │ │ │ │ │ │ │ │ ├─ Hit? → Branch │ │ │ │ │ │ │ │ │ │ │ ├─ True: │ │ │ │ │ │ ├─ Hit Actor → Does Implement Interface (UDamageable)? │ │ │ │ │ │ │ ├─ True: │ │ │ │ │ │ │ │ ├─ Call I_Damageable.Take Damage │ │ │ │ │ │ │ │ │ ├─ Damage Amount: WeaponData → Get Equipment Data → Damage │ │ │ │ │ │ │ │ │ ├─ Damage Causer: Self (or OwningPawn) │ │ │ │ │ │ │ │ │ ├─ Damage Type: Framework.Combat.Damage.Physical │ │ │ │ │ │ │ │ │ ├─ Hit Location: HitResult.ImpactPoint │ │ │ │ │ │ │ │ │ └─ Hit Direction: (ImpactPoint - Start) normalized │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └─ Print String: "Hit {HitActor} for {Damage} damage" │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └─ False: Print "Hit non-damageable object" │ │ │ │ │ │ │ │ │ │ │ │ │ └─ Spawn Decal/Impact Effect at Hit Location │ │ │ │ │ │ │ │ │ │ │ └─ False (miss): nothing │ │ │ │ │ │ │ │ │ └─ Draw Debug Line (red for 1 second, editor only) │ │ │ │ │ │ │ ├─ [Recoil] │ │ │ │ └─ Get BPC_RecoilSystem (on pawn) → Apply Recoil (WeaponData) │ │ │ │ │ │ │ └─ Delay (FireRateTimer) → Set bIsFiring = false → Return True │ │ │ │ │ └─ False: (out of ammo) │ │ ├─ Play click/empty sound │ │ ├─ (Optional) Auto-reload → Call Reload │ │ └─ Return False ``` ### 3.6 — Reload Logic ``` Function: Reload │ ├─ CurrentAmmoInMag >= MaxMagazineSize? → Return (already full) ├─ Set bIsReloading = true │ ├─ Get Owning Pawn → Get BPC_AmmoComponent │ │ │ └─ Get Ammo Count (AmmoTypeTag = Framework.Item.Ammo.Pistol9mm) │ │ │ ├─ Branch (> 0?) │ │ ├─ True: │ │ │ ├─ Rounds To Load = Min(AmmoAvailable, MaxMagazineSize - CurrentAmmoInMag) │ │ │ ├─ CurrentAmmoInMag += Rounds To Load │ │ │ ├─ AmmoComponent → Consume Ammo (RoundsToLoad) │ │ │ └─ Play ReloadSound │ │ │ │ │ └─ False: Print "No ammo" → Return │ │ │ ├─ Delay (ReloadTime) → Set bIsReloading = false │ └─ Broadcast OnReloadComplete ``` --- ## 4. Wire BPC_AmmoComponent (On Player Pawn) Create BP child of `BPC_AmmoComponent` (spec #70): | Variable | Type | Default | Purpose | |----------|------|---------|---------| | `AmmoPool` | Map (GameplayTag → Integer) | Empty | AmmoType → Count | ``` Function: Get Ammo Count (AmmoTypeTag) → Integer │ └─ AmmoPool.Find(AmmoTypeTag) → Return count (0 if not found) Function: Consume Ammo (AmmoTypeTag, Amount) │ ├─ Current = Get Ammo Count (AmmoTypeTag) ├─ Current -= Amount ├─ Clamp to 0 minimum └─ AmmoPool[AmmoTypeTag] = Current Function: Add Ammo (AmmoTypeTag, Amount) │ ├─ AmmoPool.Find or Add (AmmoTypeTag) └─ Increment by Amount ``` --- ## 5. Wire BPC_RecoilSystem (On Player Pawn) Create BP child of `BPC_RecoilSystem` (spec #77): ``` Function: Apply Recoil (WeaponData: DA_ItemData*) │ ├─ WeaponData → Get Equipment Data → Get Damage (or FireRate) ├─ Calculate kick: PitchUp = random(1.0, 2.5), YawSide = random(-0.5, 0.5) │ ├─ Get Player Controller → Add Controller Pitch Input (PitchUp × -1) ├─ Get Player Controller → Add Controller Yaw Input (YawSide) │ └─ (For smooth recovery, use a Timeline or interp to return to original aim) ``` --- ## 6. Create DA_Item_Ammo_Pistol9mm (Ammo Data Asset) ``` Content Browser → Game/Items/ Right-click → Miscellaneous → Data Asset → DA_ItemData Name: DA_Item_Ammo_Pistol9mm Properties: ItemTag: Framework.Item.Ammo.Pistol9mm DisplayName: "9mm Rounds" ItemType: Ammo StackLimit: 999 Weight: 0.05 (AmmoData sub-panel auto-appears — set PerPickupCount = 15) ``` Create `BP_Pickup_Ammo_Pistol9mm` following the same pattern as other pickups. On interact, call `BPC_AmmoComponent.Add Ammo(AmmoTypeTag, PerPickupCount)` instead of adding to inventory. --- ## 7. Verification Checklist **Step 1 — Pickup:** - [ ] Pistol pickup in level → mesh visible → interact → goes to inventory - [ ] Auto-equips to primary weapon slot (weapon appears in player's hand) **Step 2 — Fire:** - [ ] Equip pistol, press fire → muzzle flash + sound + hitscan line - [ ] Hit an enemy actor implementing `I_Damageable` → health decreases - [ ] Ammo counter decreases (12 → 11 → 10...) - [ ] Camera kicks slightly on each shot (recoil) - [ ] Fire rate enforced: cannot spam fire faster than 0.15s **Step 3 — Empty Magazine:** - [ ] Fire all 12 rounds → click sound plays, no trace, no damage - [ ] Auto-reload? Or manual reload prompt appears **Step 4 — Reload:** - [ ] Press Reload key → reload animation/sound plays - [ ] Ammo transfers from pool to magazine - [ ] Magazine shows 12 rounds again - [ ] If no ammo in pool → reload fails **Step 5 — Ammo Pickup:** - [ ] Pick up `BP_Pickup_Ammo_Pistol9mm` → ammo pool increases by 15 - [ ] Reload → magazine fills, pool decreases by rounds loaded **Step 6 — Damage Pipeline:** - [ ] Enemy pawn has `BPC_DamageReceptionSystem` + `BPC_HealthSystem` - [ ] Fire at enemy → `ApplyDamage(15.0, ...)` called - [ ] Damage pipeline: resistance → armor → shield → health - [ ] Enemy health decreases proportionally --- ## 8. Weapon Data Flow (Complete) ``` Player Input (IA_Fire) │ └─ BPC_ActiveItemSystem routes to equipped weapon │ └─ BP_Pistol (implements I_UsableItem) │ ├─ [Ammo Check] → BPC_AmmoComponent (in-mag count) ├─ [Hitscan] → Line Trace from MuzzleSocket │ │ │ └─ Hit Actor (implements I_Damageable) │ │ │ └─ BPC_DamageReceptionSystem.ApplyDamage(RawDamage, Causer, Type, Location, Direction) │ │ │ ├─ Calculate Resistance │ ├─ Apply Shield (BPC_ShieldDefenseSystem) │ ├─ Apply Health (BPC_HealthSystem) │ ├─ Evaluate Hit Reaction (BPC_HitReactionSystem) │ │ ├─ Stagger? (damage > threshold) │ │ └─ Knockdown? (damage > higher threshold) │ │ │ └─ Broadcast OnDamageReceived → UI, audio, metrics │ ├─ [Recoil] → BPC_RecoilSystem.ApplyRecoil() ├─ [Effects] → MuzzleFlash, FireSound, ImpactDecal └─ [Fire Rate] → Timer lockout until next shot When magazine empty: └─ Player presses IA_Reload └─ BP_Pistol.Reload() └─ BPC_AmmoComponent.ConsumeAmmo() (from pool) └─ BP_Pistol.CurrentAmmoInMag += rounds loaded ``` --- ## 9. Key Differences — Weapon vs Tool vs Consumable | Aspect | Pistol (Weapon) | Flashlight (Tool) | MedKit (Consumable) | |--------|----------------|-------------------|---------------------| | ItemType | `Weapon` | `Tool` | `Consumable` | | Slot | `PrimaryWeapon` | `Tool` | None (inventory bag) | | Use action | Frame-driven (continuous) | Toggle (single click) | One-shot (consumed) | | Ammo | Yes (magazine + pool) | No (battery as optional resource) | No | | Damage output | Yes | No | No (heals) | | Fire rate | Throttled (timer) | N/A | Use duration (2s anim) | | Recoil | Yes | No | No | | Reload | Yes | No | No | | Stackable | No (StackLimit=1) | No (StackLimit=1) | Yes (StackLimit=5) | | Interfaces | `I_UsableItem` | `I_UsableItem` + `I_Toggleable` | `I_UsableItem` | | Pickup → Equip | Auto-equips to slot | Auto-equips to slot | Goes to inventory only |