Files
UE5-Modular-Game-Framework/docs/blueprints/02-player/15_BPC_PlayerMetricsTracker.md
Lefteris Notas 411edea8ce add blueprints
2026-05-19 13:22:27 +03:00

335 lines
14 KiB
Markdown

# 15 — Player Metrics Tracker (`BPC_PlayerMetricsTracker`)
## Purpose
Records and exposes player behaviour telemetry throughout a play session. Metrics feed into the adaptive difficulty system, achievement tracking, end-game stats screen, and narrative pacing adjustments. This is a pure data-gathering component — it never blocks or modifies gameplay directly.
## Dependencies
- **Requires:** `GI_GameFramework` (session start/end events), `DA_GameDifficulty` (adaptive thresholds)
- **Required By:** `BPC_AdaptiveDifficulty` (consumes metrics), `WBP_StatsScreen` (end-game display), `GM_CoreGameMode` (death/checkpoint metrics)
- **Engine/Plugin Requirements:** `FTimerHandle` for play time tracking
## Class Info
| Property | Value |
|----------|-------|
| **Parent Class** | `ActorComponent` |
| **Class Type** | Blueprint Component |
| **Asset Path** | `Content/Framework/Player/BPC_PlayerMetricsTracker` |
| **Implements Interfaces** | `I_Persistable` (save/load metrics) |
---
## 1. Enums
### `E_MetricEventType`
| Value | Description |
|-------|-------------|
| `Death = 0` | Player died |
| `HideStarted = 1` | Player entered a hide spot |
| `HideEnded = 2` | Player exited a hide spot |
| `ItemCollected = 3` | Player picked up an item |
| `EnemyEncountered = 4` | Player entered an enemy detection range |
| `EnemyEvaded = 5` | Player escaped enemy detection |
| `StressPeakReached = 6` | Stress tier exceeded Distressed |
| `AreaExplored = 7` | Player entered a new area/chapter |
| `NarrativeEvent = 8` | Story beat triggered |
| `CombatEvent = 9` | Player used a weapon or was attacked |
---
## 2. Structs
### `S_BehaviourEvent`
| Field | Type | Description |
|-------|------|-------------|
| `Timestamp` | `Float` | Game time in seconds when event occurred |
| `EventType` | `E_MetricEventType` | What happened |
| `ContextTag` | `GameplayTag` | Tagged context for filtering |
| `FloatValue` | `Float` | Numeric payload (e.g. distance, intensity) |
| `StringValue` | `String` | Text payload (e.g. location name, enemy ID) |
| `Location` | `Vector` | World location where event occurred |
### `S_MetricsSnapshot`
| Field | Type | Description |
|-------|------|-------------|
| `TotalPlayTime` | `Float` | Total seconds played |
| `TimesDied` | `Integer` | Total deaths this session |
| `TimesHid` | `Integer` | Total hide events |
| `TotalDistanceWalked` | `Float` | Cumulative walking distance (cm) |
| `TotalDistanceSprinted` | `Float` | Cumulative sprinting distance (cm) |
| `ItemsCollected` | `Integer` | Total items picked up |
| `UniqueItemsCollected` | `Set<FName>` | Names of unique items collected |
| `EnemiesEncountered` | `Integer` | Total unique enemy encounters |
| `EnemiesEvaded` | `Integer` | Total successful evasions |
| `PeakStressTier` | `Float` | Highest stress tier reached (as float) |
| `StressPeakCount` | `Integer` | Times stress exceeded Distressed threshold |
| `NarrativeBeatsCompleted` | `Integer` | Story triggers activated |
| `AreasExplored` | `Set<FName>` | Unique areas/chapters visited |
| `CombatEvents` | `Integer` | Total combat interactions |
### `S_TimeSegment`
| Field | Type | Description |
|-------|------|-------------|
| `SegmentLabel` | `String` | Label for this time segment |
| `StartTime` | `Float` | Game time at segment start |
| `EndTime` | `Float` | Game time at segment end |
| `StressAverage` | `Float` | Average stress during segment |
| `MetricsDelta` | `S_MetricsSnapshot` | Metrics accumulated in this segment |
---
## 3. Variables
### Configuration (Instance Editable, Expose On Spawn)
| Variable | Type | Default | Category | Description |
|----------|------|---------|----------|-------------|
| `bTrackDetailedEvents` | `Boolean` | `true` | `Metrics Config` | If true, store full `S_BehaviourEvent` history |
| `MaxEventHistory` | `Integer` | `5000` | `Metrics Config` | Cap on stored events to prevent memory bloat |
| `bAutoSaveMetrics` | `Boolean` | `true` | `Metrics Config` | Auto-save metrics via I_Persistable on quit |
| `SnapshotInterval` | `Float` | `60.0` | `Metrics Config` | Seconds between automatic snapshot writes |
### Computed / Runtime (Public, Blueprint Read Only)
| Variable | Type | Default | Category | Description |
|----------|------|---------|----------|-------------|
| `CurrentSnapshot` | `S_MetricsSnapshot` | `-` | `Runtime Metrics` | Live accumulated metrics this session |
| `bIsTrackingSession` | `Boolean` | `false` | `Runtime Metrics` | Whether currently in a tracked session |
| `TimeSegmentHistory` | `Array<S_TimeSegment>` | `[]` | `Runtime Metrics` | Historical segments for analysis |
| `CurrentSegment` | `S_TimeSegment` | `-` | `Runtime Metrics` | Active time segment |
### Internal (Private / Protected, No Expose)
| Variable | Type | Default | Category | Description |
|----------|------|---------|----------|-------------|
| `SessionStartTime` | `Float` | `0.0` | `Timing` | Game time when session started |
| `CachedEventHistory` | `Array<S_BehaviourEvent>` | `[]` | `History` | Full event history (if tracking enabled) |
| `LastPosition` | `Vector` | `(0,0,0)` | `Tracking` | Previous frame position for distance calc |
| `SnapshotTimerHandle` | `FTimerHandle` | `-` | `Timing` | Timer for automatic snapshots |
### Replicated (if multiplayer)
| Variable | Type | Condition | Description |
|----------|------|-----------|-------------|
| `CurrentSnapshot` | `S_MetricsSnapshot` | `Replicated` | Synced session metrics |
---
## 4. Functions
### Public Functions
#### `StartSession` → `void`
- **Description:** Begins a new metrics tracking session. Resets snapshot.
- **Parameters:**
| Param | Type | Description |
|-------|------|-------------|
| `SegmentLabel` | `String` | Label for initial segment (e.g. "Chapter 1") |
- **Blueprint Authority:** Any
- **Flow:**
1. Set SessionStartTime = Current Game Time
2. Reset CurrentSnapshot to zero values
3. Initialize CurrentSegment with label and start time
4. Set bIsTrackingSession = true
5. Start snapshot timer
6. Fire OnSessionStarted
#### `EndSession` → `S_MetricsSnapshot`
- **Description:** Ends the tracking session and returns final snapshot.
- **Parameters:** None
- **Flow:**
1. Finalize CurrentSegment end time
2. Finalize CurrentSnapshot.PlayTime
3. Add CurrentSegment to TimeSegmentHistory
4. Set bIsTrackingSession = false
5. Clear snapshot timer
6. Fire OnSessionEnded
7. Return CurrentSnapshot
#### `RecordEvent` → `void`
- **Description:** Records a single behaviour event into history and updates snapshot.
- **Parameters:**
| Param | Type | Description |
|-------|------|-------------|
| `EventType` | `E_MetricEventType` | Type of event |
| `ContextTag` | `GameplayTag` | Filtering tag |
| `FloatValue` | `Float` | Numeric payload |
| `StringValue` | `String` | Text payload |
- **Flow:**
1. If not bIsTrackingSession: return
2. Create S_BehaviourEvent with current timestamp
3. If bTrackDetailedEvents and event count < MaxEventHistory:
- Add event to CachedEventHistory
4. Update CurrentSnapshot based on EventType:
- Death: TimesDied++
- HideStarted: TimesHid++
- ItemCollected: ItemsCollected++, add name to UniqueItemsCollected
- EnemyEncountered: EnemiesEncountered++
- EnemyEvaded: EnemiesEvaded++
- StressPeakReached: StressPeakCount++, update PeakStressTier if higher
- NarrativeEvent: NarrativeBeatsCompleted++
- AreaExplored: add to AreasExplored
- CombatEvent: CombatEvents++
5. Fire OnEventRecorded
#### `RecordDistanceTraveled` → `void`
- **Description:** Called by movement system or tick to log distance.
- **Parameters:**
| Param | Type | Description |
|-------|------|-------------|
| `DistanceDelta` | `Float` | Distance moved this frame |
| `bWasSprinting` | `Boolean` | Whether player was sprinting |
- **Flow:**
1. If bWasSprinting: CurrentSnapshot.TotalDistanceSprinted += Delta
2. Else: CurrentSnapshot.TotalDistanceWalked += Delta
#### `GetMetricsSnapshot` → `S_MetricsSnapshot`
- **Description:** Returns a copy of the current live snapshot.
- **Parameters:** None
- **Flow:** Return CurrentSnapshot
#### `GetEventHistory` → `Array<S_BehaviourEvent>`
- **Description:** Returns recorded event history.
- **Parameters:**
| Param | Type | Description |
|-------|------|-------------|
| `bFiltered` | `Boolean` | If true, apply filter |
| `FilterType` | `E_MetricEventType` | Only return events of this type |
- **Flow:**
1. If bFiltered: return filtered CachedEventHistory by FilterType
2. Else: return CachedEventHistory
#### `GetPlayTimeFormatted` → `String`
- **Description:** Returns total play time as formatted string (HH:MM:SS).
- **Parameters:** None
- **Flow:** Format SecondsToTimeString(SessionDuration)
### Protected / Private Functions
#### `OnTickDistance (Tick)` → `void`
- **Description:** Each tick, compute distance from LastPosition. Only active when tracking.
- **Flow:**
1. If not bIsTrackingSession: return
2. Get current actor location
3. Delta = Distance(LastPosition, CurrentLocation)
4. If Delta < 10.0: skip (noise threshold)
5. Check movement mode from BPC_MovementStateSystem
6. Call RecordDistanceTraveled(Delta, bWasSprinting)
7. Update LastPosition = CurrentLocation
#### `CreateSnapshot` → `void`
- **Description:** Timer callback to create periodic snapshots.
- **Flow:**
1. Copy CurrentSnapshot as snapshot
2. Finalize CurrentSegment end time
3. Store snapshot delta in CurrentSegment.MetricsDelta
4. Add CurrentSegment to TimeSegmentHistory
5. Start new CurrentSegment
---
## 5. Event Dispatchers
| Dispatcher | Parameters | Bind Access | Description |
|------------|-----------|-------------|-------------|
| `OnSessionStarted` | `String SegmentLabel` | `Public` | Fired when a tracking session begins |
| `OnSessionEnded` | `S_MetricsSnapshot FinalSnapshot` | `Public` | Fired when a tracking session ends |
| `OnEventRecorded` | `S_BehaviourEvent Event` | `Public` | Fired each time an event is recorded |
| `OnMetricsThresholdReached` | `E_MetricEventType EventType`, `int CurrentValue`, `int Threshold` | `Public` | Fired when a metric crosses a threshold |
| `OnSnapshotCreated` | `S_TimeSegment Segment` | `Public` | Fired when a periodic snapshot is written |
---
## 6. Overridden Events / Custom Events
### Event: `BeginPlay`
- **Description:** Cache owner reference, start session if game has started.
- **Flow:**
1. Get owning pawn
2. Bind to GI_GameFramework.OnGamePhaseChanged
3. If game phase is Playing: StartSession("Initial")
### Custom Event: `OnGamePhaseChangedHandler`
- **Description:** Auto-start/stop session based on game phase.
- **Flow:**
1. If NewPhase == Playing: StartSession("Resume")
2. If NewPhase == Menu / Transition / Ended: EndSession()
---
## 7. Blueprint Graph Logic Flow
```mermaid
flowchart TD
A[RecordEvent called] --> B{Is session active?}
B -->|No| C[Return]
B -->|Yes| D[Build S_BehaviourEvent]
D --> E{Has history space?}
E -->|Yes| F[Append to CachedEventHistory]
E -->|No| G[Skip history store]
G --> H[Update CurrentSnapshot counters]
F --> H
H --> I{Threshold crossed?}
I -->|Yes| J[Fire OnMetricsThresholdReached]
I -->|No| K[Fire OnEventRecorded]
J --> K
L[Tick: RecordDistanceTraveled] --> M[Get current location]
M --> N[Distance from LastPosition]
N --> O{Delta > 10 cm?}
O -->|No| P[Skip]
O -->|Yes| Q[Update snapshot distance]
Q --> R[Set LastPosition]
S[SnapshotTimer fires] --> T[Copy CurrentSnapshot]
T --> U[Write segment to history]
U --> V[Fire OnSnapshotCreated]
```
---
## 8. Communication Matrix
| Who Talks | How | What Is Sent |
|-----------|-----|-------------|
| `BPC_PlayerMetricsTracker` | `Direct call (other)` | `GetMetricsSnapshot` -> `BPC_AdaptiveDifficulty` |
| `BPC_PlayerMetricsTracker` | `Dispatcher` | `OnEventRecorded` -> `BPC_AdaptiveDifficulty` (real-time feed) |
| `BPC_PlayerMetricsTracker` | `Dispatcher` | `OnSessionEnded` -> `WBP_StatsScreen` (end-game display) |
| `BPC_PlayerMetricsTracker` | `Dispatcher` | `OnMetricsThresholdReached` -> `GM_CoreGameMode` (achievements) |
| `BPC_HealthSystem` | `Direct` | Calls `RecordEvent(Death)` on player death |
| `BPC_HidingSystem` | `Direct` | Calls `RecordEvent(HideStarted/HideEnded)` |
| `BPC_StressSystem` | `Direct` | Calls `RecordEvent(StressPeakReached)` |
| `GI_GameFramework` | `Dispatcher` | `OnGamePhaseChanged` -> `BPC_PlayerMetricsTracker` (start/stop) |
| `BPC_NarrativeManager` | `Direct` | Calls `RecordEvent(NarrativeEvent)` on story beat |
| `BPC_WeaponSystem` | `Direct` | Calls `RecordEvent(CombatEvent)` on attack |
| `BPC_InventoryManager` | `Direct` | Calls `RecordEvent(ItemCollected)` on pickup |
---
## 9. Validation / Testing Checklist
- [ ] StartSession resets all counters to zero
- [ ] Distance tracking correctly accumulates walking vs sprinting
- [ ] Events are capped at MaxEventHistory and do not overflow
- [ ] Snapshot timer creates periodic snapshots at correct interval
- [ ] EndSession returns accurate final snapshot
- [ ] Filtered event history returns only matching event types
- [ ] Threshold dispatchers fire at correct metric values
- [ ] I_Persistable save/load preserves snapshot across sessions
- [ ] Edge case: Rapid events within one frame all recorded
- [ ] Edge case: Session ends with zero events — snapshot is valid zero-state
- [ ] Edge case: Distance tracking ignores teleportation (delta > 5000 cm)
- [ ] Formatted play time displays correctly for all durations
---
## 10. Reuse Notes
- The metrics snapshot is designed to be consumed by `BPC_AdaptiveDifficulty` for dynamic difficulty adjustment.
- Event history can be serialised to disk via `I_Persistable` for cross-session analytics.
- The same `BPC_PlayerMetricsTracker` can be attached to any character blueprint — not just the player.
- Threshold values are not defined here; they live in `DA_GameDifficulty` or `DA_AdaptiveSettings`.
- For debugging, call `GetEventHistory(false)` to dump all events to log.
---
*Blueprint Spec: Player Metrics Tracker. Conforms to TEMPLATE.md v1.0 — part of the UE5 Modular Game Framework.*