Skip to main content

Interaction Guide

Complete guide to implementing proximity-based interactions using ElysAwareness.

Overview

The interaction system builds on top of Perception via the UERPDomainComponent base class to provide eligibility validation, state management, and multiplayer-safe execution.

Architecture:

  • Perception: Selects the best candidate locally (client-side detection)
  • Domain (Interaction): UERPInteractionComponent extends UERPDomainComponent, validates eligibility using IERPInteractable and optional rules
  • Presentation: UERPInteractableComponent on the candidate actor manages focus feedback (outline + widget) automatically. Alternatively, listen to domain events to drive your own HUD.

ERPInteractionComponent

ERPInteractionComponent extends UERPDomainComponent and provides complete interaction state management.

What it does:

  • Inherits automatic channel binding and candidate tracking from UERPDomainComponent
  • Overrides IsCandidateEligible to check IERPInteractable + CanBeInteractedWith + rules
  • Maintains a FERPInteractionDescriptor (by value) with all available actions for the current candidate
  • Handles StartInteractionAttempt(UInputAction*) / StopInteractionAttempt(UInputAction*) — routes input to the correct action (instant, hold, multi-press, wheel)
  • Manages the challenge lifecycle per attempt (DuplicateObject per attempt, delegate routing)
  • Handles the wheel flow: fires OnWheelRequested, receives SubmitWheelSelection / CancelWheelSelection from the widget
  • Handles networking: Server RPC with ActionIndex, client result callbacks
  • Emits events: OnDomainCandidateChanged, OnDescriptorChanged, OnInteractionExecuted, OnInteractionFailed, OnInteractionProgress, OnWheelRequested, OnWheelDismissed

Multiplayer Support

The interaction component handles networking automatically:

  • Standalone: Executes interaction directly
  • Client: Sends Server_RequestInteraction(Actor, ActionIndex) RPC
  • Server: Validates distance, perception filters, eligibility, then executes
  • Server → Client: Notifies via Client_OnInteractionSuccess / Client_OnInteractionFailed

ActionIndex is resolved client-side from UInputAction* before the RPC — this avoids serializing UInputAction pointers across the network.

The component has SetIsReplicatedByDefault(true) to support Server/Client RPCs.


Basic Setup

1. Configure Interaction Pipeline

On your PlayerController or Pawn, add and configure ERPAwarenessComponent:

Interaction Pipeline Settings:

  • Channel Id: Interact
  • Default Sampling Range: 300.0 (shorter than targeting)
  • Sampler: ERPSphereOverlapSampler
    • Trace Channel: Visibility
    • Ignore Context Actor: check
  • Filters: ERPInteractableFilter
  • Scorers: ERPDistanceScorer (normalize by range), Weight: 1.0
  • ResolverPolicy: LowestScore (default)

2. Add Interaction Component

Add ERPInteractionComponent to your PlayerController or Pawn:

Configuration:

  • Channel Id: Interact (must match pipeline)
  • Max Interaction Distance: 500.0 (server-side validation distance)
  • Rules: (optional instanced rules)

3. Set Up the Interactable Actor

Add UERPInteractableComponent to any actor you want to be interactable. This implements IERPInteractable and manages visual feedback automatically.

Component properties (configure in the Details panel):

PropertyDescription
DefaultDescriptorStatic descriptor (ObjectName, ObjectDescription, ObjectIcon, Actions, MultiActionMode)
SingleActionWidgetClassWidget shown when the descriptor has exactly one action
MultiActionWidgetClassWidget shown when 2+ actions in DirectKeys mode (action list)
WheelWidgetClassWidget shown when 2+ actions in Wheel mode (radial selector)
AmbientWidgetClassAlways-visible world marker shown outside interaction range (optional)
WidgetClassOverrideOverrides all of the above — always uses this class
WidgetOffsetOffset from attachment point (e.g. (0, 0, 100) = 100 cm up)
WidgetAnchorTagComponent tag to use as widget anchor instead of root (default: ERP_WidgetAnchor)
bEnableCustomDepthFeedbackEnable outline on focus (default: true)
CustomDepthStencilValueStencil value for the outline (default: 1)

Blueprint — bind the interaction event:

Event BeginPlay
-> Get Component (ERPInteractableComponent)
-> Bind Event to OnInteracted
-> Your gameplay logic — ActionIndex tells you which action was executed

Bind BuildDescriptor for dynamic descriptors (e.g. a door that shows "Open" or "Close"):

Event BeginPlay
-> Get Component (ERPInteractableComponent)
-> Bind Event to BuildDescriptor
-> Your function (Actor* Interactor) -> returns FERPInteractionDescriptor

The system also detects actors that implement IERPInteractable directly — see the C++ section for details.

4. Handle Input

Bind your input actions to StartInteractionAttempt() and StopInteractionAttempt(), passing the UInputAction that triggered the event:

Input Action: IA_Interact (Started)
-> InteractionComponent -> StartInteractionAttempt(IA_Interact)

Input Action: IA_Interact (Completed / Cancelled)
-> InteractionComponent -> StopInteractionAttempt(IA_Interact)

Input Action: IA_Inspect (Started)
-> InteractionComponent -> StartInteractionAttempt(IA_Inspect)

Input Action: IA_Inspect (Completed / Cancelled)
-> InteractionComponent -> StopInteractionAttempt(IA_Inspect)

Passing null to StartInteractionAttempt uses the primary action (Actions[0]).

StartInteractionAttempt() inspects the descriptor and dispatches accordingly:

Descriptor StateBehaviour
No descriptor or empty Actions arrayLogs a warning and returns — nothing happens
TriggeredAction not found in ActionsLogs a warning and returns — check that the InputAction in your descriptor matches the one you pass to StartInteractionAttempt
Wheel mode (Actions.Num() > 1, Wheel)Opens the wheel — fires OnWheelRequested(Actions)
Action with no ChallengeExecutes immediately
Action with UERPHoldChallengeStarts the hold timer, fires OnInteractionProgress each tick
Action with UERPMultiPressChallengeIncrements press count, fires OnInteractionProgress
Common pitfall

If pressing the interact key does nothing, check that your FERPInteractionDescriptor has at least one entry in its Actions array, and that each action has its InputAction field set (e.g. IA_ERP_Interact). An empty Actions array is the most common cause of silent interaction failures.

Wheel input flow:

// Your wheel widget opens via OnWheelRequested:
Event: OnWheelRequested (Actions)
-> Open your wheel widget, populate from Actions

// When the player selects an action in the wheel widget:
-> InteractionComponent -> SubmitWheelSelection(SelectedInputAction)

// To cancel without selecting:
-> InteractionComponent -> CancelWheelSelection()

Visual Feedback (Optional)

Visual feedback is entirely optional. Choose the approach that fits your project:

ApproachWhen to use
UERPInteractableComponentWidget anchored near the actor; outline; no PlayerController wiring needed for the visual side
Custom HUD from eventsFixed-position HUD widget driven from the PlayerController
Fully customHandle everything from perception events directly

Approach 1: UERPInteractableComponent

This is the all-in-one approach for actor-side feedback. It manages:

  • An ambient UWidgetComponent visible from a distance (when AmbientWidgetClass is set)
  • An interaction UWidgetComponent shown on focus (swaps with the ambient widget)
  • A custom depth outline when the actor is focused
  • Automatic widget class selection based on descriptor action count and mode

How it works internally:

ERPInteractionComponent (on PlayerController)
→ detects candidate change
→ FindComponentByClass<UERPInteractableComponent> on the candidate actor
→ SetInteractionFocused(PC, true/false) → shows/hides widget, fires OnInteractionFocused
→ UpdateInteractionDescriptor(PC, Descriptor) → updates widget class + content, fires OnDescriptorUpdated
→ UpdateInteractionProgress(PC, Action, Progress) → drives challenge widget, fires OnInteractionProgress

Widget class selection:

PriorityConditionClass Used
1WidgetClassOverride is setAlways WidgetClassOverride
2Actions.Num() > 1 + Wheel modeWheelWidgetClass
3Actions.Num() > 1 + DirectKeys modeMultiActionWidgetClass
4OtherwiseSingleActionWidgetClass

Widget positioning:

Use WidgetOffset (FVector) for a simple offset from the actor's root:

  • (0, 0, 150) — 150 cm above the root component

For precise control, add a scene component to your actor Blueprint with the tag ERP_WidgetAnchor. The widget attaches to that component instead of the root.

WidgetSpace defaults to Screen — the component projects from the anchor's world position to screen.

Blueprint NativeEvents (override in a Blueprint child of UERPInteractableComponent):

EventWhen it firesTypical use
OnInteractionFocused(PC, bFocused)Actor gains/loses focusAdditional visual feedback beyond outline
OnDescriptorUpdated(PC, Descriptor)Descriptor refreshed while focusedReact to dynamic descriptor changes
OnInteractionProgress(PC, Action, Progress)Challenge in progress (0..1)Drive custom progress UI outside the widget

Overriding CanBeInteractedWith

Bind the CanInteractCheck delegate to control interactability at runtime:

Blueprint:

Event BeginPlay
-> Get Component (ERPInteractableComponent)
-> Bind Event to CanInteractCheck
-> Your function (Actor* Interactor) -> returns bool
-> e.g., Return: NOT bIsLocked

C++:

InteractableComponent->CanInteractCheck.BindUObject(this, &AYourActor::CheckCanInteract);

bool AYourActor::CheckCanInteract(AActor* Interactor)
{
return !bIsLocked;
}

If not bound, the default is true (always interactable).

Dynamic Descriptors via BuildDescriptor

Bind BuildDescriptor to generate the descriptor at runtime:

Blueprint:

Event BeginPlay
-> Get Component (ERPInteractableComponent)
-> Bind Event to BuildDescriptor
-> Your function (Actor* Interactor) -> returns FERPInteractionDescriptor
-> Make FERPInteractionAction:
ActionName: Branch(bIsOpen, "Close", "Open")
InputAction: IA_Interact
-> Make FERPInteractionDescriptor:
ObjectName: "Door"
Actions: [Action]
-> Return Descriptor

C++:

InteractableComponent->BuildDescriptor.BindUObject(this, &AYourDoor::GetCurrentDescriptor);

FERPInteractionDescriptor AYourDoor::GetCurrentDescriptor(AActor* Interactor)
{
FERPInteractionAction Action;
Action.ActionName = bIsOpen
? FText::FromString(TEXT("Close"))
: FText::FromString(TEXT("Open"));
Action.InputAction = InteractAction;

FERPInteractionDescriptor Desc;
Desc.ObjectName = FText::FromString(TEXT("Door"));
Desc.Actions.Add(Action);
return Desc;
}

If not bound, DefaultDescriptor is used.

Approach 2: Custom HUD from Domain Events

Skip UERPInteractableComponent entirely. Listen to ERPInteractionComponent events on the player side and drive your own HUD widget:

Event: OnDescriptorChanged (Candidate, Descriptor, ChannelId)
-> Update your HUD widget with Descriptor.ObjectName, Actions, etc.

Event: OnDomainCandidateChanged (Previous, New, ChannelId)
-> Show/hide your widget based on IsValid(New)

Event: OnInteractionProgress (Action, Progress)
-> Update challenge progress bar on your HUD
// Action identifies which action's challenge is progressing

Event: OnWheelRequested (Actions)
-> Open your wheel widget, populate with Actions

Event: OnWheelDismissed (bCancelled)
-> Close your wheel widget

The widget can be positioned anywhere on screen, independent of the interactable actor's location.

Approach 3: Fully Custom From Perception Events

Skip both the interaction component and feedback. Listen directly to perception events:

Perception->OnChannelCandidateAcquired  // raw candidate detected
Perception->OnChannelCandidateLost // raw candidate lost
Perception->OnChannelEvaluated // all scored candidates

Blueprint Examples

Minimal Setup (UERPInteractableComponent)

PlayerController Components:

  • ERPAwarenessComponent (interaction pipeline configured)
  • ERPInteractionComponent (listening to "Interact" channel)

Interactable Actor Components:

  • ERPInteractableComponent
    • DefaultDescriptor.ObjectName: "Door"
    • DefaultDescriptor.Actions[0].ActionName: "Open"
    • DefaultDescriptor.Actions[0].InputAction: IA_Interact
    • SingleActionWidgetClass: WBP_SinglePrompt (your widget Blueprint)
    • WidgetOffset: (0, 0, 150)

Interactable Actor Event Graph:

Event BeginPlay
-> Get Component (ERPInteractableComponent)
-> Bind Event to OnInteracted
-> Switch on ActionIndex
-> 0: Toggle Door State

PlayerController Event Graph:

Input Action: IA_Interact (Started)
-> InteractionComponent -> StartInteractionAttempt(IA_Interact)

Input Action: IA_Interact (Completed)
-> InteractionComponent -> StopInteractionAttempt(IA_Interact)

Event: OnInteractionExecuted (InteractableActor)
-> Your gameplay logic

Event: OnInteractionFailed (InteractableActor, Reason)
-> Your error handling

Multi-Action Setup (DirectKeys)

Example: a chest that can be Opened (IA_Interact) or Inspected (IA_Inspect).

Interactable Actor:

  • ERPInteractableComponent
    • DefaultDescriptor.MultiActionMode: DirectKeys
    • DefaultDescriptor.Actions[0]: ActionName="Open", InputAction=IA_Interact
    • DefaultDescriptor.Actions[1]: ActionName="Inspect", InputAction=IA_Inspect
    • MultiActionWidgetClass: WBP_MultiActionPrompt

PlayerController:

Input Action: IA_Interact (Started)
-> InteractionComponent -> StartInteractionAttempt(IA_Interact)

Input Action: IA_Inspect (Started)
-> InteractionComponent -> StartInteractionAttempt(IA_Inspect)

The component resolves which action matches the UInputAction* and executes it.

Wheel Setup

Example: an NPC with Talk, Trade, and Quest actions opened via one input.

Interactable Actor:

  • ERPInteractableComponent
    • DefaultDescriptor.MultiActionMode: Wheel
    • DefaultDescriptor.Actions[0]: ActionName="Talk", InputAction=IA_Talk
    • DefaultDescriptor.Actions[1]: ActionName="Trade", InputAction=IA_Trade
    • DefaultDescriptor.Actions[2]: ActionName="Quest", InputAction=IA_Quest
    • WheelWidgetClass: WBP_ActionWheel

PlayerController:

Input Action: IA_Interact (Started)
-> InteractionComponent -> StartInteractionAttempt()
// Fires OnWheelRequested(Actions) — opens your wheel widget

Event: OnWheelRequested (Actions)
-> WheelWidget -> PopulateSlices(Actions)
-> WheelWidget -> SetVisibility(Visible)

// Widget calls this when player confirms:
-> InteractionComponent -> SubmitWheelSelection(SelectedAction)

// Widget calls this on cancel:
-> InteractionComponent -> CancelWheelSelection()

Widget Integration

Widgets are UUserWidget subclasses of UERPInteractionWidgetBase. The component manages widget lifecycle automatically; your widget overrides events to display data.

Actions.Num()MultiActionModeWidget Class Used
1AnySingleActionWidgetClass
2+DirectKeysMultiActionWidgetClass
2+WheelWheelWidgetClass
Any(override set)WidgetClassOverride

Widget base class hooks:

EventWhen firedWhat to do
OnDescriptorUpdated(Descriptor)Descriptor changesUpdate ObjectName, ObjectDescription, ObjectIcon
OnActionsUpdated(Actions)Action list changesBuild action rows or wheel slices
OnActionProgress(Action, Progress)Challenge progressingRoute to challenge sub-widget
OnFocusChanged(bFocused)Focus gained/lostShow/hide animations

Challenge sub-widgets (UERPChallengeWidgetBase) display hold rings or press counters. Create one per action row; forward progress from OnActionProgress to the matching sub-widget.

For detailed widget setup, see the Widget Guide.


C++ Example

PlayerController Setup

#include "Domains/Interaction/ERPInteractionComponent.h"

AYourPlayerController::AYourPlayerController()
{
PerceptionComponent = CreateDefaultSubobject<UERPAwarenessComponent>(TEXT("Perception"));
InteractionComponent = CreateDefaultSubobject<UERPInteractionComponent>(TEXT("Interaction"));
// ChannelId defaults to "Interact"
}

void AYourPlayerController::BeginPlay()
{
Super::BeginPlay();
InteractionComponent->OnDescriptorChanged.AddDynamic(this, &AYourPlayerController::OnDescriptorChanged);
InteractionComponent->OnInteractionExecuted.AddDynamic(this, &AYourPlayerController::OnInteractionExecuted);
InteractionComponent->OnWheelRequested.AddDynamic(this, &AYourPlayerController::OnWheelRequested);
}

void AYourPlayerController::HandleInteractInputPressed()
{
InteractionComponent->StartInteractionAttempt(IA_Interact); // UInputAction* set in header
}

void AYourPlayerController::HandleInteractInputReleased()
{
InteractionComponent->StopInteractionAttempt(IA_Interact);
}
#include "Domains/Interaction/ERPInteractableComponent.h"
#include "Domains/Interaction/ERPInteractionAction.h"
#include "Domains/Interaction/ERPInteractionDescriptor.h"

AYourDoor::AYourDoor()
{
InteractableComponent = CreateDefaultSubobject<UERPInteractableComponent>(TEXT("Interactable"));
InteractableComponent->WidgetOffset = FVector(0.f, 0.f, 150.f);
}

void AYourDoor::BeginPlay()
{
Super::BeginPlay();
InteractableComponent->BuildDescriptor.BindUObject(this, &AYourDoor::GetCurrentDescriptor);
InteractableComponent->OnInteracted.AddDynamic(this, &AYourDoor::OnInteracted);
}

FERPInteractionDescriptor AYourDoor::GetCurrentDescriptor(AActor* Interactor)
{
FERPInteractionAction Action;
Action.ActionName = bIsOpen
? FText::FromString(TEXT("Close"))
: FText::FromString(TEXT("Open"));
Action.InputAction = InteractAction; // UInputAction* set in editor

FERPInteractionDescriptor Desc;
Desc.ObjectName = FText::FromString(TEXT("Wooden Door"));
Desc.Actions.Add(Action);
return Desc;
}

void AYourDoor::OnInteracted(AActor* InstigatorActor, int32 ActionIndex)
{
bIsOpen = !bIsOpen;
// Play animation, replicate state, etc.
}

Interactable Actor — Direct Interface (Advanced)

For actors that implement IERPInteractable directly without the component:

#include "Domains/Interaction/ERPInteractable.h"
#include "Domains/Interaction/ERPInteractionAction.h"

class AYourDoor : public AActor, public IERPInteractable
{
GENERATED_BODY()

public:
virtual bool CanBeInteractedWith_Implementation(AActor* InteractingActor) const override
{
return true;
}

virtual void GetInteractionDescriptor_Implementation(FERPInteractionDescriptor& OutDescriptor) const override
{
FERPInteractionAction Action;
Action.ActionName = bIsOpen
? FText::FromString(TEXT("Close"))
: FText::FromString(TEXT("Open"));
Action.InputAction = InteractAction;

OutDescriptor.ObjectName = FText::FromString(TEXT("Door"));
OutDescriptor.Actions.Add(Action);
}

// ActionIndex is the index into Descriptor.Actions that was executed.
// Pass INDEX_NONE (-1) or 0 for the primary action.
virtual bool ExecuteInteraction_Implementation(AActor* InstigatorActor, int32 ActionIndex) override
{
bIsOpen = !bIsOpen;
return true;
}

protected:
bool bIsOpen = false;
};
note

Actors using the direct interface don't get automatic widget and outline feedback. Drive those from PlayerController events (OnDescriptorChanged, OnDomainCandidateChanged) if needed.


Advanced Patterns

Challenge-Based Interactions

Challenges are instanced UERPInteractionChallenge subclasses set on each FERPInteractionAction. The interaction component creates a DuplicateObject per attempt so concurrent players don't share mutable state.

Available challenges:

ClassDescriptionKey Properties
UERPHoldChallengeHold the key for a durationHoldDuration (seconds)
UERPMultiPressChallengePress the key N timesRequiredPresses, PressCooldown, DecayPerSecond

To configure a challenge in Blueprint — set it on the FERPInteractionAction.Challenge property in the Details panel (it's instanced/inline).

// C++ — create a hold challenge on an action
FERPInteractionAction Action;
Action.ActionName = FText::FromString(TEXT("Loot"));
Action.InputAction = IA_Interact;

UERPHoldChallenge* HoldChallenge = NewObject<UERPHoldChallenge>(this);
HoldChallenge->HoldDuration = 2.0f;
Action.Challenge = HoldChallenge;

Desc.Actions.Add(Action);

Interaction Rules

Add instanced UERPInteractionRule objects to the InteractionComponent for composable validation:

UCLASS(Blueprintable, EditInlineNew)
class UMyDistanceRule : public UERPInteractionRule
{
GENERATED_BODY()
public:
UPROPERTY(EditAnywhere) float MaxDistance = 300.f;

virtual bool CanInteract_Implementation(AActor* Instigator, AActor* Target, FName ChannelId) const override
{
return FVector::Dist(Instigator->GetActorLocation(), Target->GetActorLocation()) <= MaxDistance;
}
};

Server-Side Validation

The Server_RequestInteraction(Actor, ActionIndex) RPC validates:

  1. Distance: MaxInteractionDistance property on the component
  2. Perception Filters: Re-runs channel filters server-side via ValidateActorForChannel
  3. Eligibility: Re-checks CanBeInteractedWith and all rules
  4. Execution: Calls ExecuteInteraction(InstigatorActor, ActionIndex) on the interactable

Dynamic Descriptors

Descriptors are pulled from GetInteractionDescriptor (or BuildDescriptor) when the candidate changes. If the same interactable changes its descriptor while remaining the active candidate, call ForceRefreshDescriptor():

// After changing internal state that affects the descriptor:
InteractionComponent->ForceRefreshDescriptor();

This re-pulls the descriptor and if it changed:

  • Broadcasts OnDescriptorChanged
  • Calls UpdateInteractionDescriptor on UERPInteractableComponent (updates the actor-side widget)

Hold-to-Interact with Animation

Play a montage that tracks the player's hold progress, scrubbing the animation as the key is held.

Setup:

  1. Add a UERPHoldChallenge to your action's Challenge property
  2. Bind OnInteractionProgress on the interaction component

Blueprint pattern:

Event: OnInteractionProgress (Action, Progress)
-> Branch: Progress > 0
True:
-> Get Montage Length (YourMontage) -> Multiply: Progress * Length -> Result = Position
-> Montage Set Position (YourMontage, Position)
-> Branch: Is Montage Playing?
False: -> Montage Play (YourMontage, PlayRate = 0.0)
False:
-> Montage Stop (YourMontage, BlendOutTime = 0.2)

Event: OnInteractionExecuted (InteractableActor)
-> Montage Stop (HoldMontage)
-> Montage Play (CompletionMontage)

Key points:

  • Play the montage with PlayRate = 0.0 so it doesn't advance on its own
  • Scrub the position using Progress * MontageLength
  • When Progress resets to 0 (key released), stop the montage
  • On completion, play a separate completion montage

Continuous Interactions (Grab and Push)

Let the player hold a key to grab an object and move it while held. The interaction never "completes" — it cancels when the key is released.

Setup:

  1. Create a Hold challenge with a very long HoldDuration (e.g., 999 seconds)
  2. The challenge will never complete naturally — it acts as a "held" state

Blueprint pattern (interactable actor):

Event: OnInteracted (InstigatorActor, ActionIndex)
-> This fires when the hold challenge starts (not completes)
-> Skip this — use the component events below instead

// On the PlayerController, bind the interaction component events:

Event: OnInteractionProgress (Action, Progress)
-> Branch: Progress > 0 AND NOT bIsGrabbing
True:
-> Set bIsGrabbing = true
-> Attach Physics Constraint (Player -> Crate)
-> Montage Play (GrabMontage)

Event: OnInteractionProgress (Action, Progress)
-> Branch: Progress == 0 AND bIsGrabbing
True:
-> Set bIsGrabbing = false
-> Remove Physics Constraint
-> Montage Play (ReleaseMontage)

The hold challenge's OnInputReleased resets progress to 0, which you detect to release the grab. The challenge never reaches 100% because the duration is effectively infinite.


QTE Challenges

Three built-in QTE challenge types for more dynamic interactions.

Timed Press

ERPTimedPressChallenge — the player must press the action key before a countdown expires.

Configuration:

PropertyTypeDefaultDescription
TimeWindowfloat3.0Seconds before the challenge fails

Behavior:

  • Progress counts down from 1 to 0
  • If the player presses before timeout: OnComplete(true)
  • If the timer expires: OnComplete(false)

Widget: ERPBaseTimedPressChallengeWidget — shows a countdown bar and key prompt. See Built-in Widgets for style properties.

Blueprint setup:

// On the interactable actor's descriptor:
Action.Challenge = ERPTimedPressChallenge
-> TimeWindow = 2.5

// The interaction component handles the rest automatically.
// Bind OnInteractionExecuted for success, OnInteractionFailed for timeout.

Sequence

ERPSequenceChallenge — the player must press a series of input actions in order.

Configuration:

PropertyTypeDefaultDescription
SequenceTArray<UInputAction*>Ordered list of input actions to press
bFailOnWrongKeybooltrueFail immediately if the wrong key is pressed
TimeLimitfloat0.0Max seconds to complete (0 = no limit)

Behavior:

  • Progress = completed keys / total keys (e.g., 2/4 = 0.5)
  • Each correct press advances to the next key
  • Wrong key press: fails if bFailOnWrongKey is true, ignored otherwise

Blueprint setup:

// Configure the challenge:
Action.Challenge = ERPSequenceChallenge
-> Sequence: [IA_LightAttack, IA_HeavyAttack, IA_LightAttack, IA_Dodge]
-> bFailOnWrongKey = true
-> TimeLimit = 5.0

// In your widget, use GetNextExpectedAction() to show which key to press next.
// Progress updates after each correct press.

Use GetCurrentKeyIndex() and GetNextExpectedAction() to drive UI that highlights the current key in the sequence.

Timing

ERPTimingChallenge — the player must press when a moving cursor is inside a target zone (like a fishing minigame or forge timing bar).

Configuration:

PropertyTypeDefaultDescription
CursorSpeedfloat1.0How fast the cursor moves (cycles per second)
TargetCenterfloat0.5Center of the target zone (0..1 along the track)
TargetHalfWidthfloat0.1Half-width of the target zone
bFailOnMissboolfalseFail immediately if the player presses outside the zone
RequiredHitsint321Number of successful hits needed to complete

Behavior:

  • Cursor oscillates back and forth along a 0..1 track
  • Player presses the action key when the cursor is inside [TargetCenter - HalfWidth, TargetCenter + HalfWidth]
  • Hit inside zone: increments hit count; miss: fails or is ignored based on bFailOnMiss
  • Progress = current hits / required hits

Widget: ERPBaseTimingChallengeWidget — shows a horizontal track with a moving cursor and a green target zone. See Built-in Widgets for style properties.

Blueprint setup:

// Configure the challenge:
Action.Challenge = ERPTimingChallenge
-> CursorSpeed = 1.5
-> TargetCenter = 0.6
-> TargetHalfWidth = 0.08
-> bFailOnMiss = true
-> RequiredHits = 3

// Use for: fishing, lockpicking, forge hammer timing
// Call GetCursorPosition() and IsCursorInTargetZone() from your widget to drive visuals.

How MarkDescriptorDirty Works

When your interactable changes its data while it is already the focused candidate, the system does not detect the change automatically. You need to tell it to re-read the descriptor so the widget updates.

When to call it: whenever the interactable's state changes while the player is looking at it. Examples: a door goes from "Open" to "Locked", a cooldown expires, an inventory update changes the available actions.

Two ways to trigger it:

  1. From the interactable actor: call MarkDescriptorDirty() on UERPInteractableComponent. This notifies the interaction component to re-read the descriptor.
  2. From the player side: call ForceRefreshDescriptor() on UERPInteractionComponent if you do not have access to the interactable component.

The built-in cooldown system calls this automatically when a cooldown starts or expires -- you do not need to call it yourself for cooldowns.

Example: a chest that changes from "Open" to "Already Looted" after interaction:

Event: OnInteracted (InstigatorActor, ActionIndex)
-> Set bLooted = true
-> Get Component (ERPInteractableComponent) -> MarkDescriptorDirty()
// BuildDescriptor now returns "Already Looted" instead of "Open"

Interaction Cooldowns

Three approaches for preventing repeated interactions: built-in per-action cooldowns, state-based cooldowns, and manual gating.

Built-In Per-Action Cooldowns

The simplest approach. Set the Cooldown field directly on FERPInteractionAction:

FieldTypeDefaultDescription
Cooldownfloat0.0Seconds before this action can be used again
CooldownScopeEERPCooldownScopeThisActionWhich actions are blocked during cooldown

CooldownScope options:

  • ThisAction -- only this specific action is disabled during cooldown. Other actions on the same interactable remain available.
  • AllActions -- all actions on this interactable are blocked. The widget hides entirely during cooldown.

Blueprint setup:

// In your descriptor (DefaultDescriptor or BuildDescriptor):
Make FERPInteractionAction:
ActionName: "Harvest"
InputAction: IA_Interact
Cooldown: 5.0
CooldownScope: ThisAction

Querying cooldown state from the player side:

InteractionComponent -> IsActionOnCooldown(IA_Interact) -> bool
InteractionComponent -> GetActionCooldownRemaining(IA_Interact) -> float (seconds)

Cooldowns reset automatically when the player changes target (looks at a different interactable).

Pattern 1: State-Based Cooldown (Instanced Interaction)

Best for ERPInstanceProxy or any actor with a state machine. Add a cooldown state step that auto-advances back to the ready state.

StateSteps:
[0] State: ERP.Instance.Ready
Descriptor: Actions = [Open]
Duration: 0 (waits for interaction)

[1] State: ERP.Instance.Cooldown
Descriptor: Actions = [] (empty = not interactable)
Duration: 5.0 (auto-advance after 5 seconds)
bLoopToFirst: true (returns to step 0)

During the cooldown step, the actor has no actions so no prompt appears. After 5 seconds it loops back to step 0 and becomes interactable again.

Pattern 2: CanBeInteractedWith Gate

Best for regular actors with ERPInteractableComponent. Track a timer in your actor and block interactions until it expires.

Blueprint:

// Variables: CooldownEndTime (float), CooldownDuration (float, default 5.0)

Event BeginPlay
-> Get Component (ERPInteractableComponent)
-> Bind Event to CanInteractCheck
-> Your Function:
-> Return: GetGameTimeSinceCreation >= CooldownEndTime

Event: OnInteracted (InstigatorActor, ActionIndex)
-> Set CooldownEndTime = GetGameTimeSinceCreation + CooldownDuration
-> ForceRefreshDescriptor() // Updates the widget immediately
-> Your gameplay logic

Call ForceRefreshDescriptor() after starting the cooldown so the widget updates (e.g., hides the prompt or shows a "cooling down" state).

tip

For most use cases, the built-in Cooldown field on FERPInteractionAction is the easiest approach. Use state-based or manual gating only when you need custom cooldown visuals or logic.


Troubleshooting

SymptomCauseFix
Nothing happens when pressing interactDescriptor's Actions array is emptyAdd at least one FERPInteractionAction with an InputAction to your descriptor
Nothing happens, but prompt is visibleThe InputAction passed to StartInteractionAttempt doesn't match any action in the descriptorEnsure the same UInputAction asset (e.g. IA_ERP_Interact) is used in both the descriptor's Actions and your input binding
No prompt appearsAwareness pipeline doesn't resolve the interactableCheck that the interactable has a collision channel matching your sampler's TraceChannel, and that it passes all pipeline filters
"Too far" failure in logServer-side distance validationIncrease MaxInteractionDistance on UERPInteractionComponent, or move closer
"Execution failed" in logCanBeInteractedWith returned false, or a Rule failedCheck your CanInteractCheck delegate and any Rules on the interaction component
Interaction works once then stopsDescriptor changes after first interaction (e.g. CanBeInteractedWith now returns false)Verify your interactable's state doesn't inadvertently block re-interaction
Logging

All interaction warnings are logged under the LogElysAwareness category. Filter your Output Log by this category to see detailed diagnostics.


Next Steps

Build your interaction UI with Widget Guide.

Set up targeting with Targeting Guide.

Create custom domains with Custom Domains.

Check complete API in API Reference.