# 16 — Interaction Detector (`BPC_InteractionDetector`) ## Purpose The player's "interaction eye" — performs trace-based detection of interactable objects in the world, sorts them by priority/distance, tracks the current best target, and manages the interaction prompt widget. Every interactive system in the game funnels through this component. ## Dependencies - **Requires:** `I_Interactable` (interface on all interactable actors), `PlayerCameraManager` (for trace origin), `BPC_MovementStateSystem` (block interaction during specific states) - **Required By:** `WBP_InteractionPrompt` (HUD element), `BPC_PickupComponent`, `BPC_InteractableDoorComponent`, `BPC_LeverPuzzleComponent` - **Engine/Plugin Requirements:** `LineTraceByChannel` (ECC_GameTraceChannel1 = Interaction), `TimerHandle` for scan interval ## Class Info | Property | Value | |----------|-------| | **Parent Class** | `ActorComponent` | | **Class Type** | Blueprint Component | | **Asset Path** | `Content/Framework/Interaction/BPC_InteractionDetector` | | **Implements Interfaces** | None | --- ## 1. Enums ### `E_InteractionInputMode` | Value | Description | |-------|-------------| | `Press = 0` | Single press to interact | | `Hold = 1` | Hold over duration to confirm | | `DoubleTap = 2` | Two rapid presses | | `Auto = 3` | Automatic on proximity (no input) | ### `E_InteractionPriority` | Value | Description | |-------|-------------| | `Low = 0` | Ambient / cosmetic items | | `Normal = 1` | Standard pickups, doors | | `High = 2` | Story-critical, puzzles | | `Emergency = 3` | Immediate threat (hide spot, weapon) | ### `E_DetectionState` | Value | Description | |-------|-------------| | `NoTarget = 0` | Nothing in range | | `TargetInRange = 1` | Potential target found | | `TargetConfirmed = 2` | Best target selected | | `Interacting = 3` | Currently performing interaction | --- ## 2. Structs ### `S_InteractableTarget` | Field | Type | Description | |-------|------|-------------| | `TargetActor` | `AActor` | The interactable actor | | `InterfaceRef` | `I_Interactable` | Cast to interface | | `Priority` | `E_InteractionPriority` | Priority level | | `Distance` | `Float` | Distance from player to target | | `InteractionLabel` | `FText` | Display name (e.g. "Open Door", "Pick Up Key") | | `InputMode` | `E_InteractionInputMode` | How interaction is triggered | | `HoldDuration` | `Float` | Seconds required if InputMode is Hold | | `bIsHighlighted` | `Boolean` | Whether target is visually highlighted | | `TargetLocation` | `Vector` | World location for UI widget | ### `S_InteractionResult` | Field | Type | Description | |-------|------|-------------| | `bSuccess` | `Boolean` | Whether interaction was successful | | `FailureReason` | `FText` | Human-readable failure reason | | `TargetActor` | `AActor` | The interacted actor | | `ContextTag` | `GameplayTag` | Tag for event recording | --- ## 3. Variables ### Configuration (Instance Editable, Expose On Spawn) | Variable | Type | Default | Category | Description | |----------|------|---------|----------|-------------| | `InteractionRange` | `Float` | `250.0` | `Detection Config` | Max trace distance (cm) | | `DetectionAngle` | `Float` | `45.0` | `Detection Config` | Cone half-angle for detection (degrees) | | `TraceRadius` | `Float` | `10.0` | `Detection Config` | Sphere trace radius | | `ScanInterval` | `Float` | `0.1` | `Detection Config` | Seconds between detection scans | | `MaxTargetsInRange` | `Integer` | `16` | `Detection Config` | Max detected targets per scan | | `bShowDebugTrace` | `Boolean` | `false` | `Detection Config` | Visualise trace in editor | | `TraceChannel` | `TEnumAsByte` | `ECC_GameTraceChannel1` | `Detection Config` | Collision channel for interaction | | `bBlockDuringDeath` | `Boolean` | `true` | `Detection Config` | Block interaction while dead | | `bBlockDuringHiding` | `Boolean` | `true` | `Detection Config` | Block interaction while hidden | | `bBlockDuringCombat` | `Boolean` | `false` | `Detection Config` | Block interaction during combat | ### Internal (Private / Protected, No Expose) | Variable | Type | Default | Category | Description | |----------|------|---------|----------|-------------| | `BestTargetIndex` | `Integer` | `-1` | `Detection State` | Index of currently selected target | | `DetectedTargets` | `Array` | `[]` | `Detection State` | All detected targets this scan | | `CurrentTarget` | `S_InteractableTarget` | `-` | `Detection State` | Currently selected best target | | `DetectionState` | `E_DetectionState` | `NoTarget` | `Detection State` | Current state of the detector | | `bIsPerformingInteraction` | `Boolean` | `false` | `Detection State` | Whether interaction is in progress | | `HoldInteractionProgress` | `Float` | `0.0` | `Detection State` | Progress for hold-type interactions | | `OwnerPlayerController` | `APlayerController` | `None` | `Cache` | Cached player controller | | `OwnerCameraManager` | `APlayerCameraManager` | `None` | `Cache` | Cached camera manager | | `ScanTimerHandle` | `FTimerHandle` | `-` | `Timing` | Timer for scan interval | | `DetectedActors_LastFrame` | `Set` | `{}` | `Tracking` | Previous frame targets for enter/exit detection | ### Replicated (if multiplayer) | Variable | Type | Condition | Description | |----------|------|-----------|-------------| | `CurrentTarget` | `S_InteractableTarget` | `Replicated` | Synced selected target | --- ## 4. Functions ### Public Functions #### `StartDetection` → `void` - **Description:** Begins the scan loop for interactable targets. - **Parameters:** None - **Flow:** 1. Get owner Player Controller 2. Get Player Camera Manager 3. Start scan timer (ScanInterval) 4. If bShowDebugTrace: enable debug line visualisation #### `StopDetection` → `void` - **Description:** Stops the scan loop and clears detected targets. - **Parameters:** None - **Flow:** 1. Clear scan timer 2. Clear DetectedTargets array 3. Set DetectionState = NoTarget 4. Fire OnTargetLost 5. Clear any highlight effects #### `PerformInteraction` → `S_InteractionResult` - **Description:** Executes interaction on the current best target. - **Parameters:** | Param | Type | Description | |-------|------|-------------| | `InputMode` | `E_InteractionInputMode` | How the input was triggered | - **Flow:** 1. If not CurrentTarget valid or CurrentTarget actor is None: return failure 2. If bIsPerformingInteraction: return failure (already interacting) 3. If InputMode != CurrentTarget.InputMode: return failure (input mismatch) 4. Set bIsPerformingInteraction = true 5. Set DetectionState = Interacting 6. Fire OnInteractionStarted 7. Call CurrentTarget.InterfaceRef.ExecuteInteraction (owner) 8. Wait for I_Interactable.OnInteractionCompleted or timeout 9. On completion: set bIsPerformingInteraction = false 10. Resume scanning 11. Return S_InteractionResult with success/failure #### `CancelInteraction` → `void` - **Description:** Aborts the current interaction if possible. - **Parameters:** None - **Flow:** 1. If not bIsPerformingInteraction: return 2. Call CurrentTarget.InterfaceRef.CancelInteraction 3. Set bIsPerformingInteraction = false 4. HoldInteractionProgress = 0.0 5. Set DetectionState = TargetConfirmed 6. Fire OnInteractionCancelled #### `GetBestTarget` → `S_InteractableTarget` - **Description:** Returns the current best target. - **Parameters:** None - **Flow:** Return CurrentTarget #### `HasTarget` → `Boolean` - **Parameters:** None - **Flow:** Return DetectionState >= TargetInRange #### `ForceSetTarget` → `void` - **Description:** Forcefully sets a specific target (for scripted interactions). - **Parameters:** | Param | Type | Description | |-------|------|-------------| | `TargetActor` | `AActor` | The specific actor to target | - **Flow:** 1. If TargetActor implements I_Interactable: - Build S_InteractableTarget - Set CurrentTarget - Set DetectionState = TargetConfirmed - Fire OnTargetFound 2. Else: fire OnInteractionError(not interactable) ### Private Functions #### `ScanForInteractables (Timer)` → `void` - **Description:** Timer callback — performs trace and sorts targets. - **Flow:** 1. If DetectionState == Interacting: return (skip scan while interacting) 2. Get camera forward vector and world location 3. Sphere trace forward (TraceRadius, InteractionRange, TraceChannel) 4. For each hit actor: - If implements I_Interactable: add to DetectedTargets - If not: skip 5. Remove duplicates by actor reference 6. Sort by Priority (descending), then Distance (ascending) 7. Clamp to MaxTargetsInRange 8. Track enter/exit for each actor — fire OnTargetEntered / OnTargetExited 9. Select BestTarget from sorted list 10. Update DetectionState accordingly 11. Fire OnTargetFound or OnTargetLost #### `CalculateInteractionScore` → `Float` - **Description:** Computes a score for a target based on priority, distance, and facing angle. - **Parameters:** | Param | Type | Description | |-------|------|-------------| | `Target` | `S_InteractableTarget` | The target to score | - **Flow:** 1. Score = Priority * 100 - Distance * 0.5 2. Facing bonus: if angle < 15 deg, add 50 3. Return Max(0, Score) #### `HighlightTarget` → `void` - **Description:** Applies highlight effect to target actor. - **Parameters:** | Param | Type | Description | |-------|------|-------------| | `Target` | `S_InteractableTarget` | Target to highlight | | `bHighlighted` | `Boolean` | Whether to enable or disable | - **Flow:** 1. Call Target.InterfaceRef.SetHighlighted(bHighlighted) #### `GetViewTraceOrigin` → `Vector, Vector` - **Description:** Returns camera location and forward direction for trace. - **Parameters:** None - **Flow:** 1. If OwnerCameraManager valid: - Return cam location and forward vector 2. Else: fall back to owner actor location and forward --- ## 5. Event Dispatchers | Dispatcher | Parameters | Bind Access | Description | |------------|-----------|-------------|-------------| | `OnTargetFound` | `S_InteractableTarget NewTarget` | `Public` | New best target selected | | `OnTargetLost` | `S_InteractableTarget LostTarget` | `Public` | Current target lost from view / range | | `OnTargetEntered` | `AActor TargetActor` | `Public` | Actor entered detectable range | | `OnTargetExited` | `AActor TargetActor` | `Public` | Actor left detectable range | | `OnInteractionStarted` | `S_InteractableTarget Target` | `Public` | Interaction began | | `OnInteractionCompleted` | `S_InteractionResult Result` | `Public` | Interaction finished | | `OnInteractionCancelled` | `S_InteractableTarget Target` | `Public` | Interaction was cancelled | | `OnInteractionError` | `FText ErrorMessage` | `Public` | Interaction blocked or failed | | `OnHoldProgressUpdated` | `Float Progress`, `S_InteractableTarget Target` | `Public` | Hold interaction progress [0..1] | --- ## 6. Overridden Events / Custom Events ### Event: `BeginPlay` - **Description:** Cache references, bind to relevant systems, start detection. - **Flow:** 1. Get owning actor → ensure it is a Pawn 2. Get Player Controller from owner 3. Get Player Camera Manager from controller 4. Bind to BPC_MovementStateSystem.OnPostureChanged — block when hidden 5. Bind to BPC_HealthSystem.OnDeathStateChanged — block when dead 6. StartDetection() ### Custom Event: `OnInputInteractPressed` - **Description:** Handles press input for interaction. - **Flow:** 1. If DetectionState < TargetConfirmed: return 2. If CurrentTarget.InputMode == Press: - PerformInteraction(Press) 3. If CurrentTarget.InputMode == Hold: - Start hold timer 4. If CurrentTarget.InputMode == DoubleTap: - Wait for second press within threshold ### Custom Event: `OnHoldProgressTick (Timer)` → `void` - **Description:** Ticks hold interaction progress and fires progress dispatcher. - **Flow:** 1. HoldInteractionProgress += DeltaTime / CurrentTarget.HoldDuration 2. Clamp to 1.0 3. Fire OnHoldProgressUpdated 4. If HoldInteractionProgress >= 1.0: PerformInteraction(Hold) ### Custom Event: `OnInputInteractReleased` - **Description:** Cancels hold interaction if button released early. - **Flow:** 1. If CurrentTarget.InputMode == Hold and not completed: - CancelInteraction() - HoldInteractionProgress = 0.0 --- ## 7. Blueprint Graph Logic Flow ```mermaid flowchart TD A[Scan Timer Fires] --> B[Get Camera Location / Forward] B --> C[SphereTraceForObjects] C --> D[Hit actors found?] D -->|No| E[Clear targets] E --> F[DetectionState = NoTarget] F --> G[Fire OnTargetLost] D -->|Yes| H[Filter by I_Interactable] H --> I[Sort by Priority then Distance] I --> J[Select BestTarget] J --> K{BestTarget changed?} K -->|Yes| L[Unhighlight old target] L --> M[Highlight new target] M --> N[Fire OnTargetFound] K -->|No| O[Keep current target] P[OnInputInteractPressed] --> Q{DetectionState >= Confirmed?} Q -->|No| R[Return] Q -->|Yes| S{InputMode match?} S -->|Press| T[PerformInteraction] S -->|Hold| U[Start hold timer] S -->|DoubleTap| V[Start double-tap timer] T --> W[Call ExecuteInteraction on target] W --> X[Fire OnInteractionStarted] X --> Y{Interaction succeeded?} Y -->|Yes| Z[Fire OnInteractionCompleted] Y -->|No| AA[Fire OnInteractionError] ``` --- ## 8. Communication Matrix | Who Talks | How | What Is Sent | |-----------|-----|-------------| | `BPC_InteractionDetector` | `Dispatcher` | `OnTargetFound` -> `WBP_InteractionPrompt` (show prompt) | | `BPC_InteractionDetector` | `Dispatcher` | `OnTargetLost` -> `WBP_InteractionPrompt` (hide prompt) | | `BPC_InteractionDetector` | `Dispatcher` | `OnInteractionStarted` -> `BPC_PlayerMetricsTracker` (log event) | | `BPC_InteractionDetector` | `Dispatcher` | `OnInteractionCompleted` -> `BPC_PlayerMetricsTracker` (log result) | | `BPC_InteractionDetector` | `Dispatcher` | `OnHoldProgressUpdated` -> `WBP_InteractionPrompt` (progress bar) | | `BPC_InteractionDetector` | `Dispatcher` | `OnInteractionCancelled` -> `WBP_InteractionPrompt` (hide) | | `BPC_InteractionDetector` | `Listener` | Binds to `BPC_MovementStateSystem.OnPostureChanged` | | `BPC_InteractionDetector` | `Listener` | Binds to `BPC_HealthSystem.OnDeathStateChanged` | | `BPC_InteractionDetector` | `Direct` | Calls `I_Interactable.ExecuteInteraction` on target | | `BPC_InteractionDetector` | `Direct` | Calls `I_Interactable.CancelInteraction` on target | | `BPC_InteractionDetector` | `Direct` | Calls `I_Interactable.SetHighlighted` on target | | `BPC_InteractionDetector` | `Direct call (other)` | `GetBestTarget` called by `WBP_InteractionPrompt` for name | | `PC_PlayerController` | `Input Event` | Binds press / release / hold to `OnInputInteractPressed` etc. | --- ## 9. Validation / Testing Checklist - [ ] Trace detects actors implementing I_Interactable at correct range - [ ] Priority sorting: Emergency targets always selected over Low - [ ] Same priority: closest target selected - [ ] Detection respects blocking states (dead, hiding) - [ ] Hold interaction progress updates and fires progress dispatcher - [ ] Double-tap interaction waits for second press within window - [ ] Cancelling hold interaction resets progress and fires cancelled - [ ] Interaction blocked during combat if bBlockDuringCombat is true - [ ] Targeting auto-switches if closer target enters range - [ ] Target exit detection fires when actor moves beyond max range - [ ] Edge case: No targets in range — DetectionState stays NoTarget - [ ] Edge case: Target destroyed mid-interaction — cancels gracefully - [ ] Edge case: Multiple interactables in trace — selects correctly - [ ] Debug trace visualisation draws correct sphere/line - [ ] Scan timer pauses while interacting, resumes after completion --- ## 10. Reuse Notes - TraceChannel should be set to a custom object channel (ECC_GameTraceChannel1) with only interactable actors responding. - Priority values can be overridden per actor via the `I_Interactable` interface's `GetInteractionPriority` function. - The hold interaction progress is designed to drive a radial progress widget in the HUD. - For controller/console: `E_InteractionInputMode.Hold` is preferred to prevent accidental interactions. - The same component can be attached to NPCs if they need interaction detection. - To support networked games: replicate CurrentTarget and have each client run their own trace locally. --- *Blueprint Spec: Interaction Detector. Conforms to TEMPLATE.md v1.0 — part of the UE5 Modular Game Framework.*