Files
UE5-Modular-Game-Framework/docs/game/item-flashlight.md

412 lines
15 KiB
Markdown

# Item Example — Flashlight (Tool)
**Item Type:** `Tool`
**Complexity:** Medium
**Systems Used:** `DA_ItemData`, `BP_ItemPickup`, `I_UsableItem`, `I_Toggleable`, `BPC_InventorySystem`, `BPC_EquipmentSlotSystem`
**What You Learn:** Data Asset setup, pickup actor with soft reference mesh, Construction Script wiring, toggleable active-use item, inventory integration
---
## 1. Create the DA_ItemData
### 1.1 — Create the Data Asset
```
Content Browser → Game/Items/
(create folder "Items" if needed)
Right-click → Miscellaneous → Data Asset
Class: DA_ItemData
Name: DA_Item_Flashlight
```
### 1.2 — Fill Core Properties
Open `DA_Item_Flashlight`:
| Property | Value | Notes |
|----------|-------|-------|
| `Item Tag` | `Framework.Item.Tool.Flashlight` | Must be registered in `DA_GameTagRegistry`. Tag hierarchy: Framework → Item → Tool → Flashlight |
| `Display Name` | "Flashlight" | Player sees this in inventory UI |
| `Description` | "A heavy-duty tactical flashlight. Click to toggle on/off." | Shown in inventory |
| `Icon` | `T_Flashlight_Icon` | Assign any texture; for prototype, use a white square |
| `World Mesh` | `SM_Flashlight` | **Soft reference** — doesn't need to be loaded at startup; Construction Script resolves it |
| `Weight` | `1.5` | Carried weight in your carry limit |
| `Stack Limit` | `1` | Cannot stack multiple flashlights |
| `Item Type` | `Tool` | Determines that `Equipment Data` panel shows |
### 1.3 — Fill Equipment Data
Scroll down — the `Equipment Data` panel appears because `ItemType == Tool`:
| Field | Value | Notes |
|-------|-------|-------|
| `Slot` | `Framework.Equipment.Slot.Tool` | Which equipment slot it goes into |
| `Damage` | `0` | Not a weapon |
| `Fire Rate` | `0` | Not a weapon |
| `Range` | `0` | N/A |
| `Magazine Size` | `0` | N/A |
| `Reload Time` | `0` | N/A |
### 1.4 — Save
File → Save (or Ctrl+S). The Data Asset is ready.
---
## 2. Create the BP_Pickup_Flashlight (World Pickup Actor)
This is the actor the player sees in the level. It reads the Data Asset and represents it in the world.
### 2.1 — Create Blueprint
```
Content Browser → Game/Pickups/
(create folder "Pickups" if needed)
Right-click → Blueprint Class → Actor
Name: BP_Pickup_Flashlight
```
### 2.2 — Add Components
Open `BP_Pickup_Flashlight`**Viewport** tab.
**Add Component** button (top-left of the Components panel):
| Order | Component Class | Name | Purpose |
|-------|----------------|------|---------|
| 1 | `StaticMeshComponent` | `Mesh` | Renders the flashlight 3D model in the world |
| 2 | `SphereComponent` | `PickupTrigger` | Detects when player is close enough to interact |
| 3 | *(Optional)* `PointLightComponent` | `BatteryIndicator` | Small glow showing the pickup has charge |
**Select `PickupTrigger` → Details:**
- `Sphere Radius``200.0`
- **Collision → Collision Presets** → `OverlapOnlyPawn`
**Select `Mesh` → Details:**
- **Collision → Collision Enabled** → `No Collision` (the sphere handles interaction; mesh collision is unnecessary)
Your Components panel should look like:
```
BP_Pickup_Flashlight (self)
├── DefaultSceneRoot
├── Mesh ← StaticMeshComponent
├── PickupTrigger ← SphereComponent (radius 200)
└── BatteryIndicator ← PointLightComponent (optional)
```
### 2.3 — Create the ItemData Variable
In the **My Blueprint** panel (left side):
1. Click **+ Variable** → name: `ItemData` → type: **DA_ItemData → Object Reference**
2. Select the variable → Details:
- **Instance Editable** → ✓
- **Category** → `Pickup`
- **Tooltip** → "The item Data Asset this pickup represents"
This is simpler than using a struct for a single item — you can expand to a struct later if you need more fields.
### 2.4 — Wire the Construction Script
Switch to the **Construction Script** tab (or open the graph). This runs when the blueprint is compiled or when you change `ItemData` in the editor.
```
[Construction Script]
├─ ItemData (drag from My Blueprint → Get ItemData)
├─ Is Valid? (ItemData)
│ │
│ ├─ Branch (Condition = IsValid result)
│ │
│ ├─ True:
│ │ │
│ │ ├─ ItemData → Get World Mesh
│ │ │ This returns a TSoftObjectPtr<UStaticMesh>
│ │ │ │
│ │ │ └─ To Soft Object Reference (conversion node, or just drag from pin)
│ │ │ └─ Resolve Soft Reference (or "Async Load Asset")
│ │ │ │
│ │ │ ├─ On Complete (if async) → Set Static Mesh (Mesh component, New Mesh = loaded asset)
│ │ │ │
│ │ │ └─ For Construction Script, use "Load Synchronous" to see it in editor immediately:
│ │ │ └─ Sync Load Asset (from soft ref) → Set Static Mesh (Mesh)
│ │ │
│ │ ├─ ItemData → Get Display Name → Set Actor Label
│ │ │ (This renames the actor in the world to "Flashlight" so it's easy to find in the World Outliner)
│ │ │
│ │ └─ ItemData → Get Weight → (optional: store for physics if dropped)
│ │
│ └─ False:
│ └─ Print String ("No ItemData assigned to BP_Pickup_Flashlight")
│ └─ Only if Editor (not PIE/standalone)
```
**Exact node sequence:**
| Step | Right-click / Drag | Search | Connect |
|------|-------------------|--------|---------|
| 1 | Drag `ItemData` variable → Get | — | — |
| 2 | Drag from ItemData pin | `Is Valid` | Branch Condition |
| 3 | True branch → drag from ItemData | `Get World Mesh` | — |
| 4 | Drag from World Mesh pin | `Load Synchronous` | — |
| 5 | Drag `Mesh` component from Components → Get | — | Target of Set Static Mesh |
| 6 | Drag from Load Synchronous Return Value | `Set Static Mesh` (New Mesh) | — |
| 7 | Drag from ItemData → `Get Display Name` | — | To String (Auto) |
| 8 | Drag from string result | `Set Actor Label` → New Actor Label | — |
### 2.5 — Add I_Interactable Interface
1. **Class Settings** (toolbar button)
2. **Implemented Interfaces****Add** → search and select `UInteractable`
3. **Compile** (blue checkmark). Now these events appear:
#### Event Interact
```
Event Interact (Interface -> UInteractable)
│ Interactor: AActor*
│ Return Node expects a Boolean
├─ Interactor → Get Component by Class → Class: UBPC_InventorySystem
│ │
│ └─ Is Valid?
│ ├─ False → Return False (no inventory on this actor)
│ │
│ └─ True → [InventorySystem ref]
│ │
│ ├─ ItemData → Is Valid?
│ │ └─ False → Return False
│ │
│ ├─ [InventorySystem] → Can Add Item
│ │ ├─ Item: ItemData
│ │ ├─ Quantity: 1
│ │ └─ Result → Branch
│ │ ├─ True:
│ │ │ ├─ [InventorySystem] → Add Item (Item=ItemData, Quantity=1)
│ │ │ │ → Result (int32, ignore)
│ │ │ ├─ Destroy Actor
│ │ │ └─ Return True
│ │ └─ False:
│ │ ├─ Print String ("Inventory full or cannot carry")
│ │ └─ Return False
```
#### Event Can Interact
```
Event Can Interact (Interface -> UInteractable)
│ Interactor: AActor*
│ OutBlockReason: FText& (by reference)
│ Return Node expects Boolean
├─ ItemData → Is Valid?
│ ├─ False → OutBlockReason = "No item data configured" → Return False
│ └─ True → Return True
```
#### Event Get Interaction Prompt
```
Event Get Interaction Prompt (Interface -> UInteractable)
│ Return Node expects FText
├─ ItemData → Get Display Name
├─ Format Text: "Pick up {0}" (format with DisplayName)
└─ Return the formatted text
```
### 2.6 — Optional: Bobbing & Rotation
In **Event Graph → Event BeginPlay**:
```
Event BeginPlay
├─ Mesh → Set Collision Enabled → No Collision
├─ PickupTrigger → Set Collision Enabled → Query Only
├─ [Create a Timeline named "FloatAnim"]
│ │
│ ├─ Float Track: 0.0 to 1.0, length 2.0 seconds, Looping ✓
│ │
│ └─ Timeline → Update pin:
│ │
│ ├─ Mesh → Add World Rotation
│ │ ├─ Delta Rotation: (0, 0, TimelineOutput * 90.0)
│ │ └─ Sweep: false, Teleport: false
│ │
│ └─ Mesh → Set World Location (interpolation)
│ └─ Z = ActorLocation.Z + sin(TimelineOutput * 2π) * 5.0
│ (Use "Make Vector" → Break Actor Location → modify Z → "Lerp" or direct set)
└─ Play Timeline from Start
```
> For the bobbing math: `sin(TimelineOutput * 6.28318) * 5.0` gives a smooth ±5 unit bounce.
---
## 3. Create the BP_Flashlight (Active-Use Actor — Toggleable)
This is a separate Blueprint for the *held* flashlight — what the player actually uses after picking it up. It implements `I_UsableItem` and `I_Toggleable`.
### 3.1 — Create Blueprint
```
Content Browser → Game/Actors/
Right-click → Blueprint Class → Actor
Name: BP_Flashlight
```
### 3.2 — Add Components
| # | Component | Name | Purpose |
|---|-----------|------|---------|
| 1 | `StaticMeshComponent` | `BodyMesh` | Flashlight body |
| 2 | `SpotLightComponent` | `LightBeam` | The actual light cone |
| 3 | `AudioComponent` | `ClickSound` | Plays the toggle click sound |
Select `LightBeam` → Details:
- `Intensity``5000.0` (bright)
- `Inner Cone Angle``15.0`
- `Outer Cone Angle``30.0`
- **Visible** → false (starts off — player toggles it on)
### 3.3 — Add Toggle State Variable
**+ Variable** → Name: `bIsOn` → Type: `Boolean` → Default: `false`
- **Replication** → `Replicated` (if multiplayer)
### 3.4 — Add Interfaces
Class Settings → Interfaces → Add:
- `UUsableItem` — so the player can select and use it from inventory
- `UToggleable` — so the player can toggle it on/off
### 3.5 — Wire I_Toggleable
```
Event Toggle (Interface -> UToggleable)
│ Toggler: AActor*
├─ bIsOn = NOT bIsOn (FlipFlop or Boolean NOT)
├─ Branch (bIsOn)
│ ├─ True:
│ │ ├─ LightBeam → Set Visibility (true)
│ │ ├─ ClickSound → Play
│ │ └─ (Optional) Play toggle-on animation montage
│ │
│ └─ False:
│ ├─ LightBeam → Set Visibility (false)
│ └─ ClickSound → Play
Event Set State (Interface -> UToggleable)
│ bNewState: Boolean, Setter: AActor*
├─ Set bIsOn = bNewState
├─ LightBeam → Set Visibility (bNewState)
└─ (Don't play click sound — this is programmatic, not player toggle)
Event Get Current State
│ Return Boolean
└─ Return bIsOn
Event Get State Label
│ Return FText
└─ Format Text: "Flashlight is {0}" → format with (bIsOn ? "ON" : "OFF")
```
### 3.6 — Wire I_UsableItem
```
Event Use Item (Interface -> UUsableItem)
│ User: AActor*, Target: AActor*
│ Return Boolean
├─ Call Toggle (self, User) ← reuses the I_Toggleable logic
└─ Return True
Event Can Use Item
│ User, Target
│ Return Boolean
└─ Return True (can always toggle flashlight)
Event Get Use Duration
│ Return Float
└─ Return 0.0 (instant toggle)
Event On Item Used
│ User: AActor*
└─ (Optional) Play a small haptic pulse on controller
```
### 3.7 — Compile & Save
The held flashlight is ready. When a player picks up `BP_Pickup_Flashlight` and the inventory's `BPC_ActiveItemSystem` routes the item to use, it calls `I_UsableItem.UseItem()` on this actor, which toggles the light.
---
## 4. Wire Inventory Integration
### 4.1 — In Your Player Pawn BP
When `BPC_ActiveItemSystem` detects that an item of type `Tool` is used:
1. Get the equipped item from `BPC_EquipmentSlotSystem`
2. Spawn `BP_Flashlight` actor (if not already spawned)
3. Call `I_UsableItem.UseItem()`
4. The flashlight actor handles its own toggle state
### 4.2 — Quick Test Without Full Integration
For a rapid prototype, add this to your **PlayerCharacter's Event Graph**:
```
Event InputAction (IA_UseItem, Pressed)
├─ Branch (bFlashlightEquipped) ← set this bool when flashlight is picked up
│ ├─ True:
│ │ ├─ Get All Actors of Class (BP_Flashlight)
│ │ ├─ For Each → Call I_Toggleable.Toggle
│ │ └─ (In production, use BPC_ActiveItemSystem instead of this crude search)
│ └─ False: do nothing
```
---
## 5. Verification Checklist
**Step 1 — Data Asset:** Open `DA_Item_Flashlight` → all fields filled, no blank properties.
**Step 2 — Pickup in Editor:** Drag `BP_Pickup_Flashlight` into level → set `ItemData` → the mesh appears in viewport immediately (Construction Script).
**Step 3 — Pickup Interaction (PIE):**
- [ ] Walk up to flashlight pickup → interaction prompt appears ("Pick up Flashlight")
- [ ] Press Interact → flashlight disappears from world
- [ ] Check log/print: `BPC_InventorySystem.AddItem` was called
- [ ] In debug: `Get All Items` on inventory → flashlight is in a slot
**Step 4 — Held Use:**
- [ ] Toggle via IA_UseItem → light turns on (visible in world)
- [ ] Toggle again → light turns off
- [ ] Click sound plays on each toggle
**Step 5 — Equipment Slot:**
- [ ] Flashlight occupies `Framework.Equipment.Slot.Tool` slot
- [ ] Cannot equip another tool while flashlight is in slot (slot type validation)
---
## 6. Common Issues
| Issue | Cause | Fix |
|-------|-------|-----|
| Mesh doesn't appear in Construction Script | Soft reference not loaded | Use `Load Synchronous` instead of `Async Load Asset` in Construction Script |
| Pickup doesn't respond to interaction | `UInteractable` interface not added | Class Settings → Interfaces → Add `UInteractable` → Compile |
| "Can Add Item" always returns false | `BPC_InventorySystem.MaxWeight` too low or `GridWidth/Height = 0` | Set MaxWeight to 0 (unlimited) for testing, or increase it |
| Light doesn't toggle | `bIsOn` variable not replicated or `I_Toggleable` not wired | Verify `Toggle` event is called; check `Set Visibility` target is the correct light component |
| "No ItemData configured" error | Forgot to set `ItemData` on the pickup instance in the level | Select the pickup → Details → set ItemData → recompile |