Compare commits
2 Commits
bc8ab7c48f
...
eeb1bf82c9
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
eeb1bf82c9 | ||
|
|
f272257cb3 |
@@ -6,6 +6,8 @@
|
|||||||
|----------|-------|
|
|----------|-------|
|
||||||
| **Class** | [`FL_GameUtilities`] |
|
| **Class** | [`FL_GameUtilities`] |
|
||||||
| **Parent** | [`BlueprintFunctionLibrary`] |
|
| **Parent** | [`BlueprintFunctionLibrary`] |
|
||||||
|
|
||||||
|
> **⚠️ UE5 BP Limitation:** `BlueprintFunctionLibrary` requires C++ to create. This is the ONE file in the framework that needs a minimal C++ class. See Section "Blueprint-Only Alternative" below for the pure BP workaround using Macro Library + direct engine nodes.
|
||||||
| **Folder** | [`Framework/Core/`] |
|
| **Folder** | [`Framework/Core/`] |
|
||||||
| **Categorization** | [Core\Framework] |
|
| **Categorization** | [Core\Framework] |
|
||||||
|
|
||||||
@@ -88,9 +90,9 @@ This is a pure Function Library. It has **no variables, no event dispatchers, no
|
|||||||
|
|
||||||
| Function | Category | Inputs | Outputs | Description |
|
| Function | Category | Inputs | Outputs | Description |
|
||||||
|----------|----------|--------|---------|-------------|
|
|----------|----------|--------|---------|-------------|
|
||||||
| [`LogDebug`] | Debug\Logging | `Category: Name`, `Message: String`, `Color: LinearColor` | — | Conditional screen + log output. Only fires in editor/development builds. Stripped in shipping builds via `DO_CHECK` flag. |
|
| [`LogDebug`] | Debug\Logging | `Category: Name`, `Message: String`, `Color: LinearColor`, `WorldContext: Object` | — | Conditional screen + log output. Uses `Branch: Is Editor Build` → then Print String + Draw Debug String. Inactive in shipping builds. |
|
||||||
| [`DrawDebugSphere`] | Debug\Drawing | `WorldContext: Object`, `Center: Vector`, `Radius: Float`, `Color: LinearColor`, `Duration: Float` | — | Draws a debug sphere for visual debugging in-editor. Stripped in shipping builds. |
|
| [`DrawDebugSphere`] | Debug\Drawing | `WorldContext: Object`, `Center: Vector`, `Radius: Float`, `Color: LinearColor`, `Duration: Float` | — | Draws a debug sphere. **BP node exists:** `Draw Debug Sphere`. Stripped in shipping. |
|
||||||
| [`DrawDebugString3D`] | Debug\Drawing | `WorldContext: Object`, `Location: Vector`, `Text: String`, `Color: LinearColor`, `Duration: Float` | — | Draws a 3D world-space debug string. Stripped in shipping builds. |
|
| [`DrawDebugString3D`] | Debug\Drawing | `WorldContext: Object`, `Location: Vector`, `Text: String`, `Color: LinearColor`, `Duration: Float` | — | Draws 3D world-space debug string. **BP node exists:** `Draw Debug String`. Stripped in shipping. |
|
||||||
|
|
||||||
## Blueprint Flow
|
## Blueprint Flow
|
||||||
|
|
||||||
@@ -126,18 +128,41 @@ All functions can be called from Construction Scripts since they are pure (no wo
|
|||||||
|
|
||||||
## Reuse Notes
|
## Reuse Notes
|
||||||
|
|
||||||
- This library ships verbatim to every project.
|
- This library ships as a minimal C++ `BlueprintFunctionLibrary` (required for static function libraries in UE5).
|
||||||
- Add project-specific helpers in a separate `FL_ProjectUtilities` child library that references this one.
|
- Add project-specific helpers in a separate child library or Macro Library.
|
||||||
- Do **not** add game-specific functions here — extend with a new Function Library inheriting from this pattern.
|
- Do **not** add game-specific functions here — extend with a new library.
|
||||||
- Debug functions are automatically stripped in Shipping builds via `DO_CHECK` preprocessor — no manual removal needed.
|
- For a 100% Blueprint approach, see "Blueprint-Only Alternative" below.
|
||||||
|
|
||||||
## Success Criteria
|
## Success Criteria
|
||||||
|
|
||||||
1. Any Blueprint in the project can call `RemapFloat` from the Math category without errors.
|
1. Any Blueprint can call `RemapFloat` from the Math category without errors.
|
||||||
2. `GetSubsystemSafe` returns `None` instead of crashing when a subsystem doesn't exist.
|
2. `GetSubsystemSafe` returns `None` instead of crashing when a subsystem doesn't exist.
|
||||||
3. `FormatTime(3661.0)` returns `01:01:01`.
|
3. `FormatTime(3661.0)` returns `01:01:01`.
|
||||||
4. `LogDebug` prints to both the output log and the viewport in editor builds.
|
4. `LogDebug` prints to both the output log and the viewport in editor builds.
|
||||||
5. `LogDebug` produces no output in a shipping build.
|
5. `LogDebug` produces no output in a shipping build (via `Is Editor Build` branch).
|
||||||
|
|
||||||
|
## Blueprint-Only Alternative
|
||||||
|
|
||||||
|
If you cannot compile C++, replace `FL_GameUtilities` with direct Blueprint nodes or a **Blueprint Macro Library**:
|
||||||
|
|
||||||
|
| FL_GameUtilities Function | Pure BP Equivalent (no C++ needed) |
|
||||||
|
|--------------------------|-----------------------------------|
|
||||||
|
| `GetSubsystemSafe` | `Get Game Instance` → `Get Subsystem (Class)` → `Is Valid` branch |
|
||||||
|
| `GetGameFramework` | `Get Game Instance` → `Cast to GI_GameFramework` |
|
||||||
|
| `GetPlayerController` | `Get Player Controller (0)` → `Cast to PC_CoreController` |
|
||||||
|
| `HasGameplayTag` | `Does Actor Have Tag` (BP node) |
|
||||||
|
| `AddGameplayTagToActor` | `Get Actor Gameplay Tag Container` → `Add Tag` |
|
||||||
|
| `RemoveGameplayTagFromActor` | `Get Actor Gameplay Tag Container` → `Remove Tag` |
|
||||||
|
| `MakeTagFromString` | `Make Literal Gameplay Tag` OR `Get Gameplay Tag from Name` |
|
||||||
|
| `FindComponentByInterface` | `Get Component by Class` + `Does Implement Interface` |
|
||||||
|
| `RemapFloat` | `Map Range Clamped` (built-in BP math node) |
|
||||||
|
| `LerpClamped` | `Lerp` + `Clamp (Float)` |
|
||||||
|
| `FormatTime` | Custom BP function using division/modulo |
|
||||||
|
| `LogDebug` | `Branch: Is Editor Build` → `Print String` |
|
||||||
|
| `DrawDebugSphere` | `Draw Debug Sphere` (built-in BP node) |
|
||||||
|
| `DrawDebugString3D` | `Draw Debug String` (built-in BP node) |
|
||||||
|
|
||||||
|
**Blueprint Macro Library:** Create a **Macro Library** asset (`Content/Framework/Core/ML_GameUtilities`). Macros support `WorldContext`, execution pins, and can call engine nodes — no C++ required. The trade-off: macros are NOT "static pure" — they need execution flow pins.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -370,4 +370,137 @@ Interact_Implementation
|
|||||||
### Anti-Cheat
|
### Anti-Cheat
|
||||||
- Server validates player is within interaction range before opening door.
|
- Server validates player is within interaction range before opening door.
|
||||||
- Server validates key item is in player inventory before unlocking.
|
- Server validates key item is in player inventory before unlocking.
|
||||||
- Server validates barricade damage source.
|
- Server validates barricade damage source.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 14. Manual Implementation Guide
|
||||||
|
|
||||||
|
### 14.1 Class Setup
|
||||||
|
1. Create Blueprint Class: Parent = `Actor`, Name = `BP_DoorActor`
|
||||||
|
2. Add components: `DoorRoot` (SceneComponent), `DoorFrame` (StaticMesh — static), `DoorMesh` (StaticMesh — rotates/slides), `InteractionCollision` (Box or Sphere)
|
||||||
|
3. Implement Interfaces: `I_Interactable`, `I_Persistable`
|
||||||
|
4. Set `Replicates` = true in Class Defaults
|
||||||
|
|
||||||
|
### 14.2 Variable Defaults
|
||||||
|
| Variable | Default |
|
||||||
|
|----------|---------|
|
||||||
|
| `CurrentState` | `Closed` |
|
||||||
|
| `CurrentLockState` | `Unlocked` |
|
||||||
|
| `CurrentBarricadeHealth` | `100.0` |
|
||||||
|
| `AutoCloseDelay` | `3.0` (0 = never) |
|
||||||
|
| `OpenAngle` | `90.0` |
|
||||||
|
| `OpenSpeed` | `2.0` |
|
||||||
|
| `CloseSpeed` | `1.5` |
|
||||||
|
|
||||||
|
### 14.3 Function Node-by-Node
|
||||||
|
|
||||||
|
#### `BeginPlay`
|
||||||
|
```
|
||||||
|
Event BeginPlay
|
||||||
|
→ Cache OwningActor (Self)
|
||||||
|
→ Get DoorMesh by tag/name → Cache DoorMeshComponent
|
||||||
|
→ Spawn Audio Component → Cache DoorAudioComponent
|
||||||
|
→ Apply DefaultState from DoorConfig:
|
||||||
|
Switch on DoorConfig.DefaultState:
|
||||||
|
Closed → (nothing, already closed)
|
||||||
|
Locked → Call LockDoor
|
||||||
|
Open → Call ForceSetState(Open) ← for save/load or pre-opened doors
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `Interact_Implementation(Instigator: Actor)` — I_Interactable
|
||||||
|
```
|
||||||
|
Switch on CurrentState:
|
||||||
|
Closed → Call TryOpen(Instigator)
|
||||||
|
Open → Call TryClose(Instigator)
|
||||||
|
Locked → Call TryUnlockWithItem(Instigator)
|
||||||
|
Branch on result:
|
||||||
|
True → Call UnlockDoor → Call TryOpen(Instigator)
|
||||||
|
False → Call PlayLockedFeedback → Fire OnInteractionFailed
|
||||||
|
Barricaded → Call DamageBarricade(Instigator)
|
||||||
|
If attack breaks barricade → OnBarricadeBroken → SetState Closed
|
||||||
|
Opening/Closing → Return (ignored during animation)
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `TryOpen(Instigator)` — **Must check HasAuthority() for MP**
|
||||||
|
```
|
||||||
|
Branch: CurrentState != Closed → Return false
|
||||||
|
If DoorConfig.IsOneWay:
|
||||||
|
Check player side (dot product of door forward vs player forward)
|
||||||
|
If wrong side → Play locked feedback → Return false
|
||||||
|
SetWeaponState(Opening)
|
||||||
|
Fire OnDoorStateChanged
|
||||||
|
PlayOpenAnimation() ← uses Timeline, not Timer
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `PlayOpenAnimation` — **Key node setup:**
|
||||||
|
```
|
||||||
|
Create Timeline (float track, 0→1 over OpenSpeed seconds)
|
||||||
|
Timeline Update pin:
|
||||||
|
→ Get DoorMesh → Set Relative Rotation:
|
||||||
|
Lerp (Quat): startRotation to startRotation + Rotator(0, OpenAngle, 0) by Timeline Alpha
|
||||||
|
Timeline Finished pin:
|
||||||
|
→ Call OnOpenComplete
|
||||||
|
Play Sound: AudioConfig.OpenSound at DoorMesh location via SS_AudioManager
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `OnOpenComplete`
|
||||||
|
```
|
||||||
|
Set CurrentState = Open
|
||||||
|
Fire OnDoorOpened(Instigator, bIsOpenFromFront)
|
||||||
|
If AutoCloseDelay > 0:
|
||||||
|
Set Timer by Event (AutoCloseDelay) → Call TryClose(Self)
|
||||||
|
Use "Self" as instigator for auto-close
|
||||||
|
Notify LinkedActors with "Open" event
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `TryClose(Instigator)`
|
||||||
|
```
|
||||||
|
Branch: CurrentState != Open → Return false
|
||||||
|
Set CurrentState = Closing
|
||||||
|
Clear AutoCloseTimer (if running — prevents auto-close during manual close)
|
||||||
|
Fire OnDoorStateChanged
|
||||||
|
PlayCloseAnimation()
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `TryUnlockWithItem(Instigator)` → `Boolean`
|
||||||
|
```
|
||||||
|
If CurrentLockState == KeyRequired:
|
||||||
|
Get RequiredItemTag from DoorConfig
|
||||||
|
Get Instigator → Get BPC_InventorySystem → Call HasItemQuantity(RequiredItemTag, 1)
|
||||||
|
If found:
|
||||||
|
If DoorConfig.RemoveItemOnUse:
|
||||||
|
BPC_InventorySystem.RemoveItemByTag(RequiredItemTag, 1)
|
||||||
|
Return true
|
||||||
|
Return false
|
||||||
|
If CurrentLockState == PuzzleLinked:
|
||||||
|
Get LinkedPuzzle → Call IsPuzzleSolved
|
||||||
|
Return result
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `DamageBarricade(Instigator, DamageAmount)`
|
||||||
|
```
|
||||||
|
Branch: CurrentState != Barricaded → Return
|
||||||
|
CurrentBarricadeHealth -= DamageAmount
|
||||||
|
If CurrentBarricadeHealth <= 0:
|
||||||
|
Set CurrentBarricadeHealth = 0
|
||||||
|
Set CurrentState = Closed
|
||||||
|
Fire OnBarricadeBroken(Instigator)
|
||||||
|
Fire OnDoorStateChanged
|
||||||
|
Mark variable dirty (replication)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 14.4 Blueprint Build Checklist
|
||||||
|
- [ ] Create BP_DoorActor with DoorFrame + DoorMesh + InteractionCollision
|
||||||
|
- [ ] Add all config variables (S_DoorConfig, S_DoorAudioConfig, LinkedActors)
|
||||||
|
- [ ] Set DoorMesh mobility to Movable (for rotation during animation)
|
||||||
|
- [ ] Implement I_Interactable with Interact_Implementation
|
||||||
|
- [ ] Build TryOpen/TryClose with state validation
|
||||||
|
- [ ] Create Timeline for door open/close animation (NOT Timer — Timeline for smooth interpolation)
|
||||||
|
- [ ] Implement auto-close timer with reset on re-open
|
||||||
|
- [ ] Build TryUnlockWithItem querying player inventory
|
||||||
|
- [ ] Implement lock/barricade damage with health tracking
|
||||||
|
- [ ] Add DoorAudioComponent for spatialized sounds
|
||||||
|
- [ ] Set up LinkedActors — designer assigns in editor
|
||||||
|
- [ ] Add networking: replicated CurrentState, Server_Interact RPC
|
||||||
|
- [ ] Test: approach → interact → door opens → auto-closes → locked door → use key → unlocks
|
||||||
@@ -94,4 +94,167 @@
|
|||||||
|
|
||||||
## 7. Reuse Notes
|
## 7. Reuse Notes
|
||||||
- All puzzle logic is data-driven via `DA_PuzzleData`
|
- All puzzle logic is data-driven via `DA_PuzzleData`
|
||||||
- LinkedActor pattern decouples puzzle from effect
|
- LinkedActor pattern decouples puzzle from effect
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. Manual Implementation Guide
|
||||||
|
|
||||||
|
### 8.1 Class Setup
|
||||||
|
1. Create Blueprint Class: Parent = `Actor`, Name = `BP_PuzzleDeviceActor`
|
||||||
|
2. Add components: StaticMesh (visual), InteractionCollision (Sphere), WidgetComponent (for interaction prompt)
|
||||||
|
3. Implement Interfaces: `I_Interactable`
|
||||||
|
4. Save to: `Content/Framework/Interaction/`
|
||||||
|
|
||||||
|
### 8.2 Variable Initialization (BeginPlay / Construction Script)
|
||||||
|
```
|
||||||
|
Event BeginPlay
|
||||||
|
├─ Set CurrentState = Unsolved (or Locked if requires unlock first)
|
||||||
|
├─ Set CurrentAttempts = 0
|
||||||
|
├─ Load PuzzleData → if valid:
|
||||||
|
│ ├─ Set InteractionPrompt = PuzzleData.PromptText
|
||||||
|
│ ├─ Set MaxAttempts = PuzzleData.MaxAttempts
|
||||||
|
│ ├─ Set CooldownSeconds = PuzzleData.Cooldown
|
||||||
|
│ └─ Apply initial visual state (dim if locked, glowing if interactable)
|
||||||
|
└─ Register with SS_SaveManager if implements I_Persistable
|
||||||
|
```
|
||||||
|
|
||||||
|
### 8.3 Function Implementations
|
||||||
|
|
||||||
|
#### `I_Interactable: OnInteract(Instigator: Actor)` → `Boolean`
|
||||||
|
|
||||||
|
```
|
||||||
|
[Function: OnInteract] — called by BPC_InteractionDetector
|
||||||
|
Step 1: Branch on CurrentState:
|
||||||
|
Case Unsolved → Call BeginInteraction(Instigator) → Return true
|
||||||
|
Case Solved → Play solved animation/sound → Return false (already solved)
|
||||||
|
Case Locked → Check if Instigator has required key:
|
||||||
|
If yes → unlock → BeginInteraction
|
||||||
|
If no → Play locked feedback → Return false
|
||||||
|
Case InProgress → Return false (already interacting)
|
||||||
|
Case Failed → If bResetOnFail: ResetPuzzle → BeginInteraction
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `BeginInteraction(Instigator: Actor)` → `void`
|
||||||
|
|
||||||
|
```
|
||||||
|
[Function: BeginInteraction]
|
||||||
|
Step 1: Set CurrentState = InProgress
|
||||||
|
Step 2: Set bIsInteractable = false (block re-interaction during solve)
|
||||||
|
Step 3: Fire OnPuzzleStateChanged(InProgress)
|
||||||
|
|
||||||
|
Step 4: Branch on PuzzleData.PuzzleType:
|
||||||
|
Lever, PressurePlate, ValveWheel:
|
||||||
|
- No UI overlay needed; player manipulates world objects
|
||||||
|
- Enable input on the puzzle's interactive levers/buttons
|
||||||
|
Keypad, CombinationLock, CircuitBoard:
|
||||||
|
- Open puzzle UI widget (attached to WidgetComponent or fullscreen)
|
||||||
|
- Set player input mode to UI Only
|
||||||
|
- Route input to puzzle widget
|
||||||
|
|
||||||
|
Step 5: Block player movement (optional, per puzzle config)
|
||||||
|
Step 6: Set interaction camera view (zoom to puzzle if needed)
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `CheckSolution(PlayerInput: varies by puzzle type)` → `Boolean`
|
||||||
|
|
||||||
|
```
|
||||||
|
[Function: CheckSolution]
|
||||||
|
Step 1: Read correct solution from PuzzleData.Solution
|
||||||
|
Step 2: Compare player input against solution:
|
||||||
|
- Lever sequence: check order of pulled levers matches solution
|
||||||
|
- Keypad: check entered code matches solution code
|
||||||
|
- CombinationLock: check each dial position
|
||||||
|
- CircuitBoard: check all connections match solution
|
||||||
|
- PressurePlate: check weight/actor matches
|
||||||
|
Step 3: If match:
|
||||||
|
Call OnPuzzleSolved(Instigator)
|
||||||
|
Return true
|
||||||
|
Step 4: If no match:
|
||||||
|
Increment CurrentAttempts
|
||||||
|
Branch on MaxAttempts != -1 AND CurrentAttempts >= MaxAttempts:
|
||||||
|
True → Call OnPuzzleFailed()
|
||||||
|
False → Show hint/wrong feedback, allow retry
|
||||||
|
Return false
|
||||||
|
```
|
||||||
|
|
||||||
|
**Node example for keypad puzzle:**
|
||||||
|
```
|
||||||
|
Get SolutionString from PuzzleData
|
||||||
|
Branch: PlayerInputCode == SolutionString
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `OnPuzzleSolved(Solver: Actor)` → `void`
|
||||||
|
|
||||||
|
```
|
||||||
|
[Function: OnPuzzleSolved]
|
||||||
|
Step 1: Set CurrentState = Solved
|
||||||
|
Step 2: Set bIsInteractable = false (permanently)
|
||||||
|
Step 3: Play solved visual: change material to green/solved glow
|
||||||
|
Step 4: Play solved sound via SS_AudioManager
|
||||||
|
Step 5: Fire OnPuzzleSolved(Solver)
|
||||||
|
Step 6: Fire OnPuzzleStateChanged(Solved)
|
||||||
|
|
||||||
|
Step 7: Notify LinkedActor (if set):
|
||||||
|
If LinkedActor valid:
|
||||||
|
If LinkedActor implements I_Toggleable → Call Toggle(Solver)
|
||||||
|
If LinkedActor implements I_Lockable → Call Unlock(Solver)
|
||||||
|
Fire OnPuzzleLinkedActorTriggered(LinkedActor)
|
||||||
|
|
||||||
|
Step 8: Grant rewards from PuzzleData:
|
||||||
|
Get BPC_InventorySystem → If PuzzleData.RewardItem valid → Call AddItem
|
||||||
|
Get BPC_NarrativeStateSystem → Set flag: PuzzleData.CompletionFlag
|
||||||
|
|
||||||
|
Step 9: EndInteraction()
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `OnPuzzleFailed()` → `void`
|
||||||
|
|
||||||
|
```
|
||||||
|
[Function: OnPuzzleFailed]
|
||||||
|
Step 1: Set CurrentState = Failed
|
||||||
|
Step 2: Play failure sound/visual
|
||||||
|
Step 3: Fire OnPuzzleFailed(CurrentAttempts)
|
||||||
|
Step 4: Fire OnPuzzleStateChanged(Failed)
|
||||||
|
|
||||||
|
Step 5: Branch on bResetOnFail:
|
||||||
|
True → Start timer for CooldownSeconds → Call ResetPuzzle
|
||||||
|
False → If MaxAttempts reached: Set CurrentState = Locked permanently
|
||||||
|
|
||||||
|
Step 6: EndInteraction()
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `EndInteraction()` → `void`
|
||||||
|
|
||||||
|
```
|
||||||
|
[Function: EndInteraction]
|
||||||
|
Step 1: Close puzzle UI widget (if open)
|
||||||
|
Step 2: Restore player input mode to Game Only
|
||||||
|
Step 3: Re-enable player movement
|
||||||
|
Step 4: Restore camera to default
|
||||||
|
Step 5: If CurrentState != InProgress → Set bIsInteractable = true
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `ResetPuzzle()` → `void`
|
||||||
|
|
||||||
|
```
|
||||||
|
[Function: ResetPuzzle]
|
||||||
|
Step 1: Set CurrentState = Unsolved
|
||||||
|
Step 2: Set CurrentAttempts = 0
|
||||||
|
Step 3: Reset all puzzle levers/dials/inputs to default positions
|
||||||
|
Step 4: Set bIsInteractable = true
|
||||||
|
Step 5: Fire OnPuzzleStateChanged(Unsolved)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 8.4 Blueprint Build Checklist
|
||||||
|
- [ ] Create BP_PuzzleDeviceActor actor with mesh + collision + widget components
|
||||||
|
- [ ] Implement I_Interactable interface
|
||||||
|
- [ ] Add all variables: PuzzleData, CurrentState, bIsInteractable, LinkedActor, MaxAttempts, etc.
|
||||||
|
- [ ] Implement BeginInteraction with type-switch (lever vs UI vs custom)
|
||||||
|
- [ ] Implement CheckSolution with type-specific validation
|
||||||
|
- [ ] Implement OnPuzzleSolved (visual, sound, LinkedActor, rewards, narrative flag)
|
||||||
|
- [ ] Implement OnPuzzleFailed (retry or lockout)
|
||||||
|
- [ ] Implement EndInteraction (UI close, input restore, camera restore)
|
||||||
|
- [ ] Implement ResetPuzzle for retryable puzzles
|
||||||
|
- [ ] Bind to BPC_InteractionDetector via I_Interactable
|
||||||
|
- [ ] Test: approach puzzle → interact → solve → door unlocks → save/load restores state
|
||||||
@@ -85,4 +85,167 @@
|
|||||||
|
|
||||||
## 7. Reuse Notes
|
## 7. Reuse Notes
|
||||||
- Uses UE5 Motion Warping for accurate target placement
|
- Uses UE5 Motion Warping for accurate target placement
|
||||||
- Detection uses capsule trace at player height ranges
|
- Detection uses capsule trace at player height ranges
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. Manual Implementation Guide
|
||||||
|
|
||||||
|
### 8.1 Class Setup
|
||||||
|
1. Create Blueprint Class: Parent = `ActorComponent`, Name = `BPC_ContextualTraversalSystem`
|
||||||
|
2. Add to Player Character
|
||||||
|
3. Enable `Motion Warping` plugin in Project Settings
|
||||||
|
|
||||||
|
### 8.2 Variable Initialization (BeginPlay)
|
||||||
|
```
|
||||||
|
Event BeginPlay
|
||||||
|
├─ Set bIsTraversing = false
|
||||||
|
├─ Set DetectionRange = 200.0 (cm)
|
||||||
|
├─ Set DetectionHalfAngle = 45.0 (degrees)
|
||||||
|
├─ Set VaultHeightThreshold = 120.0 (waist height in cm)
|
||||||
|
├─ Set TraversalSpeed = 1.0
|
||||||
|
├─ Set TraversalCooldown = 0.5
|
||||||
|
├─ Get Owner → Get Component by Class (BPC_MovementStateSystem) → Cache reference
|
||||||
|
├─ Get Owner → Find Component by Class (BPC_CameraStateLayer) → Cache reference
|
||||||
|
└─ Get Owner → Cast to Character → Get CharacterMovement → Cache CMC reference
|
||||||
|
```
|
||||||
|
|
||||||
|
### 8.3 Function Implementations
|
||||||
|
|
||||||
|
#### `DetectTraversableObstacle()` → `Hit Result, TraversalType, Height`
|
||||||
|
|
||||||
|
```
|
||||||
|
[Function: DetectTraversableObstacle] (Pure)
|
||||||
|
Step 1: Get Owner → Get Actor Location and Forward Vector
|
||||||
|
Step 2: Three capsule traces at different heights:
|
||||||
|
Trace A (Low: half-height = 30cm):
|
||||||
|
Start: OwnerLocation + UpVector * 30
|
||||||
|
End: Start + ForwardVector * DetectionRange
|
||||||
|
Trace Channel: WorldStatic
|
||||||
|
Trace B (Medium: half-height = 60cm):
|
||||||
|
Same but offset by 60cm vertically
|
||||||
|
Trace C (High: half-height = 90cm):
|
||||||
|
Same but offset by 90cm vertically
|
||||||
|
|
||||||
|
Step 3: For each trace that hits:
|
||||||
|
Get Hit Actor → Get Hit Location → Get Hit Normal
|
||||||
|
Calculate obstacle top: trace down from above obstacle to find top edge
|
||||||
|
ObstacleHeight = topHit.Location.Z - bottomHit.Location.Z
|
||||||
|
|
||||||
|
Step 4: Classify by height:
|
||||||
|
ObstacleHeight <= 40cm → E_TraversalHeight::Low (step up)
|
||||||
|
ObstacleHeight <= VaultHeightThreshold → E_TraversalHeight::Medium (vault)
|
||||||
|
ObstacleHeight > VaultHeightThreshold → E_TraversalHeight::High (mantle)
|
||||||
|
|
||||||
|
Step 5: Check clearance above obstacle (trace upward from top)
|
||||||
|
If ceiling too low → cannot mantle → return invalid
|
||||||
|
|
||||||
|
Step 6: Determine traversal type:
|
||||||
|
- Low + no obstacle: just step (automatic)
|
||||||
|
- Medium + clearance: Vault
|
||||||
|
- High + ledge: Mantle
|
||||||
|
- Narrow gap (sides): Squeeze
|
||||||
|
- Low barrier with gap: Slide
|
||||||
|
|
||||||
|
Step 7: Calculate landing position:
|
||||||
|
Forward trace from obstacle top to find landing spot
|
||||||
|
Set MotionWarpingTarget = landingPosition
|
||||||
|
|
||||||
|
Step 8: Return HitResult, TraversalType, TraversalHeight struct
|
||||||
|
```
|
||||||
|
|
||||||
|
**Nodes:** `Line Trace by Channel` (x5+), `Break Hit Result`, `Get Hit Location`, `Get Hit Normal`, `Vector Up/Down/Forward`, `Make Vector`, `Branch`, `Switch on E_TraversalHeight`
|
||||||
|
|
||||||
|
#### `AttemptTraversal(TraversalType, TargetLocation)` → `Boolean`
|
||||||
|
|
||||||
|
```
|
||||||
|
[Function: AttemptTraversal]
|
||||||
|
Step 1: Branch on bIsTraversing → If true, return false
|
||||||
|
Step 2: Branch on CooldownActive → If cooldown timer active, return false
|
||||||
|
Step 3: Query BPC_StateManager → IsActionPermitted("Action.Traverse")
|
||||||
|
False → Fire OnTraversalFailed("Blocked by state"), return false
|
||||||
|
|
||||||
|
Step 4: Set bIsTraversing = true
|
||||||
|
Step 5: Set CurrentTraversalType = TraversalType
|
||||||
|
Step 6: Notify BPC_MovementStateSystem → SetMovementMode(Vaulting, Forced)
|
||||||
|
Step 7: Disable player input (optional — depends on traversal type)
|
||||||
|
Step 8: Fire OnTraversalStarted(TraversalType, TargetLocation)
|
||||||
|
|
||||||
|
Step 9: Call ExecuteTraversal(TraversalType, TargetLocation)
|
||||||
|
Step 10: Return true
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `ExecuteTraversal(Type, Target)` → `void`
|
||||||
|
|
||||||
|
```
|
||||||
|
[Function: ExecuteTraversal]
|
||||||
|
Step 1: Switch on Type:
|
||||||
|
Case Vault:
|
||||||
|
- Play Montage: AM_Vault (from Animation Blueprint)
|
||||||
|
- Add Motion Warping Target: "VaultTarget" → Target location
|
||||||
|
- Root motion in montage moves character to Target
|
||||||
|
Case Mantle:
|
||||||
|
- Play Montage: AM_Mantle
|
||||||
|
- Add Motion Warping Target: "MantleTop" → ledge top
|
||||||
|
- Add Motion Warping Target: "MantleLand" → landing position
|
||||||
|
Case Slide:
|
||||||
|
- Play Montage: AM_Slide
|
||||||
|
- Set capsule half-height to crouch height temporarily
|
||||||
|
- Add Motion Warping Target: "SlideTarget"
|
||||||
|
Case Squeeze:
|
||||||
|
- Play Montage: AM_Squeeze
|
||||||
|
- Add Motion Warping Target: "SqueezeTarget"
|
||||||
|
Case LedgeGrab:
|
||||||
|
- Play Montage: AM_LedgeGrab
|
||||||
|
- Disable gravity temporarily
|
||||||
|
- Attach to ledge point
|
||||||
|
|
||||||
|
Step 2: On Montage Completed or Blending Out:
|
||||||
|
Call OnTraversalComplete()
|
||||||
|
```
|
||||||
|
|
||||||
|
**Nodes:** `Play Montage`, `Add or Update Motion Warping Target` (name + location), `Set Capsule Half Height`, `On Completed` delegate
|
||||||
|
|
||||||
|
#### `OnTraversalComplete()` → `void`
|
||||||
|
|
||||||
|
```
|
||||||
|
[Function: OnTraversalComplete]
|
||||||
|
Step 1: Set bIsTraversing = false
|
||||||
|
Step 2: Restore capsule half-height (if modified)
|
||||||
|
Step 3: Re-enable player input (if disabled)
|
||||||
|
Step 4: Notify BPC_MovementStateSystem → restore previous movement mode
|
||||||
|
Step 5: Start TraversalCooldown timer → set bCanTraverse = false
|
||||||
|
On timer end → bCanTraverse = true
|
||||||
|
Step 6: Fire OnTraversalComplete(CurrentTraversalType)
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `CancelTraversal()` → `void`
|
||||||
|
|
||||||
|
```
|
||||||
|
[Function: CancelTraversal]
|
||||||
|
Step 1: Branch on bIsTraversing → If false, return
|
||||||
|
Step 2: Stop Montage (AM currently playing)
|
||||||
|
Step 3: Set bIsTraversing = false
|
||||||
|
Step 4: Restore movement mode, input, capsule height
|
||||||
|
Step 5: Fire OnTraversalFailed("Cancelled")
|
||||||
|
```
|
||||||
|
|
||||||
|
### 8.4 Event Dispatcher Bindings
|
||||||
|
|
||||||
|
| Bind to Dispatcher | Custom Event | Logic |
|
||||||
|
|-------------------|-------------|-------|
|
||||||
|
| `IA_Jump` (Pressed near obstacle) | `OnTraversalInput` | Call DetectTraversableObstacle → if valid: AttemptTraversal |
|
||||||
|
| `BPC_MovementStateSystem.OnMovementModeChanged` | `CheckTraversalState` | If in Vaulting mode and bIsTraversing false → force cancel |
|
||||||
|
|
||||||
|
### 8.5 Blueprint Build Checklist
|
||||||
|
- [ ] Enable Motion Warping plugin
|
||||||
|
- [ ] Create BPC_ContextualTraversalSystem, add to Player Character
|
||||||
|
- [ ] Add all variables with defaults
|
||||||
|
- [ ] Create traversal montages: AM_Vault, AM_Mantle, AM_Slide, AM_Squeeze, AM_LedgeGrab
|
||||||
|
- [ ] Add Motion Warping notifies in montages at key frames
|
||||||
|
- [ ] Implement DetectTraversableObstacle with multi-height capsule traces
|
||||||
|
- [ ] Implement AttemptTraversal with state/cooldown checks
|
||||||
|
- [ ] Implement ExecuteTraversal with type-switch and motion warping targets
|
||||||
|
- [ ] Implement OnTraversalComplete cleanup
|
||||||
|
- [ ] Bind IA_Jump for traversal input
|
||||||
|
- [ ] Test: vault over low wall, mantle onto ledge, slide under barrier
|
||||||
@@ -51,4 +51,125 @@
|
|||||||
|
|
||||||
## 7. Reuse Notes
|
## 7. Reuse Notes
|
||||||
- Generic base for all "press E to use" world objects
|
- Generic base for all "press E to use" world objects
|
||||||
- Can be subclassed for specialized behavior
|
- Can be subclassed for specialized behavior
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. Manual Implementation Guide
|
||||||
|
|
||||||
|
### 8.1 Class Setup
|
||||||
|
1. Create Blueprint Class: Parent = `ActorComponent`, Name = `BPC_UsableWorldObjectSystem`
|
||||||
|
2. Attach to any world actor that should be "usable" (switches, buttons, valves, terminals, notes, audio logs)
|
||||||
|
3. The owner actor must implement `I_Interactable` (or this component implements it on behalf of owner)
|
||||||
|
|
||||||
|
### 8.2 Variable Initialization (BeginPlay)
|
||||||
|
```
|
||||||
|
Event BeginPlay
|
||||||
|
├─ Set bIsActive = true
|
||||||
|
├─ Set bHasBeenUsed = false
|
||||||
|
├─ Load InteractionData (Data Asset reference)
|
||||||
|
│ ├─ Read PromptText → store for UI
|
||||||
|
│ ├─ Read InteractionDuration → for hold interactions
|
||||||
|
│ └─ Read ObjectType → set behavior mode
|
||||||
|
└─ Set LinkedActor from owner variable (designer-assigned in editor)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 8.3 Function Implementations
|
||||||
|
|
||||||
|
#### `OnUse(Instigator: Actor)` → `Boolean`
|
||||||
|
|
||||||
|
```
|
||||||
|
[Function: OnUse] — core interaction handler
|
||||||
|
|
||||||
|
Step 1: Branch on bIsActive → If false, Fire OnObjectStateChanged(false), Return false
|
||||||
|
Step 2: Branch on bSingleUse AND bHasBeenUsed:
|
||||||
|
True → Return false (already used once)
|
||||||
|
|
||||||
|
Step 3: Switch on ObjectType:
|
||||||
|
Case Switch:
|
||||||
|
- Toggle bIsActive (invert)
|
||||||
|
- Play switch animation: flip lever/button
|
||||||
|
- If LinkedActor: call I_Toggleable.Toggle(Instigator) on LinkedActor
|
||||||
|
- Start CooldownSeconds timer → bIsActive = true after cooldown
|
||||||
|
|
||||||
|
Case Button:
|
||||||
|
- Set bIsActive = false (press in)
|
||||||
|
- Play button press animation
|
||||||
|
- Notify LinkedActor if set
|
||||||
|
- Start timer → spring back: bIsActive = true
|
||||||
|
|
||||||
|
Case Valve:
|
||||||
|
- Begin rotate interaction (player holds E and moves mouse)
|
||||||
|
- Call Adjust on LinkedActor (I_Adjustable) with rotation delta
|
||||||
|
- On release: stop adjustment
|
||||||
|
|
||||||
|
Case Terminal:
|
||||||
|
- Call OpenTerminal UI on LinkedActor or self
|
||||||
|
- Set player input mode to UI Only
|
||||||
|
- On close → restore input
|
||||||
|
|
||||||
|
Case Readable:
|
||||||
|
- Get NoteText from InteractionData
|
||||||
|
- Call WBP_JournalDocumentViewer.OpenNote(NoteText)
|
||||||
|
- Play "paper rustle" sound via SS_AudioManager
|
||||||
|
|
||||||
|
Case AudioLog:
|
||||||
|
- Get AudioClip from InteractionData
|
||||||
|
- Call SS_AudioManager.PlayDialogue(AudioClip)
|
||||||
|
- Show subtitle text if available
|
||||||
|
|
||||||
|
Case Generic:
|
||||||
|
- Fire custom dispatcher or call blueprint event OnGenericUse
|
||||||
|
|
||||||
|
Step 4: Set bHasBeenUsed = true (if bSingleUse)
|
||||||
|
Step 5: Fire OnObjectUsed(Instigator)
|
||||||
|
Step 6: Return true
|
||||||
|
```
|
||||||
|
|
||||||
|
**Nodes:** `Switch on E_UsableObjectType`, `Play Animation`, `I_Toggleable.Toggle`, `Open UI Widget`, `Play Sound`
|
||||||
|
|
||||||
|
#### `SetEnabled(bEnabled: Boolean)` → `void`
|
||||||
|
|
||||||
|
```
|
||||||
|
[Function: SetEnabled]
|
||||||
|
Step 1: Set bIsActive = bEnabled
|
||||||
|
Step 2: Update visual: enable/disable highlight, change material
|
||||||
|
Step 3: Fire OnObjectStateChanged(bEnabled)
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `ResetObject()` → `void`
|
||||||
|
|
||||||
|
```
|
||||||
|
[Function: ResetObject]
|
||||||
|
Step 1: Set bHasBeenUsed = false
|
||||||
|
Step 2: Set bIsActive = true
|
||||||
|
Step 3: Reset visual state to default
|
||||||
|
Step 4: Fire OnObjectStateChanged(true)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 8.4 I_Interactable Implementation (on owner actor)
|
||||||
|
|
||||||
|
If the component is on an actor that implements I_Interactable:
|
||||||
|
|
||||||
|
```
|
||||||
|
[Owner Actor: I_Interactable.OnInteract(Instigator)]
|
||||||
|
Step 1: Get Component by Class (BPC_UsableWorldObjectSystem)
|
||||||
|
Step 2: Call BPC_UsableWorldObjectSystem.OnUse(Instigator)
|
||||||
|
|
||||||
|
[Owner Actor: I_Interactable.CanInteract(Instigator)]
|
||||||
|
Get UsableComp → Return UsableComp.bIsActive AND NOT (UsableComp.bSingleUse AND UsableComp.bHasBeenUsed)
|
||||||
|
|
||||||
|
[Owner Actor: I_Interactable.GetInteractionPrompt()]
|
||||||
|
Get UsableComp → Return UsableComp.InteractionData.PromptText
|
||||||
|
```
|
||||||
|
|
||||||
|
### 8.5 Blueprint Build Checklist
|
||||||
|
- [ ] Define enum E_UsableObjectType and create DA_InteractionData with usage config
|
||||||
|
- [ ] Create BPC_UsableWorldObjectSystem component
|
||||||
|
- [ ] Add variables: ObjectType, InteractionData, bIsActive, bSingleUse, bHasBeenUsed, CooldownSeconds
|
||||||
|
- [ ] Implement OnUse with type-switch for all 7 object types
|
||||||
|
- [ ] Implement SetEnabled / ResetObject
|
||||||
|
- [ ] Create owner actor (BP_UsableObject) that implements I_Interactable
|
||||||
|
- [ ] Route I_Interactable calls to this component
|
||||||
|
- [ ] Set LinkedActor reference for Switch/Button objects
|
||||||
|
- [ ] Test: place switch → press E → linked light toggles → cooldown → press again
|
||||||
@@ -83,4 +83,148 @@
|
|||||||
|
|
||||||
## 7. Reuse Notes
|
## 7. Reuse Notes
|
||||||
- Quick slots are configurable per project (4-8 slots)
|
- 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
|
||||||
@@ -16,7 +16,7 @@
|
|||||||
| `CollectionProgress` | `TMap<FGameplayTag, int32>` | Collected count per collection type |
|
| `CollectionProgress` | `TMap<FGameplayTag, int32>` | Collected count per collection type |
|
||||||
| `CollectionTargets` | `TMap<FGameplayTag, int32>` | Total items per collection |
|
| `CollectionTargets` | `TMap<FGameplayTag, int32>` | Total items per collection |
|
||||||
| `bShowCollectibleNotifications` | `bool` | Toast on collectible pickup |
|
| `bShowCollectibleNotifications` | `bool` | Toast on collectible pickup |
|
||||||
| `CompletionRewards` | `TMap<FGameplayTag, TArray<FPrimaryAssetId>>` | Items granted on collection completion |
|
| `CompletionRewards` | `Map<Gameplay Tag, Array<Primary Asset Id>>` | Items granted on collection completion |
|
||||||
|
|
||||||
## 3. Functions
|
## 3. Functions
|
||||||
|
|
||||||
|
|||||||
@@ -46,4 +46,129 @@
|
|||||||
| `BPC_LoreUnlockSystem` | Lore document triggers |
|
| `BPC_LoreUnlockSystem` | Lore document triggers |
|
||||||
|
|
||||||
## 6. Reuse Notes
|
## 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 |
|
| `WBP_JournalDocumentViewer` | UI display |
|
||||||
|
|
||||||
## 6. Reuse Notes
|
## 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
|
## 6. Reuse Notes
|
||||||
- Keys use gameplay tags for generic matching (any key with same tag works)
|
- 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
|
||||||
@@ -102,4 +102,61 @@ Ranged weapon specialization. Handles line trace / projectile fire, weapon sprea
|
|||||||
- Spread is cumulative while firing, resets on recovery timer.
|
- Spread is cumulative while firing, resets on recovery timer.
|
||||||
- Projectile type requires a child BP implementing `AP_BaseProjectile` with velocity and lifetime.
|
- Projectile type requires a child BP implementing `AP_BaseProjectile` with velocity and lifetime.
|
||||||
- Renamed from `BP_RangedWeapon` to `BPC_FirearmSystem` per Master naming convention.
|
- Renamed from `BP_RangedWeapon` to `BPC_FirearmSystem` per Master naming convention.
|
||||||
- Cross-references updated: `BPC_DamageHandlerComponent` → `BPC_DamageReceptionSystem`, `BPC_PlayerCameraManager` → `BPC_CameraStateLayer`, `DA_WeaponData` → `14-data-assets/`.
|
- Cross-references updated: `BPC_DamageHandlerComponent` → `BPC_DamageReceptionSystem`, `BPC_PlayerCameraManager` → `BPC_CameraStateLayer`, `DA_WeaponData` → `14-data-assets/`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Manual Implementation Guide
|
||||||
|
|
||||||
|
### Class Setup
|
||||||
|
1. Create Blueprint Class: Parent = `ActorComponent`, Name = `BPC_FirearmSystem`
|
||||||
|
2. Attach to `BP_WeaponBase` subclass: `BP_RangedWeapon`
|
||||||
|
3. Add FireSocket SceneComponent on weapon mesh (muzzle position)
|
||||||
|
|
||||||
|
### Variable Init (BeginPlay)
|
||||||
|
```
|
||||||
|
Event BeginPlay
|
||||||
|
├─ Read WeaponData from Owner BP_WeaponBase
|
||||||
|
├─ Set FireType = WeaponData.FireType (Hitscan/Projectile/Hybrid)
|
||||||
|
├─ Set Range = WeaponData.EffectiveRange
|
||||||
|
├─ Set SpreadAngle = WeaponData.BaseSpread
|
||||||
|
├─ Set BulletsPerShot = WeaponData.PelletsPerShot
|
||||||
|
├─ Set CurrentSpread = 0.0
|
||||||
|
└─ Cache: BPC_AmmoComponent, BPC_CombatFeedbackComponent, BPC_CameraStateLayer
|
||||||
|
```
|
||||||
|
|
||||||
|
### Function Node-by-Node
|
||||||
|
|
||||||
|
#### `OnFire_Implementation()` *(Overrides BP_WeaponBase.OnFire)*
|
||||||
|
```
|
||||||
|
Step 1: Get Camera Forward Vector
|
||||||
|
Step 2: Switch on FireType:
|
||||||
|
Hitscan: ForLoop (BulletsPerShot):
|
||||||
|
AimDir = Forward + CalculateSpread()
|
||||||
|
LineTraceByChannel: Start=Camera, End=Camera+AimDir*Range
|
||||||
|
If Hit & Implements I_Damageable: ApplyDamage(WeaponData.Damage, HitResult)
|
||||||
|
SpawnImpactFX(HitResult)
|
||||||
|
Projectile: ForLoop (BulletsPerShot):
|
||||||
|
Spawn ProjectileClass at FireSocket, velocity=AimDir*Speed
|
||||||
|
Hybrid: Hitscan + projectile for near-miss area
|
||||||
|
Step 3: ApplyRecoil → CameraShake or AddControllerPitch/Yaw
|
||||||
|
Step 4: ConsumeAmmo on BPC_AmmoComponent
|
||||||
|
Step 5: PlayMuzzleFlash + PlayFireSound on CombatFeedback
|
||||||
|
Step 6: CurrentSpread += SpreadIncreasePerShot (clamped to MaxSpread)
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `CalculateSpread()` → Vector *(Pure)*
|
||||||
|
```
|
||||||
|
RandomFloat(-1,1)*CurrentSpread → YawOffset
|
||||||
|
RandomFloat(-1,1)*CurrentSpread → PitchOffset
|
||||||
|
MakeRotator(PitchOffset, YawOffset, 0) → GetForwardVector
|
||||||
|
```
|
||||||
|
|
||||||
|
### Build Checklist
|
||||||
|
- [ ] Create BPC_FirearmSystem, attach to BP_RangedWeapon
|
||||||
|
- [ ] Add FireSocket at muzzle
|
||||||
|
- [ ] Implement OnFire with hitscan/projectile/hybrid switch
|
||||||
|
- [ ] Implement CalculateSpread
|
||||||
|
- [ ] Wire to AmmoComponent, CombatFeedback, CameraStateLayer
|
||||||
|
- [ ] Create ProjectileClass with ProjectileMovement for projectile weapons
|
||||||
|
- [ ] Test: fire at target → damage registers → FX plays → ammo count drops
|
||||||
@@ -131,4 +131,115 @@ Melee weapon specialization. Handles swing detection, hitbox overlap checking du
|
|||||||
- Hit collision is a simple box or capsule that exists only during Active phase.
|
- Hit collision is a simple box or capsule that exists only during Active phase.
|
||||||
- Block reduces incoming damage via DamageReception; parry reflects stagger.
|
- Block reduces incoming damage via DamageReception; parry reflects stagger.
|
||||||
- Renamed from `BP_MeleeWeapon` to `BPC_MeleeSystem` per Master naming convention.
|
- Renamed from `BP_MeleeWeapon` to `BPC_MeleeSystem` per Master naming convention.
|
||||||
- Cross-references updated: `BPC_DamageHandlerComponent` → `BPC_DamageReceptionSystem`.
|
- Cross-references updated: `BPC_DamageHandlerComponent` → `BPC_DamageReceptionSystem`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Manual Implementation Guide
|
||||||
|
|
||||||
|
### Class Setup
|
||||||
|
1. Create Blueprint Class: Parent = `ActorComponent`, Name = `BPC_MeleeSystem`
|
||||||
|
2. Attach to `BP_WeaponBase` subclass: `BP_MeleeWeapon`
|
||||||
|
3. Add `HitDetectionCollision` (Capsule or Box component) to weapon — disabled by default
|
||||||
|
|
||||||
|
### Variable Init (BeginPlay)
|
||||||
|
```
|
||||||
|
Event BeginPlay
|
||||||
|
├─ Set SwingPhase = Idle
|
||||||
|
├─ Set ComboStep = 0
|
||||||
|
├─ Set bCanCombo = false
|
||||||
|
├─ Set bIsBlocking = false
|
||||||
|
├─ Disable HitDetectionCollision (NoCollision)
|
||||||
|
├─ Get Owner Weapon → Read DA_WeaponData for: ComboWindowDuration, ChargeDuration, ParryWindow, StaggerDuration
|
||||||
|
└─ Get Owner → Find BPC_DamageReceptionSystem → Cache
|
||||||
|
```
|
||||||
|
|
||||||
|
### Function Implementations
|
||||||
|
|
||||||
|
#### `StartSwing(AttackType: EAttackType)` → `void`
|
||||||
|
|
||||||
|
```
|
||||||
|
[Function: StartSwing]
|
||||||
|
Step 1: Branch on SwingPhase != Idle AND SwingPhase != Recovery → Return (can't chain now)
|
||||||
|
Step 2: Set SwingPhase = WindUp
|
||||||
|
Step 3: Build montage section name:
|
||||||
|
If this is combo: "Combo_" + ComboStep
|
||||||
|
If AttackType == HeavyAttack: prefix "Heavy_"
|
||||||
|
If AttackType == ChargeAttack: "Charge_"
|
||||||
|
Step 4: Play Montage (SwingMontage, Section = built section name)
|
||||||
|
Step 5: Set bHitRegistered = false, Clear HitActors array
|
||||||
|
Step 6: Fire OnSwingStarted(AttackType, ComboStep)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Nodes:** `Switch on EAttackType`, `Build String`, `Play Montage` (with Starting Section), `Clear Array`
|
||||||
|
|
||||||
|
#### `OnSwingActive()` → `void` *(Called by Animation Notify: "Notify_Active")*
|
||||||
|
|
||||||
|
```
|
||||||
|
Step 1: Set SwingPhase = Active
|
||||||
|
Step 2: HitDetectionCollision.SetCollisionEnabled(QueryOnly)
|
||||||
|
Step 3: Set bHitRegistered = false
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `OnSwingHit(HitResult)` → `void` *(Called by HitDetectionCollision OnComponentBeginOverlap)*
|
||||||
|
|
||||||
|
```
|
||||||
|
Step 1: Get Hit Actor from Overlap → Cast to Actor
|
||||||
|
Step 2: ForEach HitActors → if match found → Return (already hit this actor this swing)
|
||||||
|
Step 3: Add HitActor to HitActors
|
||||||
|
Step 4: If HitActor implements I_Damageable:
|
||||||
|
Get BPC_DamageReceptionSystem → Call ApplyMeleeDamage:
|
||||||
|
BaseDamage = WeaponData.Damage
|
||||||
|
Multiplier: Light=1.0, Heavy=1.8, Charge=2.5, Sprint=1.5
|
||||||
|
HitLocation = HitResult.ImpactPoint
|
||||||
|
Apply impulse in swing direction
|
||||||
|
Step 5: Get BPC_CombatFeedbackComponent → Call PlayHitFX(HitResult)
|
||||||
|
Step 6: Apply Stagger to HitActor
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `OnSwingRecovery()` → `void` *(Animation Notify: "Notify_Recovery")*
|
||||||
|
|
||||||
|
```
|
||||||
|
Step 1: Set SwingPhase = Recovery
|
||||||
|
Step 2: HitDetectionCollision.SetCollisionEnabled(NoCollision)
|
||||||
|
Step 3: Clear HitActors
|
||||||
|
Step 4: Set bCanCombo = true
|
||||||
|
Step 5: Start Timer (ComboWindowDuration) → On timer: if no combo received → OnSwingComplete()
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `OnSwingComplete()` → `void`
|
||||||
|
|
||||||
|
```
|
||||||
|
Step 1: Set SwingPhase = Idle
|
||||||
|
Step 2: Set bCanCombo = false
|
||||||
|
Step 3: Set ComboStep = 0
|
||||||
|
Step 4: Fire OnSwingFinished
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `StartBlock()` → `void`
|
||||||
|
|
||||||
|
```
|
||||||
|
Step 1: Set bIsBlocking = true
|
||||||
|
Step 2: Play block montage loop section
|
||||||
|
Step 3: Get Owner Character → Get CharacterMovement → Set MaxWalkSpeed *= 0.5
|
||||||
|
Step 4: Set ParryWindow timer: bParryActive = true for ParryWindow seconds
|
||||||
|
On timer end → bParryActive = false
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `EndBlock()` → `void`
|
||||||
|
|
||||||
|
```
|
||||||
|
Step 1: Set bIsBlocking = false
|
||||||
|
Step 2: Stop block montage
|
||||||
|
Step 3: Restore MaxWalkSpeed
|
||||||
|
```
|
||||||
|
|
||||||
|
### Build Checklist
|
||||||
|
- [ ] Create BPC_MeleeSystem, add to BP_MeleeWeapon
|
||||||
|
- [ ] Add HitDetectionCollision component (Capsule/Box, NoCollision by default)
|
||||||
|
- [ ] Create SwingMontage with sections: Combo_1, Combo_2, Combo_3, Heavy_1, Charge_1
|
||||||
|
- [ ] Place Animation Notifies: Notify_Active, Notify_Recovery, Notify_CanCombo, Notify_ParryWindow
|
||||||
|
- [ ] Implement phase state machine: Idle→WindUp→Active→Recovery→(combo)→Idle
|
||||||
|
- [ ] Bind HitDetectionCollision.OnComponentBeginOverlap → OnSwingHit
|
||||||
|
- [ ] Implement combo system with bCanCombo window timer
|
||||||
|
- [ ] Implement block/parry with damage reduction and stagger reflection
|
||||||
@@ -59,4 +59,106 @@
|
|||||||
|
|
||||||
- ReloadMontage from DA_WeaponData per weapon; swap for each weapon type
|
- ReloadMontage from DA_WeaponData per weapon; swap for each weapon type
|
||||||
- Partial reload enabled per weapon (e.g. shotguns use individual shell reloads)
|
- Partial reload enabled per weapon (e.g. shotguns use individual shell reloads)
|
||||||
- AmmoTypeTag links to correct ammo pool in BPC_AmmoResourceSystem
|
- AmmoTypeTag links to correct ammo pool in BPC_AmmoResourceSystem
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Manual Implementation Guide
|
||||||
|
|
||||||
|
### Class Setup
|
||||||
|
1. Create Blueprint Class: Parent = `ActorComponent`, Name = `BPC_ReloadSystem`
|
||||||
|
2. Attach to `BP_WeaponBase` or `BPC_FirearmSystem`
|
||||||
|
3. Weapon must have a `ReloadMontage` with Animation Notifies
|
||||||
|
|
||||||
|
### Variable Init (BeginPlay)
|
||||||
|
```
|
||||||
|
Event BeginPlay
|
||||||
|
├─ Set bIsReloading = false
|
||||||
|
├─ Set bPartialReload = false
|
||||||
|
├─ Get Owner → Read DA_WeaponData → Load ReloadMontage, AmmoTypeTag, MagazineSize
|
||||||
|
└─ Cache: BPC_AmmoComponent, BP_WeaponBase (for state check)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Function Implementations
|
||||||
|
|
||||||
|
#### `RequestReload()` → `void`
|
||||||
|
|
||||||
|
```
|
||||||
|
[Function: RequestReload]
|
||||||
|
Step 1: Get BP_WeaponBase → Check WeaponState == Ready → If not, return (can't reload while firing)
|
||||||
|
Step 2: Call CanReload() → Branch:
|
||||||
|
False → Fire OnReloadFailed → Return
|
||||||
|
True → Call BeginReload()
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `CanReload()` → `Boolean`
|
||||||
|
|
||||||
|
```
|
||||||
|
[Function: CanReload] (Pure)
|
||||||
|
Step 1: Get BPC_AmmoComponent → Call GetAmmoInMagazine()
|
||||||
|
If magazine >= MagazineSize → Return false (already full)
|
||||||
|
Step 2: Get BPC_AmmoComponent → Call GetReserveAmmo(AmmoTypeTag)
|
||||||
|
If reserve <= 0 → Return false (no ammo to load)
|
||||||
|
Step 3: Return true
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `BeginReload()` → `void`
|
||||||
|
|
||||||
|
```
|
||||||
|
[Function: BeginReload]
|
||||||
|
Step 1: Set bIsReloading = true
|
||||||
|
Step 2: Notify BP_WeaponBase → SetWeaponState(Reloading)
|
||||||
|
Step 3: Play Montage (ReloadMontage)
|
||||||
|
Step 4: Fire OnReloadStarted
|
||||||
|
|
||||||
|
Step 5: Animation Notify "TransferRounds" setup:
|
||||||
|
In the animation montage, add Notify at the frame where the magazine is inserted.
|
||||||
|
On trigger → Call CompleteReload()
|
||||||
|
|
||||||
|
Step 6: Animation Notify "ReloadEnd" setup:
|
||||||
|
Add Notify at montage end → Call FinishReload()
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `CompleteReload()` → `void` *(Called by Notify "TransferRounds")*
|
||||||
|
|
||||||
|
```
|
||||||
|
[Function: CompleteReload]
|
||||||
|
Step 1: Get BPC_AmmoComponent:
|
||||||
|
MagazineCurrent = GetAmmoInMagazine()
|
||||||
|
ReserveCurrent = GetReserveAmmo(AmmoTypeTag)
|
||||||
|
Step 2: RoundsToAdd = Min(MagazineSize - MagazineCurrent, ReserveCurrent)
|
||||||
|
Step 3: BPC_AmmoComponent.RemoveReserveAmmo(AmmoTypeTag, RoundsToAdd)
|
||||||
|
Step 4: BPC_AmmoComponent.AddMagazineAmmo(RoundsToAdd)
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `FinishReload()` → `void` *(Called by Notify "ReloadEnd")*
|
||||||
|
|
||||||
|
```
|
||||||
|
Step 1: Set bIsReloading = false
|
||||||
|
Step 2: Notify BP_WeaponBase → SetWeaponState(Ready)
|
||||||
|
Step 3: Fire OnReloadCompleted(RoundsToAdd)
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `InterruptReload()` → `void`
|
||||||
|
|
||||||
|
```
|
||||||
|
[Function: InterruptReload]
|
||||||
|
Step 1: Branch on bIsReloading → If false, return
|
||||||
|
Step 2: If bPartialReload AND CompleteReload already called:
|
||||||
|
Keep transferred rounds (partial reload success)
|
||||||
|
Step 3: Else: no rounds transferred
|
||||||
|
Step 4: Stop Montage (ReloadMontage)
|
||||||
|
Step 5: Set bIsReloading = false
|
||||||
|
Step 6: Notify BP_WeaponBase → SetWeaponState(Ready)
|
||||||
|
Step 7: Fire OnReloadInterrupted(RoundsTransferred)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Build Checklist
|
||||||
|
- [ ] Create BPC_ReloadSystem, add to BP_RangedWeapon
|
||||||
|
- [ ] Create ReloadMontage per weapon with Notifies: TransferRounds, ReloadEnd
|
||||||
|
- [ ] Implement CanReload checking magazine fullness and reserve ammo
|
||||||
|
- [ ] Implement BeginReload with montage playback and state lock
|
||||||
|
- [ ] Wire CompleteReload to ammo transfer
|
||||||
|
- [ ] Wire FinishReload to state restoration
|
||||||
|
- [ ] Implement InterruptReload for mid-reload weapon swap
|
||||||
|
- [ ] Test: empty magazine → reload → rounds transfer → magazine full
|
||||||
@@ -13,15 +13,26 @@ The Modular Game Framework uses `UDataAsset` (and subclass `UPrimaryDataAsset` w
|
|||||||
### Conventions
|
### Conventions
|
||||||
- **Naming:** `DA_<Domain><Subtype>` — e.g., `DA_WeaponData`, `DA_AIProfile`, `DA_AtmosphereProfile`
|
- **Naming:** `DA_<Domain><Subtype>` — e.g., `DA_WeaponData`, `DA_AIProfile`, `DA_AtmosphereProfile`
|
||||||
- **Storage:** All DA files live in `Content/DataAssets/<Domain>/` in-engine
|
- **Storage:** All DA files live in `Content/DataAssets/<Domain>/` in-engine
|
||||||
- **Loading:** Use `UAssetManager` with `FPrimaryAssetType` and `FPrimaryAssetId` for async loading
|
- **Parent Class:** `Primary Data Asset` (Blueprint class). Maps to C++ `UPrimaryDataAsset`.
|
||||||
- **Validation:** Each DA implements `ValidateData()` for editor-time data integrity checks
|
- **Loading:** Use `Async Load Primary Asset` node with `Primary Asset Id` and `Primary Asset Type`
|
||||||
- **Gameplay Tags:** All DAs carry a `FGameplayTagContainer` for query and filtering
|
- **Validation:** Each DA implements a `ValidateData()` function for editor-time data integrity checks
|
||||||
|
- **Gameplay Tags:** All DAs carry a `Gameplay Tag Container` for query and filtering
|
||||||
|
|
||||||
### Dependency Rules
|
### Dependency Rules
|
||||||
1. DAs reference other DAs by `FPrimaryAssetId` or `TSoftObjectPtr`, never by hard pointer
|
1. DAs reference other DAs by `Primary Asset Id` or `Soft Object Reference`, never by hard pointer
|
||||||
2. No DA may reference a runtime system; only other DAs
|
2. No DA may reference a runtime system; only other DAs
|
||||||
3. Circular DA references are prohibited — use `World Context Object` for runtime resolution
|
3. Circular DA references are prohibited — use `World Context Object` for runtime resolution
|
||||||
4. All DA types are registered in `DefaultGame.ini` under `[/Script/Engine.AssetManagerSettings]`
|
4. All DA types are registered in `DefaultGame.ini` under `[/Script/Engine.AssetManagerSettings]`
|
||||||
|
- In BP: configure via **Project Settings → Asset Manager → Primary Asset Types to Scan**
|
||||||
|
|
||||||
|
### BP Type Reference
|
||||||
|
| C++ Type | Blueprint Variable Type | BP Node |
|
||||||
|
|----------|------------------------|---------|
|
||||||
|
| `FPrimaryAssetId` | `Primary Asset Id` | `Make Primary Asset Id`, `Break Primary Asset Id` |
|
||||||
|
| `FPrimaryAssetType` | `Primary Asset Type` | `Make Primary Asset Type` |
|
||||||
|
| `TSoftObjectPtr<T>` | `Soft Object Reference` | `Async Load Primary Asset`, `Resolve Soft Reference` |
|
||||||
|
| `FGameplayTagContainer` | `Gameplay Tag Container` | `Has Tag`, `Add Tag`, `Has Any` |
|
||||||
|
| `UPrimaryDataAsset` | `Primary Data Asset` (parent class) | Right-click → Miscellaneous → Data Asset → PrimaryDataAsset |
|
||||||
|
|
||||||
### Registered DA Types (Section 13 of Master)
|
### Registered DA Types (Section 13 of Master)
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# DA_EncounterData — Data Asset
|
# DA_EncounterData — Data Asset
|
||||||
|
|
||||||
**Parent Class:** `UDataAsset`
|
**Parent Class:** `Primary Data Asset` (Blueprint: `PrimaryDataAsset`)
|
||||||
**Dependencies:** [`BPC_ProceduralEncounter`](../10-adaptive/70_BPC_ProceduralEncounter.md)
|
**Dependencies:** [`BPC_ProceduralEncounter`](../10-adaptive/92_BPC_ProceduralEncounter.md)
|
||||||
**Purpose:** Defines procedural encounter configurations — enemy types, spawn rules, difficulty scaling, and encounter triggers.
|
**Purpose:** Defines procedural encounter configurations — enemy types, spawn rules, difficulty scaling, and encounter triggers.
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -10,18 +10,18 @@
|
|||||||
|
|
||||||
| Field | Type | Description |
|
| Field | Type | Description |
|
||||||
|-------|------|-------------|
|
|-------|------|-------------|
|
||||||
| `EncounterTag` | `FGameplayTag` | Unique encounter identifier |
|
| `EncounterTag` | `Gameplay Tag` | Unique encounter identifier |
|
||||||
| `EnemyArchetypes` | `TArray<FPrimaryAssetId>` | Allowed enemy DA_AIProfile references |
|
| `EnemyArchetypes` | `Array<Primary Asset Id>` | Allowed enemy `DA_BehaviourVariant` references |
|
||||||
| `MinEnemies` | `int32` | Minimum spawn count |
|
| `MinEnemies` | `Integer` | Minimum spawn count |
|
||||||
| `MaxEnemies` | `int32` | Maximum spawn count |
|
| `MaxEnemies` | `Integer` | Maximum spawn count |
|
||||||
| `SpawnRadius` | `float` | Radius around trigger point (cm) |
|
| `SpawnRadius` | `Float` | Radius around trigger point (cm) |
|
||||||
| `SpawnDelay` | `float` | Time between individual spawns |
|
| `SpawnDelay` | `Float` | Time between individual spawns |
|
||||||
| `bTriggerOnOverlap` | `bool` | Auto-trigger on player overlap |
|
| `bTriggerOnOverlap` | `Boolean` | Auto-trigger on player overlap |
|
||||||
| `bTriggerOnAlert` | `bool` | Trigger when alert level reaches threshold |
|
| `bTriggerOnAlert` | `Boolean` | Trigger when alert level reaches threshold |
|
||||||
| `RequiredAlertLevel` | `float` | Alert threshold (0.0-1.0) |
|
| `RequiredAlertLevel` | `Float` | Alert threshold (0.0-1.0) |
|
||||||
| `EncounterCooldown` | `float` | Min time between encounters |
|
| `EncounterCooldown` | `Float` | Min time between encounters |
|
||||||
| `DifficultyScaling` | `UCurveFloat*` | Curve for scaling enemy count/strength by difficulty |
|
| `DifficultyScaling` | `Curve Float` (Object Reference) | Curve for scaling enemy count/strength by difficulty |
|
||||||
| `NarrativePrerequisites` | `TArray<FGameplayTag>` | Narrative flags required |
|
| `NarrativePrerequisites` | `Array<Gameplay Tag>` | Narrative flags required |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# DA_ObjectiveData — Data Asset
|
# DA_ObjectiveData — Data Asset
|
||||||
|
|
||||||
**Parent Class:** `UDataAsset`
|
**Parent Class:** `Primary Data Asset` (Blueprint: `PrimaryDataAsset`)
|
||||||
**Dependencies:** [`BPC_ObjectiveSystem`](../07-narrative/39_BPC_ObjectiveSystem.md), [`WBP_ObjectiveDisplay`](../06-ui/WBP_ObjectiveDisplay.md)
|
**Dependencies:** [`BPC_ObjectiveSystem`](../07-narrative/59_BPC_ObjectiveSystem.md), [`WBP_ObjectiveDisplay`](../06-ui/54_WBP_ObjectiveDisplay.md)
|
||||||
**Purpose:** Defines objective content — objective text, requirements, completion conditions, linked narrative flags, and reward data.
|
**Purpose:** Defines objective content — objective text, requirements, completion conditions, linked narrative flags, and reward data.
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -10,16 +10,16 @@
|
|||||||
|
|
||||||
| Field | Type | Description |
|
| Field | Type | Description |
|
||||||
|-------|------|-------------|
|
|-------|------|-------------|
|
||||||
| `ObjectiveTag` | `FGameplayTag` | Unique tag for this objective |
|
| `ObjectiveTag` | `Gameplay Tag` | Unique tag for this objective |
|
||||||
| `ObjectiveText` | `FText` | Display text for HUD/journal |
|
| `ObjectiveText` | `Text` | Display text for HUD/journal |
|
||||||
| `ObjectiveCategory` | `EObjectiveCategory` | Main, Side, Hidden, Tutorial |
|
| `ObjectiveCategory` | `EObjectiveCategory` | Main, Side, Hidden, Tutorial |
|
||||||
| `PrerequisiteFlags` | `TArray<FGameplayTag>` | Narrative flags required before objective activates |
|
| `PrerequisiteFlags` | `Array<Gameplay Tag>` | Narrative flags required before objective activates |
|
||||||
| `CompletionFlags` | `TArray<FGameplayTag>` | Flags set when objective completes |
|
| `CompletionFlags` | `Array<Gameplay Tag>` | Flags set when objective completes |
|
||||||
| `bIsOptional` | `bool` | Objective can be skipped |
|
| `bIsOptional` | `Boolean` | Objective can be skipped |
|
||||||
| `ObjectiveLocation` | `FVector` | World location for marker |
|
| `ObjectiveLocation` | `Vector` | World location for marker |
|
||||||
| `RewardItems` | `TArray<FPrimaryAssetId>` | Items granted on completion |
|
| `RewardItems` | `Array<Primary Asset Id>` | Items granted on completion |
|
||||||
| `RewardExperience` | `int32` | XP awarded |
|
| `RewardExperience` | `Integer` | XP awarded |
|
||||||
| `LinkedEndingWeight` | `float` | Score contribution to ending evaluation |
|
| `LinkedEndingWeight` | `Float` | Score contribution to ending evaluation |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user