Files

380 lines
15 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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 |