328 lines
14 KiB
Markdown
328 lines
14 KiB
Markdown
# 12 — Hiding System (`BPC_HidingSystem`)
|
|
|
|
## Purpose
|
|
Manages the player's ability to hide inside, behind, or under environmental objects. Handles entering/exiting hiding spots, line-of-sight checks against enemies, peeking, and stress reduction while concealed. The central component for stealth gameplay.
|
|
|
|
## Dependencies
|
|
- **Requires:** `FL_GameUtilities` (interface casts), `I_HidingSpot` (interface on world actors)
|
|
- **Required By:** `AI_EnemyController` (query hiding state for awareness), `BPC_StressSystem` (stress decay while hidden), `BPC_PlayerController` (movement/input restriction)
|
|
- **Engine/Plugin Requirements:** GameplayTags, LineTraceByChannel (LOS checks)
|
|
|
|
## Class Info
|
|
| Property | Value |
|
|
|----------|-------|
|
|
| **Parent Class** | `ActorComponent` |
|
|
| **Class Type** | Blueprint Component |
|
|
| **Asset Path** | `Content/Framework/Player/BPC_HidingSystem` |
|
|
| **Implements Interfaces** | None |
|
|
|
|
---
|
|
|
|
## 1. Enums
|
|
|
|
### `E_HideState`
|
|
| Value | Description |
|
|
|-------|-------------|
|
|
| `Exposed = 0` | Not hiding, fully visible |
|
|
| `Entering = 1` | Transient — animation playing to enter hide spot |
|
|
| `Hidden = 2` | Inside hide spot, concealed |
|
|
| `Peeking = 3` | Partially visible, can look around |
|
|
| `Exiting = 4` | Transient — animation playing to exit hide spot |
|
|
|
|
### `E_HideType`
|
|
| Value | Description |
|
|
|-------|-------------|
|
|
| `Locker = 0` | Fully enclosed (locker, wardrobe) |
|
|
| `BehindCover = 1` | Behind low wall, crate, counter |
|
|
| `Under = 2` | Under bed, table, porch |
|
|
| `InShadow = 3` | Standing in a shadow volume |
|
|
| `TallGrass = 4` | Crouch-moving through vegetation |
|
|
|
|
### `E_PeekDirection`
|
|
| Value | Description |
|
|
|-------|-------------|
|
|
| `Left = 0` | Peek left from behind cover |
|
|
| `Right = 1` | Peek right from behind cover |
|
|
| `Over = 2` | Peek over low cover |
|
|
|
|
---
|
|
|
|
## 2. Structs
|
|
|
|
### `S_HideSpotInfo`
|
|
| Field | Type | Description |
|
|
|-------|------|-------------|
|
|
| `HidingActor` | `AActor` | The hide spot actor |
|
|
| `HideType` | `E_HideType` | Type of hiding |
|
|
| `SlotCount` | `Integer` | How many can hide here (-1 = infinite) |
|
|
| `bHasPeekAbility` | `Boolean` | Can player peek from this spot? |
|
|
| `PeekSocketLeft` | `FName` | Socket name for left peek camera |
|
|
| `PeekSocketRight` | `FName` | Socket name for right peek camera |
|
|
| `PeekSocketOver` | `FName` | Socket name for over peek camera |
|
|
| `ExitLocation` | `Vector` | World location to place player on exit |
|
|
| `ExitRotation` | `Rotator` | Rotation to face on exit |
|
|
| `bBlocksStress` | `Boolean` | Whether this spot reduces stress gain |
|
|
| `HideTags` | `GameplayTagContainer` | Tags for filtering |
|
|
| `LOSTraceChannel` | `TEnumAsByte<ETraceTypeQuery>` | Trace channel for LOS checks from this spot |
|
|
|
|
---
|
|
|
|
## 3. Variables
|
|
|
|
### Configuration (Instance Editable, Expose On Spawn)
|
|
|
|
| Variable | Type | Default | Category | Description |
|
|
|----------|------|---------|----------|-------------|
|
|
| `bCanHide` | `Boolean` | `true` | `Hiding Config` | Can this player hide at all? |
|
|
| `StressDecayWhileHidden` | `Float` | `5.0` | `Hiding Config` | Stress lost per second while hidden |
|
|
| `bEnemyCanFindHidingSpot` | `Boolean` | `true` | `Hiding Config` | Can enemies discover this spot |
|
|
| `MaxPeekDuration` | `Float` | `3.0` | `Hiding Config` | Max seconds player can peek before forced back |
|
|
| `PeekCooldown` | `Float` | `1.5` | `Hiding Config` | Seconds before player can peek again |
|
|
| `CapsuleCheckRadius` | `Float` | `50.0` | `Hiding Config` | Radius for capsule trace when finding hide spots |
|
|
|
|
### Internal (Private / Protected, No Expose)
|
|
|
|
| Variable | Type | Default | Category | Description |
|
|
|----------|------|---------|----------|-------------|
|
|
| `CurrentHideState` | `E_HideState` | `Exposed` | `Hiding State` | Current hiding state |
|
|
| `PreviousHideState` | `E_HideState` | `Exposed` | `Hiding State` | Previous state for transition detection |
|
|
| `CurrentHideSpot` | `S_HideSpotInfo` | `-` | `Hiding State` | Active hide spot data |
|
|
| `PendingHideSpot` | `AActor` | `None` | `Hiding State` | Hide spot being entered |
|
|
| `PeekTimerHandle` | `FTimerHandle` | `-` | `Hiding State` | Timer for max peek duration |
|
|
| `PeekCooldownTimerHandle` | `FTimerHandle` | `-` | `Hiding State` | Timer for peek cooldown |
|
|
| `bCanPeek` | `Boolean` | `true` | `Hiding State` | Whether peeking is allowed |
|
|
| `LastLOSCheckTime` | `Float` | `0.0` | `Hiding State` | World time of last LOS check |
|
|
| `LOSTimerHandle` | `FTimerHandle` | `-` | `Hiding State` | Timer for periodic LOS checks |
|
|
| `BreathHeldTimer` | `FTimerHandle` | `-` | `Hiding State` | Timer for breath-hold mechanic |
|
|
| `bIsHoldingBreath` | `Boolean` | `false` | `Hiding State` | Whether player is holding breath |
|
|
|
|
### Replicated (if multiplayer)
|
|
|
|
| Variable | Type | Condition | Description |
|
|
|----------|------|-----------|-------------|
|
|
| `CurrentHideState` | `E_HideState` | `RepNotify` | Replicated hide state |
|
|
| `CurrentHideSpot` | `S_HideSpotInfo` | `Replicated` | Replicated spot reference |
|
|
|
|
---
|
|
|
|
## 4. Functions
|
|
|
|
### Public Functions
|
|
|
|
#### `EnterHideSpot` → `Boolean`
|
|
- **Description:** Attempts to enter a hiding spot. Returns true if successful.
|
|
- **Parameters:**
|
|
| Param | Type | Description |
|
|
|-------|------|-------------|
|
|
| `HideSpotActor` | `AActor` | The hide spot to enter |
|
|
- **Blueprint Authority:** Server (if MP), Any (single-player)
|
|
- **Flow:**
|
|
1. Early-out if CurrentHideState != Exposed or !bCanHide
|
|
2. Validate that HideSpotActor implements I_HidingSpot
|
|
3. Check slot availability (I_HidingSpot.Execute_HasAvailableSlots)
|
|
4. If available:
|
|
- Set CurrentHideState = Entering
|
|
- Store HideSpotActor info in CurrentHideSpot
|
|
- Disable player movement input
|
|
- Play entering animation (via ABP_GASP)
|
|
- On animation complete: Set CurrentHideState = Hidden
|
|
- Fire OnHideStateChanged
|
|
- Start stress decay timer
|
|
- Start periodic LOS check timer
|
|
- Return true
|
|
5. If full: return false
|
|
|
|
#### `ExitHideSpot` → `void`
|
|
- **Description:** Exits the current hiding spot.
|
|
- **Parameters:**
|
|
| Param | Type | Description |
|
|
|-------|------|-------------|
|
|
| `bForceExit` | `Boolean` | If true, skip exit animation |
|
|
- **Flow:**
|
|
1. If CurrentHideState != Hidden and CurrentHideState != Peeking: return
|
|
2. Set CurrentHideState = Exiting
|
|
3. If bForceExit: skip animation, immediately set Exposed
|
|
4. Else: play exiting animation
|
|
5. On animation complete or bForceExit:
|
|
- Teleport player to CurrentHideSpot.ExitLocation with ExitRotation
|
|
- Re-enable movement input
|
|
- Set CurrentHideState = Exposed
|
|
- Clear CurrentHideSpot
|
|
- Stop stress decay and LOS timers
|
|
- Fire OnHideStateChanged
|
|
- Fire OnExitedHidingSpot
|
|
|
|
#### `StartPeek` → `void`
|
|
- **Description:** Begins peeking from behind cover.
|
|
- **Parameters:**
|
|
| Param | Type | Description |
|
|
|-------|------|-------------|
|
|
| `Direction` | `E_PeekDirection` | Which direction to peek |
|
|
- **Flow:**
|
|
1. Early-out if CurrentHideState != Hidden or !bCanPeek or !CurrentHideSpot.bHasPeekAbility
|
|
2. Set CurrentHideState = Peeking
|
|
3. Move camera to peek socket location
|
|
4. Start MaxPeekDuration timer
|
|
5. Start PeekCooldown timer (bCanPeek = false during cooldown)
|
|
6. Fire OnPeekStarted
|
|
|
|
#### `StopPeek` → `void`
|
|
- **Description:** Ends the peek and returns to Hidden state.
|
|
- **Flow:**
|
|
1. If CurrentHideState != Peeking: return
|
|
2. Set CurrentHideState = Hidden
|
|
3. Return camera to default hide position
|
|
4. Fire OnPeekEnded
|
|
|
|
#### `IsPlayerDetectable` → `Boolean`
|
|
- **Description:** Checks if the player is detectable from a given enemy location, considering hide type and edges.
|
|
- **Parameters:**
|
|
| Param | Type | Description |
|
|
|-------|------|-------------|
|
|
| `EnemyLocation` | `Vector` | Location of the enemy |
|
|
| `DetectionRange` | `Float` | Max range for detection check |
|
|
- **Flow:**
|
|
1. If CurrentHideState != Hidden: return true (fully detectable)
|
|
2. If EnemyLocation distance > DetectionRange: return false
|
|
3. Perform line trace from enemy to player's approximate position
|
|
4. If trace hits CurrentHideSpot.HidingActor before player: return false (blocked by cover)
|
|
5. If trace hits player directly: return true (cover not fully effective)
|
|
6. Edge case: peek state increases detection radius
|
|
|
|
#### `TryBreathHold` → `void`
|
|
- **Description:** Player holds breath to reduce noise/detectability for a short time.
|
|
- **Flow:**
|
|
1. bIsHoldingBreath = true
|
|
2. Start breath-hold timer (e.g., 8 seconds)
|
|
3. On timer end: forced exhale, brief noise event, bIsHoldingBreath = false
|
|
4. Fire OnBreathHoldChanged
|
|
|
|
### Protected / Private Functions
|
|
|
|
#### `PerformLOSCheck` → `void`
|
|
- **Description:** Timer callback — checks if any enemy has line of sight to this hide spot.
|
|
- **Flow:**
|
|
1. Get all AI_EnemyController instances within detection range
|
|
2. For each enemy: call IsPlayerDetectable(EnemyLocation)
|
|
3. If any enemy has LOS:
|
|
- Fire OnHideSpotCompromised
|
|
- If CurrentHideSpot.bEnemyCanFindHidingSpot: fire OnForcedExitWarning
|
|
|
|
#### `OnAnimationHideEnterComplete` → `void`
|
|
- **Description:** Animation notify callback when enter animation finishes.
|
|
- **Flow:**
|
|
1. Set CurrentHideState = Hidden
|
|
2. Attach player to hide spot socket if applicable
|
|
3. Fire OnHideStateChanged
|
|
4. Start stress decay loop
|
|
|
|
#### `OnAnimationHideExitComplete` → `void`
|
|
- **Description:** Animation notify callback when exit animation finishes.
|
|
- **Flow:**
|
|
1. Perform final exit steps already queued in ExitHideSpot
|
|
|
|
---
|
|
|
|
## 5. Event Dispatchers
|
|
|
|
| Dispatcher | Parameters | Bind Access | Description |
|
|
|------------|-----------|-------------|-------------|
|
|
| `OnHideStateChanged` | `E_HideState OldState`, `E_HideState NewState` | `Public` | Fired on any hide state change |
|
|
| `OnEnteredHidingSpot` | `AActor HideSpot`, `E_HideType HideType` | `Public` | Fired when player successfully hides |
|
|
| `OnExitedHidingSpot` | `AActor HideSpot` | `Public` | Fired when player leaves hiding |
|
|
| `OnPeekStarted` | `E_PeekDirection Direction` | `Public` | Fired when player starts peeking |
|
|
| `OnPeekEnded` | `none` | `Public` | Fired when player stops peeking |
|
|
| `OnHideSpotCompromised` | `AActor EnemyActor` | `Public` | Fired when an enemy sees the player's spot |
|
|
| `OnForcedExitWarning` | `float TimeUntilDiscovered` | `Public` | Fired when enemy is approaching known spot |
|
|
| `OnBreathHoldChanged` | `bool bIsHoldingBreath` | `Public` | Fired when breath hold toggles |
|
|
|
|
---
|
|
|
|
## 6. Overridden Events / Custom Events
|
|
|
|
### Event: `BeginPlay`
|
|
- **Description:** Initialises state, binds to input actions.
|
|
- **Flow:**
|
|
1. Set CurrentHideState = Exposed
|
|
2. Register with GI_GameTagRegistry if needed
|
|
3. Cache reference to CharacterMovementComponent for disabling during hide
|
|
|
|
### Custom Event: `ForceKickFromHide`
|
|
- **Description:** Called externally (e.g., by enemy discovery) to force player out of hiding.
|
|
- **Flow:**
|
|
1. Call ExitHideSpot(bForceExit = true)
|
|
2. Apply brief stun or camera shake
|
|
3. Fire OnForcedExitWarning with 0 seconds
|
|
|
|
### Custom Event: `OnDamageWhileHiding`
|
|
- **Description:** Listener for when damage is taken while hidden.
|
|
- **Flow:**
|
|
1. If damage source penetrates cover: force exit
|
|
2. Else: reduce stress instead of health (environmental damage)
|
|
|
|
---
|
|
|
|
## 7. Blueprint Graph Logic Flow
|
|
|
|
```mermaid
|
|
flowchart TD
|
|
A[EnterHideSpot called] --> B{Is Exposed?}
|
|
B -->|No| C[Return false]
|
|
B -->|Yes| D[Validate I_HidingSpot]
|
|
D --> E{Has available slots?}
|
|
E -->|No| F[Return false]
|
|
E -->|Yes| G[Set Entering state]
|
|
G --> H[Disable movement input]
|
|
H --> I[Play Enter animation]
|
|
I --> J[Animation notify: OnComplete]
|
|
J --> K[Set Hidden state]
|
|
K --> L[Start stress decay loop]
|
|
L --> M[Start periodic LOS check]
|
|
M --> N[Fire OnEnteredHidingSpot]
|
|
N --> O[Return true]
|
|
```
|
|
|
|
---
|
|
|
|
## 8. Communication Matrix
|
|
|
|
| Who Talks | How | What Is Sent |
|
|
|-----------|-----|-------------|
|
|
| `BPC_HidingSystem` | `Dispatcher` | `OnHideStateChanged` -> `BPC_PlayerController` (input), `ABP_GASP` (anim), `BPC_StressSystem` (decay), `AI_EnemyController` (awareness reset) |
|
|
| `BPC_HidingSystem` | `Dispatcher` | `OnHideSpotCompromised` -> `BPC_StressSystem` (stress spike), `BP_AudioManager` (tension music) |
|
|
| `BPC_HidingSystem` | `Dispatcher` | `OnForcedExitWarning` -> `WBP_HUD` (hide icon flash), `BP_AudioManager` (heartbeat) |
|
|
| `External (AI)` | `Query` | `IsPlayerDetectable` called by `AI_EnemyController` for LOS checks |
|
|
| `External (World)` | `Interface` | `I_HidingSpot` on `BP_Locker`, `BP_Wardrobe`, `BP_Cover` |
|
|
| `BPC_HidingSystem` | `Dispatcher` | `OnPeekStarted/Ended` -> `BPC_CameraStateLayer` (FOV/position) |
|
|
|
|
---
|
|
|
|
## 9. Validation / Testing Checklist
|
|
|
|
- [ ] EnterHideSpot correctly transitions through Entering -> Hidden
|
|
- [ ] ExitHideSpot correctly transitions through Exiting -> Exposed
|
|
- [ ] Forced exit with bForceExit = true skips animation, immediately exits
|
|
- [ ] Peek timer forces player back to Hidden after MaxPeekDuration
|
|
- [ ] PeekCooldown prevents rapid peek toggling
|
|
- [ ] IsPlayerDetectable returns correct values for all hide types and peek states
|
|
- [ ] Multiple hide spots with limited slots: full slots reject entry
|
|
- [ ] Stress decays while hidden at configured rate
|
|
- [ ] LOS check detects enemies within range and fires compromise dispatcher
|
|
- [ ] Edge case: EnterHideSpot called while already hiding returns false
|
|
- [ ] Edge case: ExitHideSpot called while Exposed does nothing
|
|
- [ ] Edge case: Destroying hide spot while inside force-exits player
|
|
|
|
---
|
|
|
|
## 10. Reuse Notes
|
|
|
|
- Enemy AI characters can use a simplified version to find and occupy hide spots.
|
|
- Hide spots can be pre-placed (lockers, beds) or dynamic (shadow volumes, tall grass).
|
|
- The LOS trace channel should be custom (e.g., DetectionChannel) to ignore small props.
|
|
- Breath-hold mechanic adds depth to stealth gameplay near enemies.
|
|
- For multiplayer: only the hiding player knows their state; other players see a generic "occupado" on the spot.
|
|
- Hide spots with bBlocksStress = true can double as narrative safe rooms.
|
|
|
|
---
|
|
|
|
*Blueprint Spec: Hiding System. Conforms to TEMPLATE.md v1.0 — part of the UE5 Modular Game Framework.* |