412 lines
15 KiB
Markdown
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 |
|