add blueprints
This commit is contained in:
333
docs/blueprints/02-player/09_BPC_StaminaSystem.md
Normal file
333
docs/blueprints/02-player/09_BPC_StaminaSystem.md
Normal file
@@ -0,0 +1,333 @@
|
||||
# 09 — Stamina System (`BPC_StaminaSystem`)
|
||||
|
||||
## Purpose
|
||||
Manages the player's stamina pool — drain during sprinting, jumping, and special actions; regeneration during idle/walking. Prevents action spamming by enforcing minimum stamina thresholds. Drives exhaustion VFX and audio cues.
|
||||
|
||||
## Dependencies
|
||||
- **Requires:** `GI_GameFramework` (phase checks), `FL_GameUtilities` (tag queries)
|
||||
- **Required By:** `BPC_MovementStateSystem` (stamina affects max speed), `WBP_HUD` (stamina bar), `BPC_PlayerMetricsTracker` (exhaustion tracking)
|
||||
- **Engine/Plugin Requirements:** GameplayTags, Timers
|
||||
|
||||
## Class Info
|
||||
| Property | Value |
|
||||
|----------|-------|
|
||||
| **Parent Class** | `ActorComponent` |
|
||||
| **Class Type** | Blueprint Component |
|
||||
| **Asset Path** | `Content/Framework/Player/BPC_StaminaSystem` |
|
||||
| **Implements Interfaces** | None |
|
||||
|
||||
---
|
||||
|
||||
## 1. Enums
|
||||
|
||||
### `E_ExhaustionState`
|
||||
| Value | Description |
|
||||
|-------|-------------|
|
||||
| `Normal = 0` | Full stamina operation |
|
||||
| `Low = 1` | Below LowThreshold, heavy breathing, slightly reduced regen delay |
|
||||
| `Exhausted = 2` | Below ExhaustedThreshold, cannot sprint, slow regen |
|
||||
|
||||
### `E_StaminaActionType`
|
||||
| Value | Description |
|
||||
|-------|-------------|
|
||||
| `Sprint = 0` | Continuous drain while sprinting |
|
||||
| `Jump = 1` | One-time drain on jump |
|
||||
| `Climb = 2` | Continuous drain while climbing |
|
||||
| `SpecialAttack = 3` | One-time drain for strong melee |
|
||||
| `Dodge = 4` | One-time drain for dodging |
|
||||
| `Environment = 5` | Drain from external sources (cold, poison) |
|
||||
|
||||
---
|
||||
|
||||
## 2. Structs
|
||||
|
||||
### `S_StaminaDrainRate`
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `ActionType` | `E_StaminaActionType` | Which action this rate applies to |
|
||||
| `DrainPerSecond` | `Float` | Stamina drained per second (continuous) |
|
||||
| `DrainFlat` | `Float` | Stamina drained once per use (one-shot) |
|
||||
| `MinStamina` | `Float` | Minimum stamina required to perform this action |
|
||||
| `CooldownAfterUse` | `Float` | Seconds before this action can be used again |
|
||||
|
||||
### `S_StaminaRegenSettings`
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `RegenRate` | `Float` | Stamina recovered per second |
|
||||
| `RegenDelay` | `Float` | Seconds after last drain before regen begins |
|
||||
| `ExhaustedRegenRate` | `Float` | Slower recovery while exhausted |
|
||||
| `ExhaustedRegenDelay` | `Float` | Longer delay while exhausted |
|
||||
| `RegenBlockedWhileMoving` | `Boolean` | If true, regen pauses while moving above walk speed |
|
||||
|
||||
---
|
||||
|
||||
## 3. Variables
|
||||
|
||||
### Configuration (Instance Editable, Expose On Spawn)
|
||||
|
||||
| Variable | Type | Default | Category | Description |
|
||||
|----------|------|---------|----------|-------------|
|
||||
| `MaxStamina` | `Float` | `100.0` | `Stamina Config` | Maximum stamina pool |
|
||||
| `DefaultDrainRates` | `Array<S_StaminaDrainRate>` | `[]` | `Stamina Config` | Base drain rates for each action type |
|
||||
| `RegenSettings` | `S_StaminaRegenSettings` | `(15.0, 2.0, 5.0, 4.0, true)` | `Stamina Config` | Regeneration behaviour |
|
||||
| `LowThreshold` | `Float` | `0.30` | `Stamina Config` | Fraction of MaxStamina for Low state |
|
||||
| `ExhaustedThreshold` | `Float` | `0.10` | `Stamina Config` | Fraction of MaxStamina for Exhausted state |
|
||||
| `bCanExhaust` | `Boolean` | `true` | `Stamina Config` | Allow exhaustion state |
|
||||
|
||||
### Internal (Private / Protected, No Expose)
|
||||
|
||||
| Variable | Type | Default | Category | Description |
|
||||
|----------|------|---------|----------|-------------|
|
||||
| `CurrentStamina` | `Float` | `100.0` | `Stamina State` | Current stamina pool |
|
||||
| `ExhaustionState` | `E_ExhaustionState` | `Normal` | `Stamina State` | Current exhaustion tier |
|
||||
| `ActiveDrains` | `Map<E_StaminaActionType, bool>` | `{}` | `Stamina State` | Which drains are currently active |
|
||||
| `RegenTimerHandle` | `FTimerHandle` | `-` | `Stamina State` | Timer for regen ticks |
|
||||
| `DrainTimerHandle` | `FTimerHandle` | `-` | `Stamina State` | Timer for continuous drain ticks |
|
||||
| `LastDrainTime` | `Float` | `0.0` | `Stamina State` | World time of last stamina drain |
|
||||
| `bRegenBlocked` | `Boolean` | `false` | `Stamina State` | True while external block is active |
|
||||
| `ActionCooldownTimers` | `Map<E_StaminaActionType, FTimerHandle>` | `{}` | `Stamina State` | Per-action cooldown timers |
|
||||
|
||||
### Replicated (if multiplayer)
|
||||
|
||||
| Variable | Type | Condition | Description |
|
||||
|----------|------|-----------|-------------|
|
||||
| `CurrentStamina` | `Float` | `RepNotify` | Replicated with OnRep handler for UI updates |
|
||||
| `ExhaustionState` | `E_ExhaustionState` | `RepNotify` | Replicated exhaustion tier |
|
||||
|
||||
---
|
||||
|
||||
## 4. Functions
|
||||
|
||||
### Public Functions
|
||||
|
||||
#### `DrainStamina` → `Boolean (success)`
|
||||
- **Description:** Attempts to drain stamina for a specific action. Returns false if insufficient stamina or cooldown active.
|
||||
- **Parameters:**
|
||||
| Param | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `ActionType` | `E_StaminaActionType` | Which action is being performed |
|
||||
| `OverrideAmount` | `Float` | If > 0, use this instead of the DrainRate config value |
|
||||
- **Blueprint Authority:** Server (if MP), Any (single-player)
|
||||
- **Flow:**
|
||||
1. If ActionCooldownTimers contains ActionType and timer active: return false
|
||||
2. Look up S_StaminaDrainRate for ActionType
|
||||
3. RequiredStamina = DrainRate.MinStamina
|
||||
4. If CurrentStamina < RequiredStamina: return false
|
||||
5. DrainAmount = OverrideAmount > 0 ? OverrideAmount : DrainRate.DrainFlat
|
||||
6. CurrentStamina = FMath::Max(0, CurrentStamina - DrainAmount)
|
||||
7. Start cooldown timer for DrainRate.CooldownAfterUse
|
||||
8. Update ExhaustionState
|
||||
9. Reset regen delay, stop regen if running
|
||||
10. Fire OnStaminaDrained
|
||||
11. If ActionType is continuous: add to ActiveDrains
|
||||
12. Fire OnStaminaChanged
|
||||
13. Return true
|
||||
|
||||
#### `StartContinuousDrain` → `void`
|
||||
- **Description:** Begins draining stamina per second for a continuous action.
|
||||
- **Parameters:**
|
||||
| Param | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `ActionType` | `E_StaminaActionType` | Sprint, Climb, etc. |
|
||||
- **Flow:**
|
||||
1. Add ActionType to ActiveDrains = true
|
||||
2. If DrainTimerHandle not active: start timer with interval 0.1 seconds
|
||||
3. Timer callback: apply (DrainRate.DrainPerSecond * 0.1) to CurrentStamina each tick
|
||||
4. If CurrentStamina <= 0: stop continuous drain, fire OnExhausted
|
||||
|
||||
#### `StopContinuousDrain` → `void`
|
||||
- **Parameters:**
|
||||
| Param | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `ActionType` | `E_StaminaActionType` | Action to stop draining for |
|
||||
- **Flow:**
|
||||
1. Set ActiveDrains[ActionType] = false
|
||||
2. If no active drains remain: clear DrainTimerHandle
|
||||
3. Start regen delay timer
|
||||
|
||||
#### `RestoreStamina` → `Float (actual restored)`
|
||||
- **Description:** Adds stamina up to MaxStamina.
|
||||
- **Parameters:**
|
||||
| Param | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `Amount` | `Float` | Stamina to restore |
|
||||
| `Tags` | `GameplayTagContainer` | Context tags (e.g. Potion, Rest) |
|
||||
- **Flow:**
|
||||
1. Previous = CurrentStamina
|
||||
2. CurrentStamina = FMath::Min(MaxStamina, CurrentStamina + Amount)
|
||||
3. Update ExhaustionState
|
||||
4. Fire OnStaminaRestored
|
||||
5. Fire OnStaminaChanged
|
||||
6. Return CurrentStamina - Previous
|
||||
|
||||
#### `GetStaminaNormalised` → `Float [0.0 - 1.0]`
|
||||
- **Flow:** Return CurrentStamina / MaxStamina
|
||||
|
||||
#### `CanAffordAction` → `Boolean`
|
||||
- **Parameters:**
|
||||
| Param | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `ActionType` | `E_StaminaActionType` | Action to check |
|
||||
- **Flow:**
|
||||
1. Look up MinStamina for this ActionType
|
||||
2. Return CurrentStamina >= MinStamina AND no active cooldown
|
||||
|
||||
#### `SetMaxStamina` → `void`
|
||||
- **Parameters:**
|
||||
| Param | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `NewMax` | `Float` | New maximum value |
|
||||
| `bMaintainRatio` | `Boolean` | Scale current stamina to maintain ratio |
|
||||
- **Flow:**
|
||||
1. If bMaintainRatio: CurrentStamina = (CurrentStamina / MaxStamina) * NewMax
|
||||
2. Else: CurrentStamina = FMath::Min(CurrentStamina, NewMax)
|
||||
3. MaxStamina = NewMax
|
||||
4. Update ExhaustionState
|
||||
5. Fire OnStaminaChanged
|
||||
|
||||
#### `BlockRegen` → `void`
|
||||
- **Parameters:**
|
||||
| Param | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `Duration` | `Float` | Seconds to block regen |
|
||||
- **Flow:**
|
||||
1. Set bRegenBlocked = true
|
||||
2. Start timer for Duration
|
||||
3. On timer end: bRegenBlocked = false
|
||||
|
||||
### Protected / Private Functions
|
||||
|
||||
#### `UpdateExhaustionState` → `void`
|
||||
- **Flow:**
|
||||
1. Normalised = CurrentStamina / MaxStamina
|
||||
2. OldState = ExhaustionState
|
||||
3. If Normalised <= ExhaustedThreshold: NewState = Exhausted
|
||||
4. Else if Normalised <= LowThreshold: NewState = Low
|
||||
5. Else: NewState = Normal
|
||||
6. If NewState != OldState:
|
||||
- ExhaustionState = NewState
|
||||
- Fire OnExhaustionStateChanged
|
||||
- If NewState == Exhausted: fire OnExhausted
|
||||
|
||||
#### `RegenTick` → `void`
|
||||
- **Flow:**
|
||||
1. If bRegenBlocked: return
|
||||
2. Rate = ExhaustionState == Exhausted ? RegenSettings.ExhaustedRegenRate : RegenSettings.RegenRate
|
||||
3. CurrentStamina = FMath::Min(MaxStamina, CurrentStamina + Rate * 0.1)
|
||||
4. Fire OnStaminaChanged
|
||||
5. If CurrentStamina >= MaxStamina: stop regen timer, fire OnStaminaFullyRestored
|
||||
|
||||
#### `OnMovementStateChanged (listener)` → `void`
|
||||
- **Flow:**
|
||||
1. Get movement mode from character
|
||||
2. If RegenSettings.RegenBlockedWhileMoving and movement speed > walk:
|
||||
- Pause regen timer
|
||||
3. Else: resume regen timer if conditions met
|
||||
|
||||
---
|
||||
|
||||
## 5. Event Dispatchers
|
||||
|
||||
| Dispatcher | Parameters | Bind Access | Description |
|
||||
|------------|-----------|-------------|-------------|
|
||||
| `OnStaminaChanged` | `float OldStamina`, `float NewStamina`, `float Delta` | `Public` | Fired on any stamina change |
|
||||
| `OnStaminaDrained` | `E_StaminaActionType ActionType`, `float Amount` | `Public` | Fired when stamina is consumed |
|
||||
| `OnStaminaRestored` | `float Amount`, `GameplayTagContainer Tags` | `Public` | Fired when stamina is gained |
|
||||
| `OnStaminaFullyRestored` | `none` | `Public` | Fired when stamina reaches MaxStamina |
|
||||
| `OnExhaustionStateChanged` | `E_ExhaustionState OldState`, `E_ExhaustionState NewState` | `Public` | Fired on any exhaustion tier change |
|
||||
| `OnExhausted` | `none` | `Public` | Fired when entering Exhausted state |
|
||||
| `OnActionCooldownStarted` | `E_StaminaActionType ActionType`, `float Cooldown` | `Public` | Fired when a per-action cooldown begins |
|
||||
| `OnActionCooldownEnded` | `E_StaminaActionType ActionType` | `Public` | Fired when a per-action cooldown expires |
|
||||
|
||||
---
|
||||
|
||||
## 6. Overridden Events / Custom Events
|
||||
|
||||
### Event: `BeginPlay`
|
||||
- **Description:** Initialises stamina, starts regen if enabled, binds to movement state changes.
|
||||
- **Flow:**
|
||||
1. CurrentStamina = MaxStamina
|
||||
2. Start regen timer with initial delay = 0
|
||||
3. Find BPC_MovementStateSystem on owner, bind to its OnMovementStateChanged dispatcher
|
||||
4. Initialise ExhaustionState = Normal
|
||||
|
||||
### Event: `OnComponentDestroyed`
|
||||
- **Description:** Clean up all timers.
|
||||
- **Flow:**
|
||||
1. Clear RegenTimerHandle, DrainTimerHandle
|
||||
2. Clear all ActionCooldownTimers
|
||||
|
||||
---
|
||||
|
||||
## 7. Blueprint Graph Logic Flow
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A[DrainStamina called] --> B{CanAffordAction?}
|
||||
B -->|No| C[Return false]
|
||||
B -->|Yes| D[Apply drain amount]
|
||||
D --> E[Update CurrentStamina]
|
||||
E --> F{ExhaustedThreshold crossed?}
|
||||
F -->|Yes| G[Set Exhausted state]
|
||||
F -->|No| H{LowThreshold crossed?}
|
||||
H -->|Yes| I[Set Low state]
|
||||
H -->|No| J[Keep Normal state]
|
||||
G --> K[Fire OnExhausted]
|
||||
K --> L[Stop continuous drains]
|
||||
I --> M[Fire OnExhaustionStateChanged]
|
||||
J --> M
|
||||
L --> M
|
||||
M --> N[Start regen delay]
|
||||
N --> O[Fire OnStaminaChanged]
|
||||
O --> P[Return true]
|
||||
D --> Q{Continuous action?}
|
||||
Q -->|Yes| R[Start DrainTimer loop]
|
||||
Q -->|No| S[Start action cooldown]
|
||||
R --> O
|
||||
S --> O
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. Communication Matrix
|
||||
|
||||
| Who Talks | How | What Is Sent |
|
||||
|-----------|-----|-------------|
|
||||
| `BPC_StaminaSystem` | `Dispatcher` | `OnStaminaChanged` -> `WBP_HUD` (stamina bar update) |
|
||||
| `BPC_StaminaSystem` | `Dispatcher` | `OnExhausted` -> `BPC_PlayerController` (player feedback), `BPC_AdaptiveEnvironment` |
|
||||
| `BPC_StaminaSystem` | `Dispatcher` | `OnExhaustionStateChanged` -> `ABP_GASP` (breathing animation) |
|
||||
| `External (PC_PlayerController)` | `Direct` | Calls `DrainStamina(Sprint)` / `StartContinuousDrain` on input |
|
||||
| `BPC_StaminaSystem` | `Dispatcher` | `OnStaminaFullyRestored` -> `WBP_HUD` (hide bar) |
|
||||
| `BPC_StaminaSystem` | `Listener` | Binds to `BPC_MovementStateSystem.OnMovementStateChanged` for regen blocking |
|
||||
|
||||
---
|
||||
|
||||
## 9. Validation / Testing Checklist
|
||||
|
||||
- [ ] DrainStamina deducts correct amount and returns false when stamina is insufficient
|
||||
- [ ] Continuous drain stops when stamina reaches 0
|
||||
- [ ] Regen does not start until RegenDelay has passed
|
||||
- [ ] Exhausted state correctly uses ExhaustedRegenRate and ExhaustedRegenDelay
|
||||
- [ ] Exhaustion state transitions are one-way downward until full rest
|
||||
- [ ] Per-action cooldowns prevent spamming dodge/jump
|
||||
- [ ] BlockRegen pauses all regen for its duration
|
||||
- [ ] RegenBlockedWhileMoving pauses regen at high speed, resumes when walking
|
||||
- [ ] Edge case: MaxStamina change with bMaintainRatio=true preserves percentage
|
||||
- [ ] Edge case: Multiple simultaneous continuous drains stack correctly
|
||||
- [ ] Edge case: DrainStamina called during active cooldown returns false without state change
|
||||
- [ ] All timers cleaned up on component destroy
|
||||
|
||||
---
|
||||
|
||||
## 10. Reuse Notes
|
||||
|
||||
- Can be placed on AI characters with simplified config (e.g. only Sprint drain, no Exhaustion).
|
||||
- For enemy stamina, consider removing the exhaustion mechanic and using only drain/restore.
|
||||
- The drain rate map supports runtime modification for buffs/debuffs (e.g. Adrenaline reduces sprint cost).
|
||||
- UI widgets should bind to OnStaminaChanged for smooth bar animation, not tick polling.
|
||||
- For multiplayer: Server authorises all DrainStamina calls; client predicts and corrects on repnotify.
|
||||
- Exhaustion state can drive conditional audio: heavy breathing, heart-beat, fatigue voice lines.
|
||||
|
||||
---
|
||||
|
||||
*Blueprint Spec: Stamina System. Conforms to TEMPLATE.md v1.0 — part of the UE5 Modular Game Framework.*
|
||||
Reference in New Issue
Block a user