docs: Add detailed item examples for Keycard, MedKit, and Pistol, including step-by-step setup instructions
This commit is contained in:
262
docs/game/item-keycard.md
Normal file
262
docs/game/item-keycard.md
Normal file
@@ -0,0 +1,262 @@
|
||||
# Item Example — Keycard (Key Item)
|
||||
|
||||
**Item Type:** `KeyItem`
|
||||
**Complexity:** Low
|
||||
**Systems Used:** `DA_ItemData`, `BP_ItemPickup`, `BPC_InventorySystem`, `BPC_KeyItemSystem`, `I_Lockable`, `BP_DoorActor`
|
||||
**What You Learn:** Key item Data Asset, auto-protection from drop/clear, used-once pattern, door unlocking via `I_Lockable`
|
||||
|
||||
---
|
||||
|
||||
## 1. Create the DA_ItemData
|
||||
|
||||
### 1.1 — Create the Data Asset
|
||||
|
||||
```
|
||||
Content Browser → Game/Items/
|
||||
Right-click → Miscellaneous → Data Asset
|
||||
Class: DA_ItemData
|
||||
Name: DA_Item_Keycard_Omega
|
||||
```
|
||||
|
||||
### 1.2 — Fill Properties
|
||||
|
||||
| Property | Value | Notes |
|
||||
|----------|-------|-------|
|
||||
| `Item Tag` | `Framework.Item.KeyItem.KeycardOmega` | Unique — registered in `DA_GameTagRegistry` |
|
||||
| `Display Name` | "Omega Keycard" | Player sees this |
|
||||
| `Description` | "A high-security keycard labeled 'Omega Wing.'" | Shown in journal/inventory |
|
||||
| `Icon` | `T_Keycard_Icon` | Card-shaped texture |
|
||||
| `World Mesh` | `SM_Keycard` | Flat card mesh |
|
||||
| `Weight` | `0.1` | Very light |
|
||||
| `Stack Limit` | `1` | One per slot — cannot stack keycards |
|
||||
| `Item Type` | `KeyItem` | Key items are auto-protected |
|
||||
| `b Is Key Item` | ✓ (checked) | This property also appears — it's a boolean flag |
|
||||
| `b Can Be Dropped` | ✗ (unchecked) | Key items should NOT be droppable (auto-forced by `bIsKeyItem`) |
|
||||
|
||||
### 1.3 — Save
|
||||
|
||||
No sub-data needed. Key items don't have Equipment or Consumable data.
|
||||
|
||||
---
|
||||
|
||||
## 2. Create the BP_Pickup_KeycardOmega
|
||||
|
||||
Same pattern as other pickups:
|
||||
|
||||
### 2.1 — Create Blueprint
|
||||
|
||||
```
|
||||
Content Browser → Game/Pickups/
|
||||
Right-click → Blueprint Class → Actor
|
||||
Name: BP_Pickup_KeycardOmega
|
||||
```
|
||||
|
||||
### 2.2 — Components
|
||||
|
||||
| # | Component | Name | Purpose |
|
||||
|---|-----------|------|---------|
|
||||
| 1 | `StaticMeshComponent` | `Mesh` | Card model |
|
||||
| 2 | `SphereComponent` | `PickupTrigger` | Radius 150, `OverlapOnlyPawn` |
|
||||
|
||||
### 2.3 — Variable
|
||||
|
||||
`ItemData` → Type: `DA_ItemData → Object Reference` → Instance Editable ✓
|
||||
|
||||
### 2.4 — Construction Script (same pattern)
|
||||
|
||||
```
|
||||
ItemData → Get World Mesh → Load Synchronous → Set Static Mesh (Mesh)
|
||||
ItemData → Get Display Name → Set Actor Label
|
||||
```
|
||||
|
||||
### 2.5 — I_Interactable Wiring (same pattern)
|
||||
|
||||
```
|
||||
Interactor → Get BPC_InventorySystem → CanAddItem(ItemData, 1)
|
||||
True → AddItem → Destroy Actor → Return True
|
||||
False → Print "Inventory Full" → Return False
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Create the Lockable Door
|
||||
|
||||
### 3.1 — Create Door Blueprint
|
||||
|
||||
```
|
||||
Content Browser → Game/Actors/
|
||||
Right-click → Blueprint Class → Actor (or BP_DoorActor from framework)
|
||||
Name: BP_Door_OmegaWing
|
||||
```
|
||||
|
||||
### 3.2 — Add Components
|
||||
|
||||
| # | Component | Name | Purpose |
|
||||
|---|-----------|------|---------|
|
||||
| 1 | `StaticMeshComponent` | `DoorFrame` | The door frame (static) |
|
||||
| 2 | `StaticMeshComponent` | `DoorPanel` | The moving door part |
|
||||
| 3 | `BoxComponent` | `InteractionTrigger` | Door interaction range |
|
||||
|
||||
### 3.3 — Add Interfaces
|
||||
|
||||
Class Settings → Interfaces → Add:
|
||||
- `UInteractable`
|
||||
- `ULockable`
|
||||
|
||||
### 3.4 — Variables
|
||||
|
||||
| Variable | Type | Default | Purpose |
|
||||
|----------|------|---------|---------|
|
||||
| `bIsLocked` | Boolean | `true` | Door starts locked |
|
||||
| `bIsOpen` | Boolean | `false` | Door starts closed |
|
||||
| `RequiredKeyTag` | GameplayTag | `Framework.Item.KeyItem.KeycardOmega` | Which key unlocks this |
|
||||
|
||||
### 3.5 — Wire I_Lockable
|
||||
|
||||
```
|
||||
Event Try Unlock (Interface -> ULockable)
|
||||
│ Unlocker: AActor*, KeyTag: FGameplayTag
|
||||
│ Return Boolean
|
||||
│
|
||||
├─ bIsLocked? → Branch
|
||||
│ ├─ True:
|
||||
│ │ ├─ KeyTag == RequiredKeyTag? → Branch
|
||||
│ │ │ ├─ True:
|
||||
│ │ │ │ ├─ Set bIsLocked = false
|
||||
│ │ │ │ ├─ Unlocker → Get BPC_KeyItemSystem
|
||||
│ │ │ │ │ └─ Consume Key (KeyTag) ← removes key from inventory
|
||||
│ │ │ │ ├─ Print "Door unlocked with Omega Keycard"
|
||||
│ │ │ │ └─ Return True
|
||||
│ │ │ │
|
||||
│ │ │ └─ False:
|
||||
│ │ │ ├─ Print "Wrong key — requires Omega Keycard"
|
||||
│ │ │ └─ Return False
|
||||
│ │ │
|
||||
│ │ └─ False: (already unlocked)
|
||||
│ │ └─ Return True
|
||||
|
||||
Event Is Locked
|
||||
│ Return Boolean
|
||||
└─ Return bIsLocked
|
||||
|
||||
Event Get Required Key Tag
|
||||
│ Return FGameplayTag
|
||||
└─ Return RequiredKeyTag
|
||||
|
||||
Event Try Lock
|
||||
│ Locker: AActor*
|
||||
│ Return Boolean
|
||||
└─ Set bIsLocked = true → Return True (re-lockable for puzzle purposes)
|
||||
```
|
||||
|
||||
### 3.6 — Wire I_Interactable (on door)
|
||||
|
||||
```
|
||||
Event Interact (Interface -> UInteractable)
|
||||
│ Interactor: AActor*
|
||||
│ Return Boolean
|
||||
│
|
||||
├─ bIsLocked? → Branch
|
||||
│ ├─ True:
|
||||
│ │ ├─ Interactor → Get BPC_KeyItemSystem
|
||||
│ │ │ └─ Has Key(RequiredKeyTag)?
|
||||
│ │ │ ├─ True:
|
||||
│ │ │ │ ├─ Call Try Unlock (self, Interactor, RequiredKeyTag)
|
||||
│ │ │ │ └─ (If unlocked) → Open Door (see below)
|
||||
│ │ │ │
|
||||
│ │ │ └─ False:
|
||||
│ │ │ ├─ Print "Door is locked. Find Omega Keycard."
|
||||
│ │ │ └─ Return False
|
||||
│ │ │
|
||||
│ │ └─ Return False
|
||||
│ │
|
||||
│ └─ False: (door is unlocked)
|
||||
│ ├─ bIsOpen? → Branch
|
||||
│ │ ├─ True → Close Door → Return True
|
||||
│ │ └─ False → Open Door → Return True
|
||||
|
||||
Function: Open Door
|
||||
│
|
||||
├─ Timeline (Rotation track, 0 → OpenAngle over OpenDuration)
|
||||
│ └─ Update: DoorPanel → Set Relative Rotation
|
||||
├─ Set bIsOpen = true
|
||||
└─ (Optional) Play door creak sound
|
||||
|
||||
Function: Close Door
|
||||
│
|
||||
├─ Timeline (Rotation track, OpenAngle → 0 over CloseDuration)
|
||||
│ └─ Update: DoorPanel → Set Relative Rotation
|
||||
├─ Set bIsOpen = false
|
||||
└─ (Optional) Play door close sound
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Wire BPC_KeyItemSystem (On Player Pawn)
|
||||
|
||||
The `BPC_KeyItemSystem` tracks which key items the player holds. It automates:
|
||||
- Protecting key items from being dropped/cleared
|
||||
- Validating key item requirements on door/lock interactions
|
||||
- Removing consumed key items after use
|
||||
|
||||
In your BP child of `BPC_KeyItemSystem`:
|
||||
|
||||
```
|
||||
Function: Has Key (KeyTag: GameplayTag) → Boolean
|
||||
│
|
||||
├─ Get Owner → Get BPC_InventorySystem
|
||||
├─ Get All Items → For Each Loop
|
||||
│ ├─ Item → Get bIsKeyItem → Branch
|
||||
│ │ ├─ True: Item → Get ItemTag == KeyTag? → Return True
|
||||
│ │ └─ False: Continue
|
||||
│ │
|
||||
│ └─ Loop end → Return False
|
||||
|
||||
Function: Consume Key (KeyTag: GameplayTag)
|
||||
│
|
||||
├─ Get Owner → Get BPC_InventorySystem
|
||||
├─ Has Key(KeyTag)? → Branch
|
||||
│ ├─ True: Inventory → Remove Item (by KeyTag, Quantity=1)
|
||||
│ └─ False: Print "No such key in inventory"
|
||||
```
|
||||
|
||||
> In the full framework, `BPC_KeyItemSystem` also prevents `Clear Inventory` and `Drop` operations from affecting key items by checking `bIsKeyItem` on the Data Asset.
|
||||
|
||||
---
|
||||
|
||||
## 5. Verification Checklist
|
||||
|
||||
**Step 1 — Pickup:**
|
||||
- [ ] `BP_Pickup_KeycardOmega` placed in level, mesh visible
|
||||
- [ ] Walk up → prompt "Pick up Omega Keycard" → press Interact → card goes to inventory
|
||||
|
||||
**Step 2 — Inventory Protection:**
|
||||
- [ ] Keycard appears in inventory with "KEY" badge/indicator
|
||||
- [ ] Try to drop keycard → blocked (cannot drop key items)
|
||||
- [ ] Try "Clear Inventory" → keycard remains (protected)
|
||||
|
||||
**Step 3 — Door Interaction:**
|
||||
- [ ] Walk up to `BP_Door_OmegaWing` while locked → prompt "Door is locked"
|
||||
- [ ] Interact → door stays closed, shows "Requires Omega Keycard"
|
||||
- [ ] Pick up keycard → walk to door → interact → door unlocks, keycard consumed from inventory
|
||||
- [ ] Interact again → door opens
|
||||
|
||||
**Step 4 — Wrong Key:**
|
||||
- [ ] Create a `DA_Item_Keycard_Beta` with a different tag
|
||||
- [ ] Pick it up, try on Omega door → "Wrong key — requires Omega Keycard"
|
||||
- [ ] Keycard_Beta stays in inventory (not consumed on wrong door)
|
||||
|
||||
---
|
||||
|
||||
## 6. KeyItem vs Regular Item — What's Different
|
||||
|
||||
| Behavior | Regular Item | Key Item (`bIsKeyItem = true`) |
|
||||
|----------|-------------|-------------------------------|
|
||||
| Can be dropped? | Yes (if `bCanBeDropped`) | No (auto-protected) |
|
||||
| Can be cleared (death/new game)? | Yes | No (preserved across death) |
|
||||
| Stacks | Yes (up to StackLimit) | Usually StackLimit=1 |
|
||||
| Appears in inventory? | Yes | Yes, with KEY indicator |
|
||||
| Consumed on use? | Depends on type | Yes (used once, then removed) |
|
||||
| Sells to vendor? | Yes | No (protected from sale) |
|
||||
|
||||
Key items exist specifically to **gate progression**. They're a player's permanent proof of having solved a puzzle, explored an area, or defeated a boss. The framework ensures they can't be accidentally lost.
|
||||
197
docs/game/item-medkit.md
Normal file
197
docs/game/item-medkit.md
Normal file
@@ -0,0 +1,197 @@
|
||||
# Item Example — MedKit (Consumable)
|
||||
|
||||
**Item Type:** `Consumable`
|
||||
**Complexity:** Low
|
||||
**Systems Used:** `DA_ItemData`, `BP_ItemPickup`, `BPC_InventorySystem`, `BPC_ConsumableSystem`, `BPC_HealthSystem`
|
||||
**What You Learn:** Consumable Data Asset, pickup actor, health restore, stackable item, quick-slot use
|
||||
|
||||
---
|
||||
|
||||
## 1. Create the DA_ItemData
|
||||
|
||||
### 1.1 — Create the Data Asset
|
||||
|
||||
```
|
||||
Content Browser → Game/Items/
|
||||
Right-click → Miscellaneous → Data Asset
|
||||
Class: DA_ItemData
|
||||
Name: DA_Item_MedKit
|
||||
```
|
||||
|
||||
### 1.2 — Fill Core Properties
|
||||
|
||||
| Property | Value | Notes |
|
||||
|----------|-------|-------|
|
||||
| `Item Tag` | `Framework.Item.Consumable.MedKit` | Unique identity |
|
||||
| `Display Name` | "MedKit" | Player sees this |
|
||||
| `Description` | "A standard medical kit. Restores 25 health." | Shown in inventory |
|
||||
| `Icon` | `T_MedKit_Icon` | Any red/white cross texture for prototype |
|
||||
| `World Mesh` | `SM_MedKit` | Soft reference — Construction Script resolves it |
|
||||
| `Weight` | `1.0` | Light carry weight |
|
||||
| `Stack Limit` | `5` | Can carry up to 5 in one stack |
|
||||
| `Item Type` | `Consumable` | Triggers Consumable Data panel |
|
||||
|
||||
### 1.3 — Fill Consumable Data
|
||||
|
||||
When `ItemType == Consumable`, the `Consumable Data` panel appears:
|
||||
|
||||
| Field | Value | Notes |
|
||||
|-------|-------|-------|
|
||||
| `Health Restore` | `25.0` | How much HP to restore (0-100 clamp) |
|
||||
| `Stress Reduce` | `0.0` | Optional stress relief |
|
||||
| `Use Duration` | `2.0` | Seconds to consume (animation time) |
|
||||
| `bConsumedOnUse` | ✓ (true) | Item is removed from inventory after use |
|
||||
|
||||
### 1.4 — Save
|
||||
|
||||
The Data Asset is ready. No other config needed for a simple consumable.
|
||||
|
||||
---
|
||||
|
||||
## 2. Create the BP_Pickup_MedKit
|
||||
|
||||
Follow the same pattern as the flashlight pickup, but simpler — no active-use actor needed.
|
||||
|
||||
### 2.1 — Create Blueprint
|
||||
|
||||
```
|
||||
Content Browser → Game/Pickups/
|
||||
Right-click → Blueprint Class → Actor
|
||||
Name: BP_Pickup_MedKit
|
||||
```
|
||||
|
||||
### 2.2 — Add Components
|
||||
|
||||
| # | Component | Name | Purpose |
|
||||
|---|-----------|------|---------|
|
||||
| 1 | `StaticMeshComponent` | `Mesh` | Renders the medkit model |
|
||||
| 2 | `SphereComponent` | `PickupTrigger` | Interaction radius (Sphere Radius → 150) |
|
||||
|
||||
Select `PickupTrigger` → Details → Collision Presets → `OverlapOnlyPawn`
|
||||
|
||||
### 2.3 — Create ItemData Variable
|
||||
|
||||
**+ Variable** → Name: `ItemData` → Type: `DA_ItemData → Object Reference`
|
||||
|
||||
Select the variable → Details:
|
||||
- **Instance Editable** → ✓
|
||||
- **Category** → `Pickup`
|
||||
|
||||
### 2.4 — Wire Construction Script
|
||||
|
||||
```
|
||||
[Construction Script]
|
||||
│
|
||||
├─ ItemData → Is Valid? → Branch
|
||||
│ ├─ True:
|
||||
│ │ ├─ ItemData → Get World Mesh → Load Synchronous → Set Static Mesh (Mesh)
|
||||
│ │ └─ ItemData → Get Display Name → Set Actor Label
|
||||
│ └─ False:
|
||||
│ └─ Print String ("No ItemData assigned to BP_Pickup_MedKit")
|
||||
```
|
||||
|
||||
### 2.5 — Add I_Interactable and Wire
|
||||
|
||||
Same pattern as flashlight. The key difference for consumables is **stacking**: the pickup can hold multiple units.
|
||||
|
||||
```
|
||||
Event Interact
|
||||
│ Interactor: AActor*
|
||||
│
|
||||
├─ Interactor → Get Component by Class (UBPC_InventorySystem) → Is Valid?
|
||||
│ │
|
||||
│ └─ True → [Inventory]
|
||||
│ │
|
||||
│ ├─ Can Add Item(ItemData, Quantity=1)
|
||||
│ │ │
|
||||
│ │ ├─ True → Add Item(ItemData, Quantity=1) → Destroy Actor → Return True
|
||||
│ │ │
|
||||
│ │ └─ False:
|
||||
│ │ ├─ Print "Inventory Full"
|
||||
│ │ └─ Return False
|
||||
```
|
||||
|
||||
> For multi-pickup stacks (e.g., picking up 3 medkits at once), set a `Quantity` variable (Integer, Instance Editable) and pass it to `Add Item`. The inventory auto-stacks identical items.
|
||||
|
||||
### 2.6 — Add Bobbing (optional)
|
||||
|
||||
Add a Timeline to `Event BeginPlay` with a sine wave for Z offset — same as flashlight example.
|
||||
|
||||
---
|
||||
|
||||
## 3. Wire Consumable Use (On Player Pawn)
|
||||
|
||||
When the player uses a consumable from their inventory or quick-slot:
|
||||
|
||||
### 3.1 — In BPC_ConsumableSystem (BP child on pawn)
|
||||
|
||||
```
|
||||
Event Use Consumable (custom event)
|
||||
│ ItemData: DA_ItemData*
|
||||
│
|
||||
├─ ItemData → Get Consumable Data (Break FItemConsumableData)
|
||||
│ │
|
||||
│ ├─ Health Restore > 0?
|
||||
│ │ └─ True:
|
||||
│ │ ├─ Get Owner → Get Component by Class (UBPC_HealthSystem)
|
||||
│ │ │ │
|
||||
│ │ │ └─ Is Valid? → True:
|
||||
│ │ │ └─ Apply Healing (HealthRestore, Healer=self)
|
||||
│ │ │ │
|
||||
│ │ │ └─ Print String: "Restored {HealthRestore} HP"
|
||||
│ │ │
|
||||
│ │ └─ False: Print "No HealthSystem found"
|
||||
│ │
|
||||
│ ├─ Stress Reduce > 0?
|
||||
│ │ └─ True:
|
||||
│ │ └─ Get BPC_StressSystem → Remove Stress (StressReduce)
|
||||
│ │
|
||||
│ ├─ bConsumedOnUse?
|
||||
│ │ └─ True:
|
||||
│ │ └─ Get BPC_InventorySystem → Remove Item (ItemData, 1)
|
||||
│ │
|
||||
│ └─ (Optional) Play sound, screen effect, widget animation
|
||||
```
|
||||
|
||||
### 3.2 — Quick Test Without Full Integration
|
||||
|
||||
Bind a test key (e.g., `IA_UseItem`) in your player character:
|
||||
|
||||
```
|
||||
Event InputAction (IA_UseItem, Pressed)
|
||||
│
|
||||
├─ Get Component by Class (UBPC_InventorySystem)
|
||||
├─ Get All Items → For Each Loop
|
||||
│ │
|
||||
│ ├─ Break FInventorySlot → Item
|
||||
│ ├─ Item → Get Item Type == Consumable?
|
||||
│ │ └─ True:
|
||||
│ │ ├─ Item → Get Consumable Data → Health Restore
|
||||
│ │ ├─ Get BPC_HealthSystem → Apply Healing (HealthRestore)
|
||||
│ │ ├─ Inventory → Remove Item (Item, 1)
|
||||
│ │ └─ Break (exit loop — only use one)
|
||||
│ │
|
||||
│ └─ Continue loop
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Verification Checklist
|
||||
|
||||
- [ ] **Data Asset:** `DA_Item_MedKit` has `ItemType = Consumable`, `HealthRestore = 25`, `StackLimit = 5`
|
||||
- [ ] **Pickup in level:** Mesh appears, bobs, interaction prompt shows "Pick up MedKit"
|
||||
- [ ] **Inventory:** Pick up medkit → appears in inventory slot. Pick up 4 more → they stack (quantity = 5). Pick up 6th → fails (stack limit).
|
||||
- [ ] **Use:** Press use item key → health increases by 25, stack count decreases by 1
|
||||
- [ ] **Weight:** Each medkit adds 1.0 to carry weight. `Get Remaining Weight` decreases by 1.0 per pickup.
|
||||
- [ ] **Full inventory:** Pick up medkit when no slots left → "Inventory Full" feedback, pickup remains in world
|
||||
|
||||
---
|
||||
|
||||
## 5. Key Concepts
|
||||
|
||||
| Concept | How It Works |
|
||||
|---------|-------------|
|
||||
| **Stacking** | `StackLimit = 5` on the Data Asset. `BPC_InventorySystem.AddItem()` auto-finds existing stacks and adds to them instead of creating new slots. |
|
||||
| **Consumed on use** | `bConsumedOnUse = true` → `BPC_ConsumableSystem` calls `RemoveItem()` after applying effects. Set to `false` for reusable items (e.g., a reusable syringe with cooldown). |
|
||||
| **Soft references** | `WorldMesh` is a `TSoftObjectPtr<UStaticMesh>` — it doesn't load into memory until Construction Script resolves it. This keeps memory low when assets aren't in use. |
|
||||
| **Inventory weight** | `BPC_InventorySystem.CurrentWeight` auto-updates when items are added/removed. `CanAddItem()` checks weight capacity before pickup. |
|
||||
379
docs/game/item-pistol.md
Normal file
379
docs/game/item-pistol.md
Normal file
@@ -0,0 +1,379 @@
|
||||
# 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 |
|
||||
Reference in New Issue
Block a user