Enhance narrative systems with detailed implementation guides and data-driven structures
- Updated BPC_NarrativeStateSystem with a comprehensive manual implementation guide, including class setup, variable initialization, and function breakdowns. - Expanded BPC_ObjectiveSystem documentation to include a manual implementation guide and detailed function descriptions. - Added a manual implementation guide for BPC_DialoguePlaybackSystem, outlining class setup and function nodes. - Introduced a manual implementation guide for BPC_DialogueChoiceSystem, detailing choice presentation and selection processes. - Enhanced BPC_BranchingConsequenceSystem documentation with a manual implementation guide for consequence evaluation. - Updated BPC_TrialScenarioSystem with a manual implementation guide for scenario management. - Expanded BPC_LoreUnlockSystem documentation to include a manual implementation guide for lore entry management. - Added a manual implementation guide for BP_NarrativeTriggerVolume, detailing trigger volume setup and action execution. - Enhanced BPC_EndingAccumulator documentation with a manual implementation guide for ending evaluation. - Updated BPC_HitReactionSystem with a manual implementation guide for hit reaction management. - Added a manual implementation guide for BPC_RecoilSystem, detailing recoil application and recovery processes. - Introduced DT_ProjectTags.csv to define gameplay tags for various systems, enhancing data-driven design capabilities.
This commit is contained in:
65
docs/blueprints/01-core/DT_ProjectTags.csv
Normal file
65
docs/blueprints/01-core/DT_ProjectTags.csv
Normal file
@@ -0,0 +1,65 @@
|
||||
Name,Tag,DevComment
|
||||
Framework_Player_State_Alive,Framework.Player.State.Alive,
|
||||
Framework_Player_State_Dead,Framework.Player.State.Dead,
|
||||
Framework_Player_State_Hidden,Framework.Player.State.Hidden,
|
||||
Framework_Player_State_Interacting,Framework.Player.State.Interacting,
|
||||
Framework_Player_Stress_Low,Framework.Player.Stress.Low,
|
||||
Framework_Player_Stress_Mid,Framework.Player.Stress.Mid,
|
||||
Framework_Player_Stress_High,Framework.Player.Stress.High,
|
||||
Framework_Player_Stress_Critical,Framework.Player.Stress.Critical,
|
||||
Framework_Player_Posture_Standing,Framework.Player.Posture.Standing,
|
||||
Framework_Player_Posture_Crouching,Framework.Player.Posture.Crouching,
|
||||
Framework_Player_Posture_Prone,Framework.Player.Posture.Prone,
|
||||
Framework_Player_Posture_Vaulting,Framework.Player.Posture.Vaulting,
|
||||
Framework_Interaction_Type_Pickup,Framework.Interaction.Type.Pickup,
|
||||
Framework_Interaction_Type_Door,Framework.Interaction.Type.Door,
|
||||
Framework_Interaction_Type_Drawer,Framework.Interaction.Type.Drawer,
|
||||
Framework_Interaction_Type_Container,Framework.Interaction.Type.Container,
|
||||
Framework_Interaction_Type_Inspect,Framework.Interaction.Type.Inspect,
|
||||
Framework_Interaction_Type_Climb,Framework.Interaction.Type.Climb,
|
||||
Framework_Interaction_Type_Hide,Framework.Interaction.Type.Hide,
|
||||
Framework_Interaction_Type_Use,Framework.Interaction.Type.Use,
|
||||
Framework_Interaction_Type_Combine,Framework.Interaction.Type.Combine,
|
||||
Framework_Interaction_Context_Requires_Key,Framework.Interaction.Context.Requires.Key,
|
||||
Framework_Interaction_Context_Requires_Item,Framework.Interaction.Context.Requires.Item,
|
||||
Framework_Interaction_Context_Locked,Framework.Interaction.Context.Locked,
|
||||
Framework_Interaction_Context_Disabled,Framework.Interaction.Context.Disabled,
|
||||
Framework_Item_Type_Weapon,Framework.Item.Type.Weapon,
|
||||
Framework_Item_Type_Consumable,Framework.Item.Type.Consumable,
|
||||
Framework_Item_Type_KeyItem,Framework.Item.Type.KeyItem,
|
||||
Framework_Item_Type_Document,Framework.Item.Type.Document,
|
||||
Framework_Item_Type_Collectible,Framework.Item.Type.Collectible,
|
||||
Framework_Item_Type_Ammo,Framework.Item.Type.Ammo,
|
||||
Framework_Item_Type_Tool,Framework.Item.Type.Tool,
|
||||
Framework_Item_Slot_PrimaryWeapon,Framework.Item.Slot.PrimaryWeapon,
|
||||
Framework_Item_Slot_SecondaryWeapon,Framework.Item.Slot.SecondaryWeapon,
|
||||
Framework_Item_Slot_Flashlight,Framework.Item.Slot.Flashlight,
|
||||
Framework_Item_Slot_Shield,Framework.Item.Slot.Shield,
|
||||
Framework_Item_Slot_Active,Framework.Item.Slot.Active,
|
||||
Game_Narrative_Flag,Game.Narrative.Flag,
|
||||
Game_Narrative_Phase,Game.Narrative.Phase,
|
||||
Game_Narrative_Choice,Game.Narrative.Choice,
|
||||
Game_Narrative_Ending,Game.Narrative.Ending,
|
||||
Game_Narrative_Flag_PrologueComplete,Game.Narrative.Flag.PrologueComplete,
|
||||
Framework_Objective_Status_Active,Framework.Objective.Status.Active,
|
||||
Framework_Objective_Status_Complete,Framework.Objective.Status.Complete,
|
||||
Framework_Objective_Status_Failed,Framework.Objective.Status.Failed,
|
||||
Framework_Objective_Status_Hidden,Framework.Objective.Status.Hidden,
|
||||
Framework_AI_Alert_None,Framework.AI.Alert.None,
|
||||
Framework_AI_Alert_Suspicious,Framework.AI.Alert.Suspicious,
|
||||
Framework_AI_Alert_Alerted,Framework.AI.Alert.Alerted,
|
||||
Framework_AI_Alert_Engaged,Framework.AI.Alert.Engaged,
|
||||
Framework_AI_Archetype_Patrol,Framework.AI.Archetype.Patrol,
|
||||
Framework_AI_Archetype_Ambush,Framework.AI.Archetype.Ambush,
|
||||
Framework_AI_Archetype_Stalker,Framework.AI.Archetype.Stalker,
|
||||
Framework_AI_Archetype_Passive,Framework.AI.Archetype.Passive,
|
||||
Framework_Save_Type_Checkpoint,Framework.Save.Type.Checkpoint,
|
||||
Framework_Save_Type_HardSave,Framework.Save.Type.HardSave,
|
||||
Framework_Save_Type_AutoSave,Framework.Save.Type.AutoSave,
|
||||
Game_Achievement,Game.Achievement,
|
||||
Game_Achievement_FirstBlood,Game.Achievement.FirstBlood,
|
||||
Game_Environment_Atmosphere,Game.Environment.Atmosphere,
|
||||
Game_Environment_Atmosphere_Eerie,Game.Environment.Atmosphere.Eerie,
|
||||
Game_Environment_Scare,Game.Environment.Scare,
|
||||
Game_Environment_Scare_MirrorJump,Game.Environment.Scare.MirrorJump,
|
||||
Framework_DeathSpace_Active,Framework.DeathSpace.Active,
|
||||
|
@@ -111,4 +111,80 @@ None required. Flat Tag maps suffice.
|
||||
| [`SS_SaveManager`](../05-save/28_SS_SaveManager.md) | [`I_Persistable`](../01-core/29_I_Persistable.md) | Flag persistence across sessions |
|
||||
|
||||
### Reuse Notes
|
||||
The entire system is tag-driven and fully generic. The `Narrative.Flag.*` and `Narrative.Phase.*` namespaces are empty by default — fill them per project with game-specific flags. Add new numeric value types (reputation, corruption, etc.) by simply calling `SetValue` with the appropriate tag. No code/blueprint changes needed.
|
||||
The entire system is tag-driven and fully generic. The `Narrative.Flag.*` and `Narrative.Phase.*` namespaces are empty by default — fill them per project with game-specific flags. Add new numeric value types (reputation, corruption, etc.) by simply calling `SetValue` with the appropriate tag. No code/blueprint changes needed.
|
||||
|
||||
---
|
||||
|
||||
## Manual Implementation Guide
|
||||
|
||||
### Class Setup
|
||||
1. Create Blueprint Class: Parent = `ActorComponent`, Name = `BPC_NarrativeStateSystem`
|
||||
2. Add to Player Character or GameState (if shared)
|
||||
3. Implement Interface: `I_Persistable`
|
||||
|
||||
### Variable Init (BeginPlay)
|
||||
```
|
||||
Event BeginPlay
|
||||
├─ Set NarrativeFlags = empty Map<GameplayTag, Boolean>
|
||||
├─ Set NarrativeValues = empty Map<GameplayTag, Float>
|
||||
├─ Set NarrativeHistory = empty Array<GameplayTag>
|
||||
├─ Set MaxHistorySize = 500
|
||||
└─ If implementing I_Persistable: register with SS_SaveManager
|
||||
```
|
||||
|
||||
### Function Node-by-Node
|
||||
|
||||
#### `SetFlag(Tag: GameplayTag, Value: Boolean)` → `void`
|
||||
```
|
||||
Step 1: OldValue = NarrativeFlags.Find(Tag) or false
|
||||
Step 2: NarrativeFlags.Add(Tag, Value) ← Map Add (overwrites if exists)
|
||||
Step 3: If Value == true AND OldValue == false:
|
||||
NarrativeHistory.Add(Tag)
|
||||
If NarrativeHistory.Length > MaxHistorySize: Remove oldest entry
|
||||
Step 4: Fire OnFlagChanged(Tag, Value)
|
||||
Step 5: Notify I_Persistable: Mark dirty for save
|
||||
```
|
||||
**Nodes:** `Map Find`, `Map Add`, `Array Add`, `Array Length`, `Remove Index 0`
|
||||
|
||||
#### `SetValue(Tag, Value: Float)` → `void`
|
||||
```
|
||||
Step 1: NarrativeValues.Add(Tag, Value)
|
||||
Step 2: Fire OnValueChanged(Tag, Value)
|
||||
```
|
||||
|
||||
#### `GetFlag(Tag)` → `Boolean` *(Pure)*
|
||||
```
|
||||
NarrativeFlags.Find(Tag) → if found: return value, else: return false
|
||||
```
|
||||
|
||||
#### `HasAllFlags(Tags: Array<GameplayTag>)` → `Boolean`
|
||||
```
|
||||
ForEach Tags:
|
||||
If GetFlag(ArrayElement) == false → Return false
|
||||
Return true
|
||||
```
|
||||
|
||||
#### `CollectState()` → `S_WorldObjectState` *(I_Persistable)*
|
||||
```
|
||||
Step 1: Create S_WorldObjectState
|
||||
Step 2: Serialize NarrativeFlags: ForEach → store (Tag, Value) as string pairs in CustomData
|
||||
Step 3: Serialize NarrativeValues similarly
|
||||
Step 4: Return struct
|
||||
```
|
||||
|
||||
#### `RestoreState(Data: S_WorldObjectState)` *(I_Persistable)*
|
||||
```
|
||||
Step 1: Clear NarrativeFlags, NarrativeValues, NarrativeHistory
|
||||
Step 2: Parse CustomData → ForEach entry → NarrativeFlags.Add(Tag, BoolValue)
|
||||
Step 3: Parse values → NarrativeValues.Add(Tag, FloatValue)
|
||||
```
|
||||
|
||||
### Build Checklist
|
||||
- [ ] Create BPC_NarrativeStateSystem, add to Player Character
|
||||
- [ ] Define variables: NarrativeFlags (Map), NarrativeValues (Map), NarrativeHistory (Array), MaxHistorySize
|
||||
- [ ] Implement SetFlag/GetFlag with map operations
|
||||
- [ ] Implement SetValue/GetValue for numeric values
|
||||
- [ ] Implement HasAllFlags/HasAnyFlags for batch queries
|
||||
- [ ] Implement CollectState/RestoreState for I_Persistable
|
||||
- [ ] Create OnFlagChanged/OnValueChanged event dispatchers
|
||||
- [ ] Test: call SetFlag → verify OnFlagChanged fires → other systems react
|
||||
@@ -114,4 +114,75 @@ Tracks active, completed, and failed objectives. Supports main objectives, sub-o
|
||||
| [`SS_SaveManager`](../05-save/28_SS_SaveManager.md) | [`I_Persistable`](../01-core/29_I_Persistable.md) | Objective state persistence |
|
||||
|
||||
### Reuse Notes
|
||||
Define objectives via `DA_ObjectiveData` data assets (one per objective) that specify display text, dependencies, associated narrative flag, and optional/hidden flags. The objective system reads from these assets at level start. Adding a new objective type (like timed objectives) requires adding an `S_TimedObjective` struct extension and a timer function — no system redesign needed.
|
||||
Define objectives via `DA_ObjectiveData` data assets (one per objective) that specify display text, dependencies, associated narrative flag, and optional/hidden flags. The objective system reads from these assets at level start. Adding a new objective type (like timed objectives) requires adding an `S_TimedObjective` struct extension and a timer function — no system redesign needed.
|
||||
|
||||
---
|
||||
|
||||
## Manual Implementation Guide
|
||||
|
||||
### Class Setup
|
||||
1. Create Blueprint Class: Parent = `ActorComponent`, Name = `BPC_ObjectiveSystem`
|
||||
2. Add to Player Character
|
||||
3. Define struct `S_ObjectiveState`: ObjectiveTag, Status (E_ObjectiveStatus), DisplayText (Text), Description (Text), bIsHidden (Boolean), bIsOptional (Boolean), Dependencies (Array<GameplayTag>)
|
||||
|
||||
### Variable Init (BeginPlay)
|
||||
```
|
||||
Event BeginPlay
|
||||
├─ Set AllObjectives = empty Map<GameplayTag, S_ObjectiveState>
|
||||
├─ Set ActiveObjectiveTags, CompletedObjectiveTags, FailedObjectiveTags = empty arrays
|
||||
└─ Get Owner → Cache BPC_NarrativeStateSystem
|
||||
```
|
||||
|
||||
### Function Node-by-Node
|
||||
|
||||
#### `RegisterObjectiveFromDataAsset(ObjTag, DataAsset)` → `void`
|
||||
```
|
||||
Step 1: Read DA_ObjectiveData fields
|
||||
Step 2: Create S_ObjectiveState struct:
|
||||
ObjectiveTag = ObjTag, Status = Inactive
|
||||
DisplayText = DataAsset.ObjectiveText
|
||||
Dependencies = DataAsset.PrerequisiteFlags
|
||||
bIsHidden = (DataAsset.ObjectiveCategory == Hidden)
|
||||
bIsOptional = DataAsset.bIsOptional
|
||||
Step 3: AllObjectives.Add(ObjTag, newState)
|
||||
```
|
||||
|
||||
#### `ActivateObjective(ObjTag)` → `void`
|
||||
```
|
||||
Step 1: State = AllObjectives.Find(ObjTag) — if not found, return
|
||||
Step 2: If State.bIsHidden: return (hidden until revealed)
|
||||
Step 3: Call CheckDependenciesMet(ObjTag) → if not met: set Status=Inactive, return
|
||||
Step 4: State.Status = Active
|
||||
Step 5: ActiveObjectiveTags.Add Unique(ObjTag)
|
||||
Step 6: Fire OnObjectiveActivated(ObjTag, DisplayData)
|
||||
Step 7: Update GS_CoreGameState.ActiveObjectiveTags
|
||||
```
|
||||
|
||||
#### `CompleteObjective(ObjTag)` → `void`
|
||||
```
|
||||
Step 1: Validate Status == Active → if not, return
|
||||
Step 2: State.Status = Complete
|
||||
Step 3: Move: ActiveObjectiveTags.Remove(ObjTag) → CompletedObjectiveTags.Add(ObjTag)
|
||||
Step 4: Fire OnObjectiveCompleted(ObjTag)
|
||||
Step 5: Set narrative flag: BPC_NarrativeStateSystem.SetFlag(ObjTag, true)
|
||||
Step 6: Check dependent objectives: ForEach AllObjectives → if Deps include ObjTag → ActivateObjective
|
||||
```
|
||||
|
||||
#### `GetActiveObjectives()` → `Array<S_ObjectiveDisplayData>`
|
||||
```
|
||||
Create results array → ForEach ActiveObjectiveTags:
|
||||
State = AllObjectives.Find(tag)
|
||||
Create S_ObjectiveDisplayData(Tag, State.DisplayText, false, State.bIsOptional)
|
||||
Add to results
|
||||
Return results
|
||||
```
|
||||
|
||||
### Build Checklist
|
||||
- [ ] Create BPC_ObjectiveSystem, add to Player Character
|
||||
- [ ] Define E_ObjectiveStatus enum and S_ObjectiveState struct
|
||||
- [ ] Implement RegisterObjectiveFromDataAsset
|
||||
- [ ] Implement ActivateObjective with dependency check
|
||||
- [ ] Implement CompleteObjective with cascading activation
|
||||
- [ ] Implement GetActiveObjectives for UI binding
|
||||
- [ ] Sync to GS_CoreGameState.ActiveObjectiveTags
|
||||
- [ ] Test: register objective → activate → complete → dependent activates
|
||||
@@ -119,4 +119,75 @@ Manages the playback of dialogue sequences: line queuing, timing, subtitle routi
|
||||
| [`GI_GameFramework`](../01-core/04_GI_GameFramework.md) | Direct | Set game phase during dialogue |
|
||||
|
||||
### Reuse Notes
|
||||
`DA_DialogueSequence` data assets hold all content — add new sequences per project without touching this system. Voice audio is optional: sequences work without audio (text-only dialogue). Lip-sync data is per-line and can use audio-driven or procedural lip sync.
|
||||
`DA_DialogueSequence` data assets hold all content — add new sequences per project without touching this system. Voice audio is optional: sequences work without audio (text-only dialogue). Lip-sync data is per-line and can use audio-driven or procedural lip sync.
|
||||
|
||||
---
|
||||
|
||||
## Manual Implementation Guide
|
||||
|
||||
### Class Setup
|
||||
1. Create Blueprint Class: Parent = `ActorComponent`, Name = `BPC_DialoguePlaybackSystem`
|
||||
2. Add to Player Character
|
||||
3. Define struct `S_DialogueLine`: SpeakerTag, LineText (Text), VoiceAudio (SoundBase), Duration (Float), FlagToSetOnComplete (GameplayTag), bIsChoicePoint (Boolean)
|
||||
|
||||
### Variable Init (BeginPlay)
|
||||
```
|
||||
Event BeginPlay
|
||||
├─ Set bIsPlaying = false, bIsPaused = false
|
||||
├─ Set LineQueue = empty Array<S_DialogueLine>
|
||||
└─ Cache: BPC_NarrativeStateSystem, SS_AudioManager (via Get Game Instance)
|
||||
```
|
||||
|
||||
### Function Node-by-Node
|
||||
|
||||
#### `PlaySequence(Sequence: DA_DialogueSequence)` → `void`
|
||||
```
|
||||
Step 1: If bIsPlaying → QueueSequence (or return error)
|
||||
Step 2: Validate RequiredFlags: Sequence.RequiredFlags → HasAllFlags → if false, return
|
||||
Step 3: Set bIsPlaying = true
|
||||
Step 4: Load LineQueue from Sequence.Lines array (copy all lines)
|
||||
Step 5: Set CurrentLineIndex = 0
|
||||
Step 6: Fire OnDialogueStarted(Sequence.SequenceTag)
|
||||
Step 7: Call PlayNextLine()
|
||||
```
|
||||
|
||||
#### `PlayNextLine()` → `void`
|
||||
```
|
||||
Step 1: If CurrentLineIndex >= LineQueue.Length:
|
||||
Fire OnSequenceCompleted → Set bIsPlaying=false → Return
|
||||
Step 2: CurrentLine = LineQueue[CurrentLineIndex]
|
||||
Step 3: If CurrentLine.bIsChoicePoint:
|
||||
Fire OnChoicePointReached → hand to BPC_DialogueChoiceSystem → pause here
|
||||
Return (resume when choice returns NextSequenceTag)
|
||||
Step 4: Fire OnLineStarted(CurrentLine)
|
||||
Step 5: If CurrentLine.VoiceAudio valid:
|
||||
SS_AudioManager.PlayDialogue(CurrentLine.VoiceAudio)
|
||||
Step 6: Show subtitle: Fire dispatcher → WBP_SubtitleDisplay.Show(CurrentLine.LineText, SpeakerTag)
|
||||
Step 7: Set Timer (CurrentLine.Duration) → On timer: Call OnLineCompleted()
|
||||
```
|
||||
|
||||
#### `OnLineCompleted()` → `void`
|
||||
```
|
||||
Step 1: If CurrentLine.FlagToSetOnComplete valid:
|
||||
BPC_NarrativeStateSystem.SetFlag(FlagToSetOnComplete, true)
|
||||
Step 2: Fire OnLineCompleted(CurrentLine)
|
||||
Step 3: Increment CurrentLineIndex
|
||||
Step 4: Call PlayNextLine()
|
||||
```
|
||||
|
||||
#### `SkipCurrentLine()` → `void`
|
||||
```
|
||||
Step 1: Clear LineTimer
|
||||
Step 2: Stop VoiceAudio if playing
|
||||
Step 3: Fire OnLineCompleted (skip to next line)
|
||||
```
|
||||
|
||||
### Build Checklist
|
||||
- [ ] Create BPC_DialoguePlaybackSystem, add to Player Character
|
||||
- [ ] Define S_DialogueLine struct and DA_DialogueSequence Data Asset
|
||||
- [ ] Implement PlaySequence with flag validation
|
||||
- [ ] Implement PlayNextLine with line queue + timer + audio + subtitle
|
||||
- [ ] Implement choice point handoff to DialogueChoiceSystem
|
||||
- [ ] Implement SkipCurrentLine/SkipSequence
|
||||
- [ ] Wire subtitles to WBP_SubtitleDisplay via dispatcher
|
||||
- [ ] Test: trigger dialogue → lines play sequentially → subtitles show → audio plays
|
||||
@@ -104,4 +104,77 @@ Presents branching dialogue choices to the player and routes the selected respon
|
||||
| [`BPC_BranchingConsequenceSystem`](42_BPC_BranchingConsequenceSystem.md) | Dispatcher | Choice flag changes trigger consequence evaluation |
|
||||
|
||||
### Reuse Notes
|
||||
Choice filtering by RequiredFlagTag allows context-sensitive dialogue without branching logic in the system. Choices can be hidden (e.g., secret dialogue options only appear if player has a specific lore unlock). The system handles all choice patterns: timed, untimed, locked, hidden, and priority-sorted.
|
||||
Choice filtering by RequiredFlagTag allows context-sensitive dialogue without branching logic in the system. Choices can be hidden (e.g., secret dialogue options only appear if player has a specific lore unlock). The system handles all choice patterns: timed, untimed, locked, hidden, and priority-sorted.
|
||||
|
||||
---
|
||||
|
||||
## Manual Implementation Guide
|
||||
|
||||
### Class Setup
|
||||
1. Create Blueprint Class: Parent = `ActorComponent`, Name = `BPC_DialogueChoiceSystem`
|
||||
2. Add to Player Character
|
||||
3. Define struct `S_DialogueChoice`: ChoiceText (Text), ResultFlagTag (GameplayTag), NextSequenceTag (GameplayTag), RequiredFlagTag (GameplayTag), Priority (Integer)
|
||||
|
||||
### Variable Init (BeginPlay)
|
||||
```
|
||||
Event BeginPlay
|
||||
├─ Set CurrentChoices = empty array
|
||||
├─ Set bChoiceActive = false
|
||||
├─ Set ChoiceTimeLimit = 0.0
|
||||
└─ Cache: BPC_NarrativeStateSystem, BPC_DialoguePlaybackSystem
|
||||
```
|
||||
|
||||
### Function Node-by-Node
|
||||
|
||||
#### `PresentChoices(Choices: Array<S_DialogueChoice>, TimeLimit: Float)` → `void`
|
||||
```
|
||||
Step 1: Filtered = GetValidChoices(Choices) ← filter by narrative flags
|
||||
Step 2: If Filtered.Length == 0: auto-cancel (or select hidden default)
|
||||
Step 3: Sort Filtered by Priority (descending)
|
||||
Step 4: Set CurrentChoices = Filtered
|
||||
Step 5: Set bChoiceActive = true
|
||||
Step 6: Fire OnChoicesPresented(CurrentChoices) → WBP_DialogueChoiceDisplay shows UI
|
||||
Step 7: If TimeLimit > 0:
|
||||
Set ChoiceTimeLimit = TimeLimit, TimeRemaining = TimeLimit
|
||||
Start looping timer (0.1s):
|
||||
TimeRemaining -= 0.1 → update UI
|
||||
If TimeRemaining <= 0 → Call OnChoiceTimedOut
|
||||
```
|
||||
|
||||
#### `GetValidChoices(Choices)` → `Array<S_DialogueChoice>`
|
||||
```
|
||||
ForEach Choices:
|
||||
If Choice.RequiredFlagTag valid:
|
||||
Call BPC_NarrativeStateSystem.GetFlag(Choice.RequiredFlagTag)
|
||||
If flag is true → Add to results
|
||||
Else: Add to results (no requirement)
|
||||
Return results
|
||||
```
|
||||
|
||||
#### `SelectChoice(ChoiceIndex: Integer)` → `void`
|
||||
```
|
||||
Step 1: Validate ChoiceIndex < CurrentChoices.Length
|
||||
Step 2: Selected = CurrentChoices[ChoiceIndex]
|
||||
Step 3: Clear ChoiceTimer
|
||||
Step 4: Fire OnChoiceSelected(Selected, ChoiceIndex)
|
||||
Step 5: Call ProcessSelectedChoice(Selected)
|
||||
```
|
||||
|
||||
#### `ProcessSelectedChoice(Choice)` → `GameplayTag` (NextSequenceTag)
|
||||
```
|
||||
Step 1: If Choice.ResultFlagTag valid:
|
||||
BPC_NarrativeStateSystem.SetFlag(Choice.ResultFlagTag, true)
|
||||
Step 2: Set bChoiceActive = false
|
||||
Step 3: Fire OnChoiceCompleted(Choice.NextSequenceTag)
|
||||
Step 4: Return Choice.NextSequenceTag ← caller (DialoguePlayback) plays this sequence
|
||||
```
|
||||
|
||||
### Build Checklist
|
||||
- [ ] Create BPC_DialogueChoiceSystem, add to Player Character
|
||||
- [ ] Define S_DialogueChoice struct
|
||||
- [ ] Implement PresentChoices with flag filtering + timer
|
||||
- [ ] Implement GetValidChoices with RequiredFlagTag check
|
||||
- [ ] Implement SelectChoice with timer clear
|
||||
- [ ] Implement ProcessSelectedChoice: set flag → return next sequence
|
||||
- [ ] Wire to WBP_DialogueChoiceDisplay via dispatcher
|
||||
- [ ] Test: dialogue choice appears → player selects → flag set → next dialogue plays
|
||||
@@ -124,4 +124,55 @@ Evaluates narrative consequences when flags change. Listens for flag changes acr
|
||||
| [`BPC_LoreUnlockSystem`](46_BPC_LoreUnlockSystem.md) | Direct | Unlock lore entries |
|
||||
|
||||
### Reuse Notes
|
||||
Consequence rules are data-driven via `DA_ConsequenceRule`, enabling designers to author branch logic without blueprint edits. The system supports both immediate and delayed consequences, allowing dramatic pacing. Priority sorting ensures critical consequences fire first. Blocking flag prevents race conditions between sequential narrative beats.
|
||||
Consequence rules are data-driven via `DA_ConsequenceRule`, enabling designers to author branch logic without blueprint edits. The system supports both immediate and delayed consequences, allowing dramatic pacing. Priority sorting ensures critical consequences fire first. Blocking flag prevents race conditions between sequential narrative beats.
|
||||
|
||||
---
|
||||
|
||||
## Manual Implementation Guide
|
||||
|
||||
### Class Setup
|
||||
1. Create Blueprint Class: Parent = `ActorComponent`, Name = `BPC_BranchingConsequenceSystem`
|
||||
2. Add to Player Character
|
||||
|
||||
### Variable Init (BeginPlay)
|
||||
```
|
||||
Event BeginPlay
|
||||
├─ Set ConsequenceRules = empty array (loaded from DA_ConsequenceRule registry)
|
||||
├─ Set PendingConsequences = empty array
|
||||
├─ Set MaxConcurrentConsequences = 3
|
||||
└─ Cache: BPC_NarrativeStateSystem, BPC_DialoguePlaybackSystem, BPC_ObjectiveSystem, BPC_CutsceneBridge, BPC_LoreUnlockSystem
|
||||
```
|
||||
|
||||
### Function Node-by-Node
|
||||
|
||||
#### `OnFlagChanged(Fage: GameplayTag, NewValue: Boolean)` → `void` *(Bind to NarrativeStateSystem.OnFlagChanged)*
|
||||
```
|
||||
Step 1: If NewValue == false → Return (only react to true flags)
|
||||
Step 2: ForEach ConsequenceRules:
|
||||
If Rule.TriggerFlag == Flag:
|
||||
Create FConsequencePayload(RuleRef=Rule, TriggerFlag=Flag, bIsImmediate=Rule.bIsImmediate, FireTime=Now+Delay)
|
||||
If bIsImmediate: Add to pending, call ProcessPendingConsequences
|
||||
Else: Add to PendingConsequences queue with timer
|
||||
```
|
||||
|
||||
#### `ProcessPendingConsequences()` → `void`
|
||||
```
|
||||
Step 1: Sort PendingConsequences by Rule.Priority (descending)
|
||||
Step 2: ForEach PendingConsequences (up to MaxConcurrentConsequences):
|
||||
Switch on Rule.ActionType:
|
||||
PlayDialogue → BPC_DialoguePlaybackSystem.PlaySequence(Rule.DialogueSequence)
|
||||
SetObjective → BPC_ObjectiveSystem.ActivateObjective(Rule.ObjectiveTag)
|
||||
UnlockLore → BPC_LoreUnlockSystem.UnlockEntry(Rule.LoreTag)
|
||||
TriggerCutscene → BPC_CutsceneBridge.PlayCutscene(Rule.CutsceneData)
|
||||
SetFlag → BPC_NarrativeStateSystem.SetFlag(Rule.FlagTag, true)
|
||||
GrantItem → BPC_InventorySystem.AddItem(Rule.ItemAsset, 1)
|
||||
Step 3: Remove processed from queue
|
||||
```
|
||||
|
||||
### Build Checklist
|
||||
- [ ] Create component, add to Player Character
|
||||
- [ ] Bind to BPC_NarrativeStateSystem.OnFlagChanged
|
||||
- [ ] Load DA_ConsequenceRule assets into ConsequenceRules array
|
||||
- [ ] Implement OnFlagChanged: match trigger flag → create payload
|
||||
- [ ] Implement ProcessPendingConsequences with action switch
|
||||
- [ ] Create DA_ConsequenceRule Data Asset type with action enum and fields
|
||||
@@ -128,4 +128,61 @@ Manages trial-based gameplay scenarios: combat gauntlets, escape sequences, inve
|
||||
| Phase 8 AI | Indirect | Spawn/despawn enemy actors by tag |
|
||||
|
||||
### Reuse Notes
|
||||
Scenarios are fully data-driven via `DA_ScenarioData`. Designers configure spawn tags, objective tags, time limits, and cleanup actions without blueprint modifications. Supports mixed scenarios: combat + investigation, timed + untimed, survival + objective.
|
||||
Scenarios are fully data-driven via `DA_ScenarioData`. Designers configure spawn tags, objective tags, time limits, and cleanup actions without blueprint modifications. Supports mixed scenarios: combat + investigation, timed + untimed, survival + objective.
|
||||
|
||||
---
|
||||
|
||||
## Manual Implementation Guide
|
||||
|
||||
### Class Setup
|
||||
1. Create Blueprint Class: Parent = `ActorComponent`, Name = `BPC_TrialScenarioSystem`
|
||||
2. Add to Player Character or GameMode
|
||||
|
||||
### Variable Init (BeginPlay)
|
||||
```
|
||||
Event BeginPlay
|
||||
├─ Set ScenarioState = Inactive
|
||||
└─ Cache: BPC_NarrativeStateSystem, BPC_ObjectiveSystem
|
||||
```
|
||||
|
||||
### Function Node-by-Node
|
||||
|
||||
#### `StartScenario(ScenarioData: DA_ScenarioData)` → `void`
|
||||
```
|
||||
Step 1: If ScenarioState != Inactive → Return (already running)
|
||||
Step 2: Set ActiveScenario = ScenarioData, ScenarioState = SetupRunning
|
||||
Step 3: Create checkpoint via SS_SaveManager (auto-save before trial)
|
||||
Step 4: Spawn enemies: ForEach ScenarioData.SpawnTags → Spawn Actor from Class at SpawnPoints
|
||||
Step 5: Lock doors/block exits: ForEach ScenarioData.BlockActor → Set enabled=false
|
||||
Step 6: If ScenarioData.bShowObjective: ActivateObjective(ScenarioData.ObjectiveTag)
|
||||
Step 7: Set ScenarioStartTime = Get Game Time
|
||||
Step 8: ScenarioState = ActiveRunning
|
||||
Step 9: Fire OnScenarioStarted
|
||||
```
|
||||
|
||||
#### `EvaluateScenario()` → `void` *(Called on Tick or timer)*
|
||||
```
|
||||
Step 1: If ScenarioState != ActiveRunning → Return
|
||||
Step 2: Check success conditions:
|
||||
If ScenarioData.SuccessFlags: HasAllFlags? → Call OnScenarioSuccess()
|
||||
If ScenarioData.SuccessKillCount: enemies killed >= count? → Success
|
||||
Step 3: Check failure conditions:
|
||||
If ScenarioData.TimeLimit > 0 AND (Now - StartTime) >= TimeLimit → OnScenarioFailure("Time expired")
|
||||
If player death detected → OnScenarioFailure("Player died")
|
||||
```
|
||||
|
||||
#### `OnScenarioSuccess()` → `void`
|
||||
```
|
||||
Step 1: ScenarioState = Success
|
||||
Step 2: ForEach ScenarioData.SuccessFlags → BPC_NarrativeStateSystem.SetFlag(flag, true)
|
||||
Step 3: If Objective active: CompleteObjective(ObjectiveTag)
|
||||
Step 4: Fire OnScenarioCompleted(true)
|
||||
Step 5: Call CleanupScenario()
|
||||
```
|
||||
|
||||
### Build Checklist
|
||||
- [ ] Create component with DA_ScenarioData reference
|
||||
- [ ] Implement StartScenario: setup → spawn → lock → objective → active
|
||||
- [ ] Implement EvaluateScenario: check win/loss conditions per tick
|
||||
- [ ] Implement OnScenarioSuccess/Failure with flag setting
|
||||
- [ ] Implement CleanupScenario: despawn enemies, unlock doors
|
||||
@@ -135,4 +135,61 @@ Manages the discovery, unlocking, and tracking of lore entries (documents, notes
|
||||
| [Save System](../04-save/32_SS_SaveManager.md) | Direct (via I_Persistable) | Save unlocked/read state |
|
||||
|
||||
### Reuse Notes
|
||||
Pure data-driven: all lore content is defined in `DA_LoreEntryData` assets. The system requires zero blueprint changes to add new lore entries. Supports any mix of discovery methods (pickup, narrative flag, location trigger, consequence). Lore categories are defined by gameplay tags, allowing flexible grouping without code changes.
|
||||
Pure data-driven: all lore content is defined in `DA_LoreEntryData` assets. The system requires zero blueprint changes to add new lore entries. Supports any mix of discovery methods (pickup, narrative flag, location trigger, consequence). Lore categories are defined by gameplay tags, allowing flexible grouping without code changes.
|
||||
|
||||
---
|
||||
|
||||
## Manual Implementation Guide
|
||||
|
||||
### Class Setup
|
||||
1. Create Blueprint Class: Parent = `ActorComponent`, Name = `BPC_LoreUnlockSystem`
|
||||
2. Add to Player Character
|
||||
|
||||
### Variable Init (BeginPlay)
|
||||
```
|
||||
Event BeginPlay
|
||||
├─ Set AllLoreEntries = loaded DA_LoreEntryData array
|
||||
├─ Set UnlockedLoreTags = empty Set<GameplayTag>
|
||||
├─ Set ReadLoreTags = empty Set<GameplayTag>
|
||||
├─ Set TotalLoreCount = AllLoreEntries.Length
|
||||
└─ Bind to BPC_NarrativeStateSystem.OnFlagChanged → check for lore unlock triggers
|
||||
```
|
||||
|
||||
### Function Node-by-Node
|
||||
|
||||
#### `UnlockLoreByTag(LoreTag: GameplayTag)` → `void`
|
||||
```
|
||||
Step 1: If UnlockedLoreTags.Contains(LoreTag) → Return (already unlocked)
|
||||
Step 2: UnlockedLoreTags.Add(LoreTag)
|
||||
Step 3: Find entry in AllLoreEntries by LoreTag
|
||||
Step 4: Add to NewLoreQueue for notification
|
||||
Step 5: Increment TotalUnlockedCount
|
||||
Step 6: Fire OnLoreUnlocked(LoreTag, EntryData)
|
||||
Step 7: Show notification: "New lore discovered: {Entry.Title}"
|
||||
```
|
||||
|
||||
#### `MarkLoreAsRead(LoreTag: GameplayTag)` → `void`
|
||||
```
|
||||
Step 1: If NOT UnlockedLoreTags.Contains(LoreTag) → Return
|
||||
Step 2: If NOT ReadLoreTags.Contains(LoreTag):
|
||||
ReadLoreTags.Add(LoreTag)
|
||||
Decrement UnreadCount
|
||||
Fire OnLoreRead(LoreTag)
|
||||
```
|
||||
|
||||
#### `GetLoreByCategory(CategoryTag: GameplayTag)` → `Array<DA_LoreEntryData>` *(Pure)*
|
||||
```
|
||||
ForEach AllLoreEntries:
|
||||
If Entry.CategoryTag == CategoryTag AND UnlockedLoreTags.Contains(Entry.LoreTag):
|
||||
Add to results
|
||||
Return results
|
||||
```
|
||||
|
||||
### Build Checklist
|
||||
- [ ] Create BPC_LoreUnlockSystem, add to Player Character
|
||||
- [ ] Define DA_LoreEntryData Data Asset (Title, Body, Category, RequiredFlag, LoreTag)
|
||||
- [ ] Implement UnlockLoreByTag with dedup check
|
||||
- [ ] Implement MarkLoreAsRead with unread counter
|
||||
- [ ] Implement category-based filtering for UI
|
||||
- [ ] Bind to NarrativeStateSystem for flag-triggered unlocks
|
||||
- [ ] Test: pick up lore item → notification appears → journal entry unlocks
|
||||
@@ -156,4 +156,45 @@ A level-placed trigger volume that detects player overlap and fires narrative ac
|
||||
### Reuse Notes
|
||||
This is the primary level-design tool for narrative gating. Designers place volumes, configure action lists, and set prerequisite flags — no blueprint editing required. Supports all narrative action types. The `CustomEvent` action type allows designers to bind custom level blueprint logic when no predefined action fits.
|
||||
|
||||
- Renamed from `47_BPC_NarrativeTriggerVolume` to `BP_NarrativeTriggerVolume` per Master naming convention.
|
||||
- Renamed from `47_BPC_NarrativeTriggerVolume` to `BP_NarrativeTriggerVolume` per Master naming convention.
|
||||
|
||||
---
|
||||
|
||||
## Manual Implementation Guide
|
||||
|
||||
### Class Setup
|
||||
1. Create Blueprint Class: Parent = `Actor`, Name = `BP_NarrativeTriggerVolume`
|
||||
2. Add `BoxComponent` as Root (name: `CollisionBox`)
|
||||
3. Set Collision Preset = `OverlapOnlyPawn`
|
||||
|
||||
### Function Node-by-Node
|
||||
|
||||
#### `Event ActorBeginOverlap(OtherActor)` → `void`
|
||||
```
|
||||
Step 1: If NOT bIsEnabled → Return
|
||||
Step 2: Cast OtherActor to Player Character → if fail, Return
|
||||
Step 3: Check RequiredFlags → if any missing, Return
|
||||
Step 4: Check ExclusiveFlags → if any set, Return
|
||||
Step 5: TriggerType == Once AND bHasTriggered? → Return
|
||||
Step 6: Cooldown active? → Return
|
||||
Step 7: Call ExecuteActions(TriggerActions)
|
||||
Step 8: Set bHasTriggered = true, LastTriggerTime = Now
|
||||
```
|
||||
|
||||
#### `ExecuteActions(Actions: Array<FTriggerActionEntry>)` → `void`
|
||||
```
|
||||
ForEach Actions → Switch on ActionType:
|
||||
SetFlag → NarrativeState.SetFlag(Tag, true)
|
||||
PlayDialogue → DialoguePlayback.PlaySequence(Asset)
|
||||
PlayCutscene → CutsceneBridge.PlayCutscene(Asset)
|
||||
ActivateObjective → ObjectiveSystem.ActivateObjective(Tag)
|
||||
StartTrial → TrialScenario.StartScenario(Asset)
|
||||
UnlockLore → LoreUnlock.UnlockLoreByTag(Tag)
|
||||
GrantItem → Inventory.AddItem(Asset, 1)
|
||||
```
|
||||
|
||||
### Build Checklist
|
||||
- [ ] Create BP_NarrativeTriggerVolume with BoxComponent
|
||||
- [ ] Define ETriggerActionType enum and FTriggerActionEntry struct
|
||||
- [ ] Bind ActorBeginOverlap → check conditions → ExecuteActions
|
||||
- [ ] Test: walk into volume → dialogue plays → flag sets → one-shot prevents re-fire
|
||||
@@ -133,4 +133,66 @@ Evaluates all narrative flags accumulated throughout gameplay and determines whi
|
||||
Completely data-driven. Designers create DA_EndingData assets with required flags, exclusive flags, score thresholds, and score entries. The priority system ensures specific endings (e.g., "True Ending") override general ones. The system supports both linear and score-based ending evaluation simultaneously.
|
||||
|
||||
- Renamed from `45_BPC_EndingAccumulatorSystem` to `BPC_EndingAccumulator` per Master naming convention.
|
||||
- Cross-references updated: `BPC_CheckpointSystem` → `BP_Checkpoint`.
|
||||
- Cross-references updated: `BPC_CheckpointSystem` → `BP_Checkpoint`.
|
||||
|
||||
---
|
||||
|
||||
## Manual Implementation Guide
|
||||
|
||||
### Class Setup
|
||||
1. Create Blueprint Class: Parent = `ActorComponent`, Name = `BPC_EndingAccumulator`
|
||||
2. Add to Player Character or GameState
|
||||
|
||||
### Variable Init (BeginPlay)
|
||||
```
|
||||
Event BeginPlay
|
||||
├─ Set EndingScores = empty Map<GameplayTag, Float>
|
||||
├─ Load all DA_EndingData assets into EndingDefinitions array
|
||||
└─ Bind to BPC_NarrativeStateSystem.OnFlagChanged
|
||||
```
|
||||
|
||||
### Function Node-by-Node
|
||||
|
||||
#### `OnNarrativeFlagChanged(Flag: GameplayTag, NewValue: Boolean)` → `void`
|
||||
```
|
||||
Step 1: If NewValue == false → Return (only accumulate on true flags)
|
||||
Step 2: ForEach EndingDefinitions:
|
||||
ForEach Entry in Ending.ScoreEntries:
|
||||
If Entry.FlagTag == Flag:
|
||||
EndingScores[Ending.EndingTag] += Entry.Weight
|
||||
Break inner loop
|
||||
Step 3: Fire OnEndingScoresUpdated(EndingScores)
|
||||
```
|
||||
|
||||
#### `EvaluateEndings()` → `DA_EndingData`
|
||||
```
|
||||
Step 1: QualifiedList = empty array
|
||||
Step 2: ForEach EndingDefinitions:
|
||||
If EndingScores[Ending.EndingTag] >= Ending.RequiredScore:
|
||||
Check Ending.RequiredFlags: all set? → Add to QualifiedList
|
||||
Check Ending.ExclusiveFlags: any set? → Skip
|
||||
Step 3: Sort QualifiedList by Priority (descending)
|
||||
Step 4: Return QualifiedList[0] (highest priority qualified ending)
|
||||
```
|
||||
|
||||
#### `GetQualifiedEndings()` → `Array<DA_EndingData>` *(Pure)*
|
||||
```
|
||||
Same logic as EvaluateEndings but returns all qualified, not just best
|
||||
```
|
||||
|
||||
#### `TriggerGameEnding(OverrideTag: GameplayTag)` → `void`
|
||||
```
|
||||
Step 1: If OverrideTag valid → SelectedEnding = Find(OverrideTag)
|
||||
Else: SelectedEnding = EvaluateEndings()
|
||||
Step 2: Fire OnGameEndingTriggered(SelectedEnding)
|
||||
Step 3: GM_CoreGameMode.TriggerEnding(SelectedEnding.EndingTag)
|
||||
→ initiates cutscene, game-over screen, credits
|
||||
```
|
||||
|
||||
### Build Checklist
|
||||
- [ ] Create BPC_EndingAccumulator
|
||||
- [ ] Define DA_EndingData with ScoreEntries, RequiredFlags, ExclusiveFlags, RequiredScore, Priority
|
||||
- [ ] Bind to NarrativeStateSystem.OnFlagChanged → accumulate scores
|
||||
- [ ] Implement EvaluateEndings with qualification gating
|
||||
- [ ] Wire to GM_CoreGameMode.TriggerEnding
|
||||
- [ ] Test: make choices throughout game → check ending scores → trigger end → correct ending plays
|
||||
@@ -189,4 +189,65 @@ flowchart TD
|
||||
|
||||
---
|
||||
|
||||
*Specification based on Master Section 5.7, line 1821.*
|
||||
*Specification based on Master Section 5.7, line 1821.*
|
||||
|
||||
---
|
||||
|
||||
## Manual Implementation Guide
|
||||
|
||||
### Class Setup
|
||||
1. Create Blueprint Class: Parent = `ActorComponent`, Name = `BPC_HitReactionSystem`
|
||||
2. Add to Player Character (or EnemyBase)
|
||||
3. Create HitReactionMontages per damage type: Physical, Fire, Explosive, etc.
|
||||
|
||||
### Variable Init (BeginPlay)
|
||||
```
|
||||
Event BeginPlay
|
||||
├─ Set CurrentTrauma = 0.0, bIsReacting = false
|
||||
├─ Populate HitReactionMontages map (E_DamageType → AnimMontage)
|
||||
├─ Cache: BPC_HealthSystem (bind OnDamageTaken), BPC_CameraStateLayer
|
||||
└─ Start TraumaDecay timer (0.1s loop)
|
||||
```
|
||||
|
||||
### Function Node-by-Node
|
||||
|
||||
#### `ProcessHitReaction(DamageEvent: S_DamageEvent)` → `void`
|
||||
```
|
||||
Step 1: If bIsReacting → Return (already playing reaction)
|
||||
Step 2: Set LastDamageDirection = DamageEvent.HitNormal * -1 ← direction damage came from
|
||||
Step 3: If DamageEvent.Amount >= RagdollThreshold:
|
||||
Play ragdoll blend: ABP → Set bRagdoll=true, physics blend
|
||||
Set bIsReacting = true → timer to recover
|
||||
Return
|
||||
Step 4: If DamageEvent.Amount >= FlinchThreshold:
|
||||
Select montage: HitReactionMontages[DamageEvent.DamageType]
|
||||
If multiple: pick NextReactionIndex, cycle it
|
||||
Play Montage with blend from LastDamageDirection
|
||||
Set bIsReacting = true
|
||||
Step 5: Call ApplyTrauma(DamageEvent.Amount * 0.5)
|
||||
Step 6: Fire OnHitReactionPlayed(DamageEvent)
|
||||
Step 7: On montage complete → Set bIsReacting = false
|
||||
```
|
||||
|
||||
#### `ApplyTrauma(Amount: Float)` → `void`
|
||||
```
|
||||
Step 1: CurrentTrauma = Min(100, CurrentTrauma + Amount)
|
||||
Step 2: Push to camera: BPC_CameraStateLayer.ApplyPostProcessOverride(TraumaVignette, CurrentTrauma/100)
|
||||
Step 3: Fire OnTraumaChanged(CurrentTrauma)
|
||||
```
|
||||
|
||||
#### `TraumaDecay Tick` → `void` *(Timer 0.1s)*
|
||||
```
|
||||
CurrentTrauma = Max(0, CurrentTrauma - TraumaDecayRate * 0.1)
|
||||
Update camera effect intensity
|
||||
If zero: clear post-process override
|
||||
```
|
||||
|
||||
### Build Checklist
|
||||
- [ ] Create BPC_HitReactionSystem, add to Player Character
|
||||
- [ ] Create hit reaction montages per damage type
|
||||
- [ ] Implement ProcessHitReaction with threshold branching
|
||||
- [ ] Implement ApplyTrauma with camera effect push
|
||||
- [ ] Set up trauma decay timer
|
||||
- [ ] Bind to BPC_HealthSystem.OnDamageTaken
|
||||
- [ ] Test: take small hit → flinch; take big hit → ragdoll; trauma decays over time
|
||||
@@ -49,4 +49,53 @@
|
||||
|
||||
- RecoilPattern is a CurveVector from DA_WeaponData — swap per weapon
|
||||
- For hitscan weapons, call ApplyRecoil on every shot. For projectile, call on fire release
|
||||
- Recovery runs on tick when bIsRecovering; disable tick at rest for performance
|
||||
- Recovery runs on tick when bIsRecovering; disable tick at rest for performance
|
||||
|
||||
---
|
||||
|
||||
## Manual Implementation Guide
|
||||
|
||||
### Class Setup
|
||||
1. Create Blueprint Class: Parent = `ActorComponent`, Name = `BPC_RecoilSystem`
|
||||
2. Attach to `BP_RangedWeapon` or Player Character
|
||||
3. **⚠️ Local only** — recoil is cosmetic, not replicated
|
||||
|
||||
### Variable Init (BeginPlay)
|
||||
```
|
||||
Event BeginPlay
|
||||
├─ Set CurrentRecoilOffset = (0, 0)
|
||||
├─ Set bIsRecovering = false
|
||||
├─ Read RecoilPattern from DA_WeaponData
|
||||
└─ Cache: BPC_CameraStateLayer (from Player)
|
||||
```
|
||||
|
||||
### Function Node-by-Node
|
||||
|
||||
#### `ApplyRecoil(ShotCount: Integer, bADS: Boolean)` → `void`
|
||||
```
|
||||
Step 1: Sample RecoilPattern curve at ShotCount (or accumulated counter)
|
||||
Step 2: Get curve output: Pitch = X channel, Yaw = Y channel
|
||||
Step 3: If bADS: Pitch *= ADSRecoilMultiplier, Yaw *= ADSRecoilMultiplier
|
||||
Step 4: CurrentRecoilOffset.X += Pitch, CurrentRecoilOffset.Y += Yaw
|
||||
Step 5: Push to camera:
|
||||
BPC_CameraStateLayer.AddControllerPitch(Pitch) ← or PlayCameraShake
|
||||
BPC_CameraStateLayer.AddControllerYaw(Yaw)
|
||||
Step 6: Set bIsRecovering = true
|
||||
Step 7: Set Timer (0.05s loop) → TickRecovery ← timer runs while recovering
|
||||
```
|
||||
|
||||
#### `TickRecovery(DeltaTime: Float)` → `void`
|
||||
```
|
||||
Step 1: CurrentRecoilOffset = Lerp(CurrentRecoilOffset, Vector2D(0,0), RecoverySpeed * DeltaTime)
|
||||
Step 2: If CurrentRecoilOffset nearly zero (< 0.01):
|
||||
ResetRecoil()
|
||||
Clear Recovery timer
|
||||
```
|
||||
|
||||
### Build Checklist
|
||||
- [ ] Create BPC_RecoilSystem, attach to Player
|
||||
- [ ] Create RecoilPattern CurveVector per weapon (X=pitch, Y=yaw)
|
||||
- [ ] Implement ApplyRecoil with curve sampling + ADS multiplier
|
||||
- [ ] Implement TickRecovery with lerp toward zero
|
||||
- [ ] Wire to BPC_FirearmSystem.OnFire
|
||||
- [ ] Test: fire weapon → camera kicks up → recovers to center
|
||||
Reference in New Issue
Block a user