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 candidate locally (client-side detection)
  • Domain (Interaction): UERPInteractionComponent extends UERPDomainComponent, validates eligibility using IERPInteractable interface and rules
  • Presentation: Optional. If an ERPBaseInteractionFeedbackComponent is present on the candidate actor, it is notified automatically. Otherwise, use domain events to drive your own UI.

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 interface + CanBeInteractedWith + rules
  • Maintains interaction descriptor (by value)
  • Handles RequestInteraction() with automatic networking (Server RPC + validation)
  • Emits events: OnDomainCandidateChanged, OnDescriptorChanged, OnInteractionExecuted, OnInteractionFailed

Multiplayer Support

The interaction component handles networking automatically:

  • Standalone: Executes interaction directly
  • Client: Sends Server_RequestInteraction RPC
  • Server: Validates distance, perception filters, eligibility, then executes
  • Server -> Client: Notifies via Client_OnInteractionSuccess / Client_OnInteractionFailed

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
  • Resolver: ERPResolverBase (lowest score wins)

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. Implement Interactable Interface

On actors you want to interact with (doors, items, NPCs):

Blueprint:

  1. Class Settings -> Interfaces -> Add ERPInteractable
  2. Implement Can Be Interacted With -> return true
  3. Implement Get Interaction Descriptor:
Event: Get Interaction Descriptor
-> Make Interaction Descriptor
- Display Name: "Door"
- Prompt Text: "Press E to Open"
- Input Action: IA_ERP_Interact (optional, for key display in widgets)
- Icon: T_DoorIcon (optional, soft reference)
- Hold Duration: 0.0 (instant) or 2.0 (hold for 2 seconds)
-> Set Out Descriptor
  1. Implement Execute Interaction:
Event: Execute Interaction (Instigator Actor)
-> Toggle Door State
-> Return true

4. Handle Input

Bind your interact action to RequestInteraction():

Input Action: Interact
-> InteractionComponent -> RequestInteraction()
// Handles networking automatically

Visual feedback is handled separately — see the next section. If your interactable actors have an ERPBaseInteractionFeedbackComponent, feedback is automatic. Otherwise, bind to OnDescriptorChanged / OnDomainCandidateChanged to drive your own UI.


Visual Feedback (Optional)

Visual feedback is entirely optional. The interaction system works the same whether or not you use the provided feedback component. Choose the approach that fits your project:

ApproachWhen to use
Auto-wired feedbackYou want out-of-the-box feedback on interactable actors
Custom UI from eventsYou want full control over your UI, driven by domain events
Fully customYou want to handle everything from perception events directly

Approach 1: Auto-Wired Feedback (ERPBaseInteractionFeedbackComponent)

Add an ERPBaseInteractionFeedbackComponent (or a Blueprint subclass of it) to an interactable actor. That's it. The ERPInteractionComponent automatically detects it and calls SetInteractionFocused / UpdateInteractionDescriptor when the actor gains or loses focus. No manual wiring needed on the PlayerController.

If the actor doesn't have this component, the interaction system silently skips the feedback — interactions still work, just without visual feedback.

How it works internally:

ERPInteractionComponent (on PlayerController)
→ detects candidate change
→ FindComponentByClass<ERPBaseInteractionFeedbackComponent> on the candidate actor
→ calls SetInteractionFocused(PC, true/false) → fires OnInteractionFocused
→ calls UpdateInteractionDescriptor(PC, Descriptor) → fires OnDescriptorUpdated

Blueprint hooks (override these in your Blueprint subclass):

EventWhen it firesTypical use
OnInteractionFocused(PC, bFocused)Actor gains/loses interaction focusShow/hide widget, enable/disable outline, play sound
OnDescriptorUpdated(PC, Descriptor)Descriptor changes (new candidate or ForceRefreshDescriptor)Push descriptor data to your widget

These are BlueprintImplementableEvent — the base class does nothing, you implement whatever visual behavior you want.

Built-in utility: Call ApplyDefaultCustomDepthFeedback(bEnable) from your OnInteractionFocused override for a standard custom depth outline. This toggles RenderCustomDepth on the owner's mesh components using CustomDepthStencilValue.

Default Implementation: ERP_DefaultInteractionFeedback

The plugin includes ERP_DefaultInteractionFeedback, a ready-to-use Blueprint subclass of ERPBaseInteractionFeedbackComponent that works with the provided widget base classes. It handles outline toggling, widget creation, visibility, and descriptor forwarding out of the box.

You can use it directly on your interactable actors, or use it as a reference to build your own feedback component.

Creating Your Own Feedback Component

Create a Blueprint child of ERPBaseInteractionFeedbackComponent and override the two events. A typical implementation manages an outline and a widget:

Setup (BeginPlay):

Event BeginPlay
-> Find WidgetComponent on owner (GetComponentByClass)
-> Store reference for later

Focus handling:

Event: On Interaction Focused (PC, bFocused)
Branch: bFocused
True:
-> Enable your outline effect
-> Create widget if not already created (CreateWidget for PC)
-> Set widget on WidgetComponent
-> Set WidgetComponent visible
False:
-> Disable outline effect
-> Set WidgetComponent hidden

Descriptor handling — the bridge to the widget:

Event: On Descriptor Updated (PC, Descriptor)
-> Ensure widget exists (create if needed)
-> Widget -> UpdateFromDescriptor(Descriptor)

Add this component to your interactable actors. The C++ interaction system handles the rest — no event binding needed on the PlayerController for visual feedback.

Approach 2: Custom UI From Domain Events

Skip the feedback component entirely. Listen to ERPInteractionComponent events on the player side and drive your own UI:

Event: OnDescriptorChanged (Candidate, Descriptor, ChannelId)
-> Update your widget with Descriptor.DisplayName, PromptText, etc.

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

This gives you full control over presentation without depending on any plugin widget class. You can use any UMG widget, any third-party UI system, or even non-UI feedback (sound, camera, etc.).

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

Build your own eligibility logic, descriptor system, and presentation from scratch.


Blueprint Examples

Minimal Setup (Auto-Wired Feedback)

If your interactable actors have an ERPBaseInteractionFeedbackComponent, visual feedback is automatic — no PlayerController wiring needed for the visual side.

PlayerController Components:

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

Interactable Actor Components:

  • ERPBaseInteractionFeedbackComponent (optional, auto-notified)

PlayerController Event Graph (input only):

Input Action: Interact
-> InteractionComponent -> RequestInteraction()
// Networking handled automatically

Event: OnInteractionExecuted (InteractableActor)
-> Your gameplay logic

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

The ERPBaseInteractionFeedbackComponent on the interactable receives OnInteractionFocused and OnDescriptorUpdated automatically. Implement these events in Blueprint to update your widget, outline, or any visual.

Custom UI Setup (No Feedback Component)

If you don't use ERPBaseInteractionFeedbackComponent and want to drive your own UI from the PlayerController side:

PlayerController Event Graph:

// Handle descriptor changes (update UI)
Event: OnDescriptorChanged (Candidate, Descriptor, ChannelId)
-> Update your widget with Descriptor.DisplayName, PromptText, etc.
-> Show widget

// Handle candidate lost (hide UI)
Event: OnDomainCandidateChanged (Previous, New, ChannelId)
-> Branch: IsValid(New)
False: -> Hide widget

// Execute interaction (player input)
Input Action: Interact
-> InteractionComponent -> RequestInteraction()

Both approaches work. Use whichever fits your project.


Widget Integration

The plugin provides widget base classes for interaction UI. The descriptor fields (DisplayName, PromptText, InputAction, Icon, HoldDuration, RequiredPresses) drive the widget content.

Descriptor StateWidget TypeBase Class
HoldDuration == 0 and RequiredPresses == 0Simple promptERPInteractionWidgetBase
HoldDuration > 0Hold-to-interactERPHoldInteractionWidgetBase
RequiredPresses > 0Multi-pressERPHoldInteractionWidgetBase

Quick wiring (presenter on your PlayerController):

Event: OnDescriptorChanged (Candidate, Descriptor, ChannelId)
-> InteractionWidget -> UpdateFromDescriptor(Descriptor)
-> InteractionWidget -> SetFocused(true)

Event: OnDomainCandidateChanged (Previous, New, ChannelId)
-> Branch: Not IsValid(New)
True: -> InteractionWidget -> SetFocused(false)

For complete step-by-step walkthroughs including hold timer management, multi-press counting, layout design, and C++ presenter examples, 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);
}

void AYourPlayerController::HandleInteractInput()
{
// Single call handles standalone + networked execution
InteractionComponent->RequestInteraction();
}

Interactable Actor

#include "Domains/Interaction/ERPInteractable.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
{
OutDescriptor.DisplayName = FText::FromString(TEXT("Door"));
OutDescriptor.PromptText = bIsOpen
? FText::FromString(TEXT("Close"))
: FText::FromString(TEXT("Open"));
OutDescriptor.InputAction = InteractAction; // UInputAction* set in editor
OutDescriptor.Icon = DoorIcon; // TSoftObjectPtr<UTexture2D>
// OutDescriptor.HoldDuration = 0.0f; // Instant (default)
}

virtual bool ExecuteInteraction_Implementation(AActor* InstigatorActor) override
{
bIsOpen = !bIsOpen;
return true;
}

protected:
bool bIsOpen = false;
};

Advanced Patterns

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 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 on the interactable

Dynamic Descriptors

Descriptors are pulled from IERPInteractable::GetInteractionDescriptor when the candidate changes. However, if the same interactable actor changes its descriptor while remaining the active candidate (e.g., a door goes from "Open" to "Locked"), the system does not detect this automatically — perception only fires events when the best candidate changes, not when its data changes.

To handle dynamic descriptor changes on the same candidate, call ForceRefreshDescriptor() on the ERPInteractionComponent:

From the interactable (Blueprint):

// When your interaction state changes:
-> Find the ERPInteractionComponent on the player (or store a reference)
-> Call ForceRefreshDescriptor()

From C++:

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

This re-pulls the descriptor from the interactable, and if it changed:

  • Broadcasts OnDescriptorChanged (so the widget/UI updates)
  • Notifies the ERPBaseInteractionFeedbackComponent on the actor (if present)

Next Steps

Build your interaction UI with Widget Guide.

Set up targeting with Targeting Guide.

Create custom domains with Custom Domains.

Learn customization in Customization.

Check complete API in API Reference.