Files

15 KiB
Raw Permalink Blame History

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