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):
UERPInteractionComponentextendsUERPDomainComponent, validates eligibility usingIERPInteractableinterface and rules - Presentation: Optional. If an
ERPBaseInteractionFeedbackComponentis 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
IsCandidateEligibleto checkIERPInteractableinterface +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_RequestInteractionRPC - 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:
- Class Settings -> Interfaces -> Add
ERPInteractable - Implement
Can Be Interacted With-> returntrue - 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
- 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:
| Approach | When to use |
|---|---|
| Auto-wired feedback | You want out-of-the-box feedback on interactable actors |
| Custom UI from events | You want full control over your UI, driven by domain events |
| Fully custom | You 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):
| Event | When it fires | Typical use |
|---|---|---|
OnInteractionFocused(PC, bFocused) | Actor gains/loses interaction focus | Show/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 State | Widget Type | Base Class |
|---|---|---|
HoldDuration == 0 and RequiredPresses == 0 | Simple prompt | ERPInteractionWidgetBase |
HoldDuration > 0 | Hold-to-interact | ERPHoldInteractionWidgetBase |
RequiredPresses > 0 | Multi-press | ERPHoldInteractionWidgetBase |
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:
- Distance:
MaxInteractionDistanceproperty on the component - Perception Filters: Re-runs channel filters server-side via
ValidateActorForChannel - Eligibility: Re-checks
CanBeInteractedWithand all rules - Execution: Calls
ExecuteInteractionon 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
ERPBaseInteractionFeedbackComponenton 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.