feat: Enhance interaction and inventory systems with new components and functionality
- Added BPC_UsableWorldObjectSystem for handling various interactable world objects with detailed manual implementation guide. - Introduced BPC_ActiveItemSystem to manage quick slots and active item usage, including cycling and selection logic. - Implemented BPC_DocumentArchiveSystem for managing collectible documents with read tracking and categorization. - Developed BPC_JournalSystem for narrative entries with auto-adding features based on gameplay events. - Created BPC_KeyItemSystem for key management with consumable and persistent key support. - Enhanced BPC_FirearmSystem for ranged weapon mechanics, including hitscan and projectile firing. - Updated BPC_MeleeSystem for melee combat with combo and blocking mechanics. - Established BPC_ReloadSystem for managing weapon reloading processes, including partial reloads and state management.
This commit is contained in:
@@ -83,4 +83,148 @@
|
||||
|
||||
## 7. Reuse Notes
|
||||
- Quick slots are configurable per project (4-8 slots)
|
||||
- Active item system is the bridge between inventory data and gameplay input
|
||||
- Active item system is the bridge between inventory data and gameplay input
|
||||
|
||||
---
|
||||
|
||||
## 8. Manual Implementation Guide
|
||||
|
||||
### 8.1 Class Setup
|
||||
1. Create Blueprint Class: Parent = `ActorComponent`, Name = `BPC_ActiveItemSystem`
|
||||
2. Add to Player Character
|
||||
3. Quick slots map inventory ItemIDs (Guid) to hotkey numbers (1-8)
|
||||
|
||||
### 8.2 Variable Initialization (BeginPlay)
|
||||
```
|
||||
Event BeginPlay
|
||||
├─ Set QuickSlots = empty Map<E_QuickSlot, Guid>
|
||||
├─ Set ActiveSlot = Slot_1
|
||||
├─ Set ActiveItem = empty S_InventoryEntry
|
||||
├─ Set bHasActiveItem = false
|
||||
├─ Set bCanSwitchItems = true
|
||||
├─ Set SwitchCooldown = 0.2
|
||||
└─ Get Owner → Find Component by Class (BPC_InventorySystem) → Cache
|
||||
└─ Bind to OnInventoryChanged → RefreshActiveItem
|
||||
```
|
||||
|
||||
### 8.3 Function Implementations
|
||||
|
||||
#### `SetQuickSlot(Slot: E_QuickSlot, ItemID: Guid)` → `void`
|
||||
|
||||
```
|
||||
[Function: SetQuickSlot]
|
||||
Step 1: Add/Update QuickSlots map: QuickSlots.Add(Slot, ItemID)
|
||||
Step 2: If ActiveSlot == Slot → Refresh active item display
|
||||
Step 3: Fire OnQuickSlotAssigned(Slot, ItemID)
|
||||
```
|
||||
|
||||
#### `ClearQuickSlot(Slot: E_QuickSlot)` → `void`
|
||||
|
||||
```
|
||||
[Function: ClearQuickSlot]
|
||||
Step 1: QuickSlots.Remove(Slot)
|
||||
Step 2: If ActiveSlot == Slot:
|
||||
Set bHasActiveItem = false, ActiveItem = empty
|
||||
Fire OnActiveItemChanged(empty, Slot)
|
||||
Step 3: Fire OnQuickSlotCleared(Slot)
|
||||
```
|
||||
|
||||
#### `SelectSlot(Slot: E_QuickSlot)` → `void`
|
||||
|
||||
```
|
||||
[Function: SelectSlot]
|
||||
Step 1: Branch on bCanSwitchItems → If false, return (switching blocked during animation)
|
||||
Step 2: Get ItemID from QuickSlots[Slot] → if no entry, return
|
||||
Step 3: Get InventorySystem → Call GetItemById(ItemID)
|
||||
Step 4: If item found AND valid:
|
||||
Set ActiveSlot = Slot
|
||||
Set ActiveItem = found entry
|
||||
Set bHasActiveItem = true
|
||||
Fire OnActiveItemChanged(ActiveItem, Slot)
|
||||
Step 5: If item NOT found (was removed from inventory):
|
||||
Call ClearQuickSlot(Slot) — auto-clean stale slot
|
||||
```
|
||||
|
||||
**Nodes:** `Map Find`, `GetItemById`, `Branch IsValid`, `Fire Event`
|
||||
|
||||
#### `GetActiveItem()` → `S_InventoryEntry` *(Pure)*
|
||||
|
||||
```
|
||||
[Function: GetActiveItem]
|
||||
Return ActiveItem
|
||||
```
|
||||
|
||||
#### `UseActiveItem()` → `Boolean`
|
||||
|
||||
```
|
||||
[Function: UseActiveItem]
|
||||
Step 1: Branch on bHasActiveItem → If false, return false
|
||||
Step 2: Get InventorySystem → Call UseItem(ActiveItem.SlotIndex)
|
||||
Step 3: Branch on result == Success:
|
||||
True → Fire OnActiveItemUsed(ActiveItem) → Return true
|
||||
False → Return false (item not usable, cooldown, etc.)
|
||||
```
|
||||
|
||||
#### `CycleNextItem()` → `void`
|
||||
|
||||
```
|
||||
[Function: CycleNextItem]
|
||||
Step 1: CurrentIndex = ActiveSlot (as int, 1-8)
|
||||
Step 2: Loop 8 times:
|
||||
NextIndex = (CurrentIndex + i) % 8 + 1
|
||||
If NOT IsQuickSlotEmpty(NextIndex):
|
||||
Call SelectSlot(NextIndex)
|
||||
Return
|
||||
Step 3: No populated slots → do nothing
|
||||
```
|
||||
|
||||
#### `CyclePreviousItem()` → `void`
|
||||
|
||||
```
|
||||
[Function: CyclePreviousItem]
|
||||
Same as CycleNextItem but decrementing: NextIndex = (CurrentIndex - i + 7) % 8 + 1
|
||||
```
|
||||
|
||||
#### `IsQuickSlotEmpty(Slot: E_QuickSlot)` → `Boolean`
|
||||
|
||||
```
|
||||
[Function: IsQuickSlotEmpty]
|
||||
If QuickSlots.Find(Slot) returns valid → Return false
|
||||
Return true
|
||||
```
|
||||
|
||||
#### `RefreshActiveItem()` → `void` *(Called when inventory changes)*
|
||||
|
||||
```
|
||||
[Function: RefreshActiveItem]
|
||||
Step 1: If NOT bHasActiveItem → Return
|
||||
Step 2: Get InventorySystem → GetItemAtSlot(ActiveItem.SlotIndex)
|
||||
Step 3: If item still exists AND same ItemID:
|
||||
Update ActiveItem (stack count may have changed)
|
||||
Fire OnActiveItemChanged(ActiveItem, ActiveSlot)
|
||||
Step 4: If item no longer exists:
|
||||
Set bHasActiveItem = false, ActiveItem = empty
|
||||
ClearQuickSlot(ActiveSlot)
|
||||
```
|
||||
|
||||
### 8.4 Event Dispatcher Bindings
|
||||
| Bind to Dispatcher | Custom Event | Logic |
|
||||
|-------------------|-------------|-------|
|
||||
| `BPC_InventorySystem.OnInventoryChanged` | `RefreshActiveItem` | Re-validate current active item exists |
|
||||
| `InputAction IA_Hotkey1` through `IA_Hotkey8` | `OnHotkeyPressed(Slot)` | Call SelectSlot corresponding slot |
|
||||
| `InputAction IA_NextItem` | `OnNextItem` | Call CycleNextItem |
|
||||
| `InputAction IA_PreviousItem` | `OnPreviousItem` | Call CyclePreviousItem |
|
||||
| `InputAction IA_UseItem` | `OnUseItem` | Call UseActiveItem |
|
||||
|
||||
### 8.5 Blueprint Build Checklist
|
||||
- [ ] Define E_QuickSlot enum (Slot_1 through Slot_8)
|
||||
- [ ] Create BPC_ActiveItemSystem, add to Player Character
|
||||
- [ ] Add variables: QuickSlots (Map), ActiveSlot, ActiveItem, bHasActiveItem, bCanSwitchItems, SwitchCooldown
|
||||
- [ ] Implement SetQuickSlot/ClearQuickSlot for assignment
|
||||
- [ ] Implement SelectSlot with validity check and stale cleanup
|
||||
- [ ] Implement UseActiveItem routing to InventorySystem.UseItem
|
||||
- [ ] Implement CycleNext/Previous with wrap-around
|
||||
- [ ] Create 8 Input Actions: IA_Hotkey1 through IA_Hotkey8
|
||||
- [ ] Bind hotkey inputs + next/previous item cycling
|
||||
- [ ] Bind to OnInventoryChanged for auto-refresh
|
||||
- [ ] Test: assign item to slot 1 → press 1 to select → press Use to consume
|
||||
@@ -46,4 +46,129 @@
|
||||
| `BPC_LoreUnlockSystem` | Lore document triggers |
|
||||
|
||||
## 6. Reuse Notes
|
||||
- Documents are inventory items with `E_ItemCategory = Document`
|
||||
- Documents are inventory items with `E_ItemCategory = Document`
|
||||
|
||||
---
|
||||
|
||||
## 7. Manual Implementation Guide
|
||||
|
||||
### 7.1 Class Setup
|
||||
1. Create Blueprint Class: Parent = `ActorComponent`, Name = `BPC_DocumentArchiveSystem`
|
||||
2. Add to Player Character
|
||||
3. Define struct `S_DocumentEntry` with fields: `DocumentID` (Guid), `DocumentTag` (GameplayTag), `Title` (Text), `Body` (Text), `Category` (Enum), `bIsRead` (Boolean), `DateCollected` (Float), `bIsFlagged` (Boolean)
|
||||
|
||||
### 7.2 Variable Initialization (BeginPlay)
|
||||
```
|
||||
Event BeginPlay
|
||||
├─ Set CollectedDocuments = empty Array<S_DocumentEntry>
|
||||
├─ Set UnreadCount = 0
|
||||
├─ Set bShowUnreadBadge = true
|
||||
├─ Get Owner → Find Component by Class (BPC_InventorySystem) → Cache
|
||||
│ └─ Bind to OnItemAdded → OnItemAddedHandler
|
||||
└─ Get Owner → Find BP_DocumentArchiveSystem → self (ensure only one)
|
||||
```
|
||||
|
||||
### 7.3 Function Implementations
|
||||
|
||||
#### `AddDocument(ItemData: DA_ItemData)` → `Boolean`
|
||||
|
||||
```
|
||||
[Function: AddDocument]
|
||||
Step 1: Branch on ItemData.ItemType == Document → If not, return false
|
||||
Step 2: Check if document with same ItemTag already in CollectedDocuments:
|
||||
ForEach CollectedDocuments → if Entry.DocumentTag == ItemData.ItemTag:
|
||||
Return false (already collected)
|
||||
Step 3: Create S_DocumentEntry struct:
|
||||
- DocumentID = New Guid
|
||||
- DocumentTag = ItemData.ItemTag
|
||||
- Title = ItemData.DisplayName
|
||||
- Body = ItemData.Description (document content)
|
||||
- Category = determine from tag (ItemTag starts with "Document.Notes" → Notes, etc.)
|
||||
- bIsRead = false
|
||||
- DateCollected = Get Game Time in Seconds
|
||||
- bIsFlagged = false
|
||||
Step 4: Add to CollectedDocuments array
|
||||
Step 5: UnreadCount += 1
|
||||
Step 6: Fire OnDocumentCollected(NewEntry)
|
||||
Step 7: Also notify BPC_LoreUnlockSystem if category is Lore
|
||||
Step 8: Return true
|
||||
```
|
||||
|
||||
**Nodes:** `New Guid`, `Make S_DocumentEntry`, `Add`, `Get Game Time in Seconds`, `Starts With (String)`
|
||||
|
||||
#### `MarkAsRead(DocumentID: Guid)` → `void`
|
||||
|
||||
```
|
||||
[Function: MarkAsRead]
|
||||
Step 1: Find document in CollectedDocuments by DocumentID:
|
||||
ForEach with Break → if Entry.DocumentID == DocumentID:
|
||||
Branch on Entry.bIsRead:
|
||||
False → Set bIsRead = true, UnreadCount -= 1
|
||||
True → Return (already read)
|
||||
Step 2: Fire OnDocumentRead(DocumentID)
|
||||
Step 3: Branch on UnreadCount == 0:
|
||||
True → Fire OnAllDocumentsRead
|
||||
```
|
||||
|
||||
#### `MarkAllAsRead()` → `void`
|
||||
|
||||
```
|
||||
[Function: MarkAllAsRead]
|
||||
Step 1: ForEach CollectedDocuments:
|
||||
Set bIsRead = true
|
||||
Step 2: Set UnreadCount = 0
|
||||
Step 3: Fire OnAllDocumentsRead
|
||||
```
|
||||
|
||||
#### `GetDocumentsByCategory(Category: E_DocumentCategory, bUnreadOnly: Boolean)` → `Array<S_DocumentEntry>`
|
||||
|
||||
```
|
||||
[Function: GetDocumentsByCategory] (Pure)
|
||||
Step 1: Create empty Array → Results
|
||||
Step 2: ForEach CollectedDocuments:
|
||||
If Entry.Category == Category AND (NOT bUnreadOnly OR NOT Entry.bIsRead):
|
||||
Add to Results
|
||||
Step 3: Return Results
|
||||
```
|
||||
|
||||
#### `GetUnreadCount()` → `Integer`
|
||||
|
||||
```
|
||||
[Function: GetUnreadCount] (Pure)
|
||||
Return UnreadCount
|
||||
```
|
||||
|
||||
#### `HasDocument(DocumentTag: GameplayTag)` → `Boolean`
|
||||
|
||||
```
|
||||
[Function: HasDocument] (Pure)
|
||||
Step 1: ForEach CollectedDocuments:
|
||||
If Entry.DocumentTag == DocumentTag → Return true
|
||||
Step 2: Return false
|
||||
```
|
||||
|
||||
#### `ToggleFlag(DocumentID: Guid)` → `void`
|
||||
|
||||
```
|
||||
[Function: ToggleFlag]
|
||||
Step 1: Find document by DocumentID in CollectedDocuments
|
||||
Step 2: Entry.bIsFlagged = NOT Entry.bIsFlagged
|
||||
Step 3: If flagged → move to top of array (optional sorting)
|
||||
```
|
||||
|
||||
### 7.4 Event Dispatcher Bindings
|
||||
| Bind to Dispatcher | Custom Event | Logic |
|
||||
|-------------------|-------------|-------|
|
||||
| `BPC_InventorySystem.OnItemAdded(Item)` | `OnItemAddedHandler` | If Item.ItemData.ItemType == Document → Call AddDocument(Item.ItemData) |
|
||||
|
||||
### 7.5 Blueprint Build Checklist
|
||||
- [ ] Define enum `E_DocumentCategory`: Notes, Letters, Audio, Data, Photos
|
||||
- [ ] Define struct `S_DocumentEntry` with all fields
|
||||
- [ ] Create BPC_DocumentArchiveSystem, add to Player Character
|
||||
- [ ] Add variables: CollectedDocuments, DocumentCategories, bShowUnreadBadge, UnreadCount
|
||||
- [ ] Implement AddDocument with duplicate check and struct creation
|
||||
- [ ] Implement MarkAsRead with UnreadCount tracking
|
||||
- [ ] Implement GetDocumentsByCategory with optional unread filter
|
||||
- [ ] Implement HasDocument for narrative queries
|
||||
- [ ] Bind to BPC_InventorySystem.OnItemAdded
|
||||
- [ ] Test: pick up document item → archive updates → UI shows badge
|
||||
@@ -43,4 +43,119 @@
|
||||
| `WBP_JournalDocumentViewer` | UI display |
|
||||
|
||||
## 6. Reuse Notes
|
||||
- Journal entries are narrative-gated via gameplay tags
|
||||
- Journal entries are narrative-gated via gameplay tags
|
||||
|
||||
---
|
||||
|
||||
## 7. Manual Implementation Guide
|
||||
|
||||
### 7.1 Class Setup
|
||||
1. Create Blueprint Class: Parent = `ActorComponent`, Name = `BPC_JournalSystem`
|
||||
2. Add to Player Character
|
||||
3. Define enum `E_JournalCategory`: Quest, Character, Lore, Notes, Tips
|
||||
4. Define struct `S_JournalEntry`: `EntryID` (Guid), `EntryTag` (GameplayTag), `Title` (Text), `Body` (Text), `Category` (E_JournalCategory), `bIsRead` (Boolean), `Timestamp` (Float), `RequiredFlag` (GameplayTag), `SourceObjective` (GameplayTag)
|
||||
|
||||
### 7.2 Variable Initialization (BeginPlay)
|
||||
```
|
||||
Event BeginPlay
|
||||
├─ Set JournalEntries = empty Array<S_JournalEntry>
|
||||
├─ Set UnreadCount = 0
|
||||
├─ Set bAutoAddOnDiscovery = true
|
||||
├─ Get Owner → Find Component by Class (BPC_NarrativeStateSystem) → Cache
|
||||
│ └─ Bind to OnNarrativeFlagSet → OnFlagSetHandler
|
||||
└─ Get Owner → Find Component by Class (BPC_ObjectiveSystem) → Cache
|
||||
└─ Bind to OnObjectiveCompleted → OnObjectiveCompleteHandler
|
||||
```
|
||||
|
||||
### 7.3 Function Implementations
|
||||
|
||||
#### `AddJournalEntry(EntryTag: GameplayTag, Title: Text, Body: Text, Category: E_JournalCategory)` → `S_JournalEntry`
|
||||
|
||||
```
|
||||
[Function: AddJournalEntry]
|
||||
Step 1: Check duplicate — ForEach JournalEntries:
|
||||
If Entry.EntryTag == EntryTag → return existing entry (no dupes)
|
||||
Step 2: Create S_JournalEntry:
|
||||
- EntryID = New Guid
|
||||
- EntryTag = input
|
||||
- Title = input
|
||||
- Body = input
|
||||
- Category = input
|
||||
- bIsRead = false
|
||||
- Timestamp = Get Game Time in Seconds
|
||||
Step 3: Add to JournalEntries array
|
||||
Step 4: Increment UnreadCount
|
||||
Step 5: Fire OnJournalEntryAdded(newEntry)
|
||||
Step 6: Return newEntry
|
||||
```
|
||||
|
||||
**Nodes:** `New Guid`, `Make S_JournalEntry`, `Add to Array`, `Increment Int`
|
||||
|
||||
#### `AddEntryFromDataAsset(JournalData: DA_JournalData)` → `S_JournalEntry`
|
||||
|
||||
```
|
||||
[Function: AddEntryFromDataAsset]
|
||||
Step 1: Read fields from DA_JournalData
|
||||
Step 2: Call AddJournalEntry(Data.EntryTag, Data.Title, Data.Body, Data.Category)
|
||||
Step 3: If Data.RequiredFlag is valid AND bAutoAddOnDiscovery:
|
||||
Register listener on NarrativeStateSystem for that flag
|
||||
Step 4: Return entry
|
||||
```
|
||||
|
||||
#### `MarkAsRead(EntryID: Guid)` → `void`
|
||||
|
||||
```
|
||||
[Function: MarkAsRead]
|
||||
Step 1: ForEach JournalEntries (with Break):
|
||||
If Entry.EntryID == EntryID:
|
||||
If NOT Entry.bIsRead:
|
||||
Set bIsRead = true
|
||||
Decrement UnreadCount
|
||||
Fire OnJournalEntryRead(EntryID)
|
||||
Return
|
||||
```
|
||||
|
||||
#### `GetEntriesByCategory(Category: E_JournalCategory)` → `Array<S_JournalEntry>`
|
||||
|
||||
```
|
||||
[Function: GetEntriesByCategory] (Pure)
|
||||
Step 1: Results = empty Array
|
||||
Step 2: ForEach JournalEntries:
|
||||
If Entry.Category == Category → Add to Results
|
||||
Step 3: Return Results
|
||||
```
|
||||
|
||||
#### `GetUnreadEntries()` → `Array<S_JournalEntry>`
|
||||
|
||||
```
|
||||
[Function: GetUnreadEntries] (Pure)
|
||||
ForEach JournalEntries:
|
||||
If NOT Entry.bIsRead → Add to Results
|
||||
Return Results
|
||||
```
|
||||
|
||||
#### `HasEntry(EntryTag: GameplayTag)` → `Boolean`
|
||||
|
||||
```
|
||||
[Function: HasEntry] (Pure)
|
||||
ForEach JournalEntries:
|
||||
If Entry.EntryTag == EntryTag → Return true
|
||||
Return false
|
||||
```
|
||||
|
||||
### 7.4 Event Dispatcher Bindings
|
||||
| Bind to Dispatcher | Custom Event | Logic |
|
||||
|-------------------|-------------|-------|
|
||||
| `BPC_NarrativeStateSystem.OnNarrativeFlagSet(Flag)` | `OnFlagSetHandler` | Check if any journal entry has RequiredFlag == Flag → AddJournalEntry for that entry |
|
||||
| `BPC_ObjectiveSystem.OnObjectiveCompleted(ObjectiveTag)` | `OnObjectiveCompleteHandler` | Add journal entry summarizing completed objective |
|
||||
|
||||
### 7.5 Blueprint Build Checklist
|
||||
- [ ] Define enum `E_JournalCategory` and struct `S_JournalEntry`
|
||||
- [ ] Create BPC_JournalSystem, add to Player Character
|
||||
- [ ] Add variables: JournalEntries, EntryCategories, UnreadCount, bAutoAddOnDiscovery
|
||||
- [ ] Implement AddJournalEntry with duplicate check
|
||||
- [ ] Implement AddEntryFromDataAsset with DA_JournalData reading
|
||||
- [ ] Implement MarkAsRead with unread counter
|
||||
- [ ] Implement filtered query functions
|
||||
- [ ] Bind to NarrativeStateSystem and ObjectiveSystem for auto-adding
|
||||
- [ ] Test: progress story → journal entries appear → UI shows unread badge
|
||||
@@ -46,4 +46,120 @@
|
||||
|
||||
## 6. Reuse Notes
|
||||
- Keys use gameplay tags for generic matching (any key with same tag works)
|
||||
- Supports consumable keys (removed after single use) and persistent keys
|
||||
- Supports consumable keys (removed after single use) and persistent keys
|
||||
|
||||
---
|
||||
|
||||
## 7. Manual Implementation Guide
|
||||
|
||||
### 7.1 Class Setup
|
||||
1. Create Blueprint Class: Parent = `ActorComponent`, Name = `BPC_KeyItemSystem`
|
||||
2. Add to Player Character
|
||||
3. Keys are inventory items with `ItemType = KeyItem` and `bIsKeyItem = true` in DA_ItemData.
|
||||
|
||||
### 7.2 Variable Initialization (BeginPlay)
|
||||
```
|
||||
Event BeginPlay
|
||||
├─ Set AcquiredKeys = empty Array<GameplayTag>
|
||||
├─ Set bShowKeyNotifications = true
|
||||
├─ Get Owner → Find Component by Class (BPC_InventorySystem) → Cache
|
||||
│ └─ Bind to OnItemAdded → OnItemAddedHandler
|
||||
└─ Bind to OnItemRemoved → OnItemRemovedHandler
|
||||
```
|
||||
|
||||
### 7.3 Function Implementations
|
||||
|
||||
#### `HasKey(KeyTag: GameplayTag)` → `Boolean`
|
||||
|
||||
```
|
||||
[Function: HasKey] (Pure)
|
||||
Step 1: ForEach AcquiredKeys:
|
||||
If AcquiredKeys[i].MatchesTag(KeyTag) → Return true
|
||||
Step 2: Optionally check inventory directly:
|
||||
Get InventorySystem → Call FindItemByTag(KeyTag)
|
||||
If found → Return true
|
||||
Step 3: Return false
|
||||
```
|
||||
|
||||
**Nodes:** `ForEachLoop`, `Matches Tag`, `Branch`, `FindItemByTag`
|
||||
|
||||
#### `AddKey(KeyTag: GameplayTag)` → `void`
|
||||
|
||||
```
|
||||
[Function: AddKey]
|
||||
Step 1: Check if tag already in AcquiredKeys:
|
||||
ForEach → if MatchesTag(KeyTag) → Return (no duplicates)
|
||||
Step 2: Add KeyTag to AcquiredKeys array
|
||||
Step 3: If bShowKeyNotifications:
|
||||
Get WBP_HUDController → Create Notification Toast: "Key acquired: {GetTagDisplayName(KeyTag)}"
|
||||
Step 4: Fire OnKeyAcquired(KeyTag)
|
||||
```
|
||||
|
||||
#### `RemoveKey(KeyTag: GameplayTag)` → `Boolean`
|
||||
|
||||
```
|
||||
[Function: RemoveKey]
|
||||
Step 1: ForEach AcquiredKeys (iterate backwards):
|
||||
If AcquiredKeys[i].MatchesTag(KeyTag):
|
||||
Remove from array at index i
|
||||
Fire OnKeyRemoved(KeyTag)
|
||||
Return true
|
||||
Step 2: Return false (key not found)
|
||||
```
|
||||
|
||||
#### `GetAllKeys()` → `Array<GameplayTag>`
|
||||
|
||||
```
|
||||
[Function: GetAllKeys] (Pure)
|
||||
Step 1: Get BPC_InventorySystem → Call FindAllItemsByCategory(KeyItem)
|
||||
Step 2: Also include AcquiredKeys (permanent keys that were consumed)
|
||||
Step 3: Return combined unique list
|
||||
```
|
||||
|
||||
#### `CanUnlock(Target: Actor)` → `Boolean, Text (failReason)`
|
||||
|
||||
```
|
||||
[Function: CanUnlock]
|
||||
Step 1: Check if Target implements I_Lockable:
|
||||
DoesImplementInterface → If not, return (false, "Cannot be unlocked")
|
||||
Step 2: Get RequiredKeyTag from Target:
|
||||
Call I_Lockable.GetRequiredKeyTag(Target) → RequiredTag
|
||||
Step 3: Call HasKey(RequiredTag):
|
||||
True → Return (true, "")
|
||||
False → Return (false, "Requires: " + GetTagDisplayName(RequiredTag))
|
||||
```
|
||||
|
||||
#### `UseKeyOnTarget(KeyTag: GameplayTag, Target: Actor)` → `Boolean`
|
||||
|
||||
```
|
||||
[Function: UseKeyOnTarget]
|
||||
Step 1: Verify HasKey(KeyTag) → If false, return false
|
||||
Step 2: Call CanUnlock(Target):
|
||||
If false → return false
|
||||
Step 3: Check if Target has I_Lockable.TryUnlock:
|
||||
Call I_Lockable.TryUnlock(Target, KeyTag) → bSuccess
|
||||
Step 4: If bSuccess:
|
||||
Fire OnKeyUsed(KeyTag, Target)
|
||||
Step 5: Check if key should be consumed:
|
||||
Get InventorySystem → FindItemByTag(KeyTag)
|
||||
If ItemData.bConsumeOnUse:
|
||||
Call RemoveKey(KeyTag)
|
||||
Get InventorySystem → RemoveItemByTag(KeyTag, 1)
|
||||
Step 6: Return bSuccess
|
||||
```
|
||||
|
||||
### 7.4 Event Dispatcher Bindings
|
||||
| Bind to Dispatcher | Custom Event | Logic |
|
||||
|-------------------|-------------|-------|
|
||||
| `BPC_InventorySystem.OnItemAdded(Item)` | `OnItemAddedHandler` | If Item.ItemData.ItemType == KeyItem → Call AddKey(Item.ItemData.ItemTag) |
|
||||
| `BPC_InventorySystem.OnItemRemoved(Item)` | `OnItemRemovedHandler` | If Item.ItemData.ItemType == KeyItem → Call RemoveKey(Item.ItemData.ItemTag) |
|
||||
|
||||
### 7.5 Blueprint Build Checklist
|
||||
- [ ] Create BPC_KeyItemSystem, add to Player Character
|
||||
- [ ] Add variables: AcquiredKeys (Array<GameplayTag>), bShowKeyNotifications, KeyItemCategory
|
||||
- [ ] Implement HasKey with tag matching and inventory fallback
|
||||
- [ ] Implement AddKey/RemoveKey with duplicate prevention
|
||||
- [ ] Implement CanUnlock using I_Lockable interface
|
||||
- [ ] Implement UseKeyOnTarget with consume-on-use check
|
||||
- [ ] Bind to BPC_InventorySystem.OnItemAdded/OnItemRemoved
|
||||
- [ ] Test: pick up key → attempt locked door → unlock succeeds → key consumed
|
||||
Reference in New Issue
Block a user