Interaction Guide
Complete guide to implementing proximity-based interactions using ElysPerceptionPlugin.
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 visual feedback (custom depth outlines by default)
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 ERPPerceptionComponent:
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. Bind to Events and Handle Input
Event BeginPlay
-> Get Component (InteractionComponent)
-> Bind Event to OnDescriptorChanged
Event: OnDescriptorChanged (Candidate, Descriptor, ChannelId)
-> Update UI with Descriptor.PromptText
Input Action: Interact
-> InteractionComponent -> RequestInteraction()
// Handles networking automatically
Visual Feedback
ERPBaseInteractionFeedbackComponent
Add this component to interactable actors to get built-in visual feedback.
Default behavior: Toggles RenderCustomDepth on the owner's mesh components when focused. This enables UE's standard custom depth outline approach (requires a post-process material reading custom depth).
Configuration:
CustomDepthStencilValue: Value set when focused (default: 1)
Integration:
- Call
SetInteractionFocused(PC, true/false)when the interaction candidate changes - Call
UpdateInteractionDescriptor(PC, Descriptor)when the descriptor changes - Override
OnInteractionFocusedin Blueprint to replace default custom depth behavior with your own
Example Blueprint integration on PlayerController:
Event: OnDomainCandidateChanged (Previous, New, ChannelId)
// Unfocus previous
-> Branch: IsValid(Previous)
True: -> Previous -> GetComponent(ERPBaseInteractionFeedbackComponent)
-> SetInteractionFocused(Self, false)
// Focus new
-> Branch: IsValid(New)
True: -> New -> GetComponent(ERPBaseInteractionFeedbackComponent)
-> SetInteractionFocused(Self, true)
Blueprint Example
Complete Interaction System
PlayerController Components:
ERPPerceptionComponent(interaction pipeline configured)ERPInteractionComponent(listening to "Interact" channel)
Event Graph:
// Handle descriptor changes (update UI)
Event: OnDescriptorChanged (Candidate, Descriptor, ChannelId)
-> Update Interaction Widget
- Set Prompt Text: Descriptor.PromptText
- Set Display Name: Descriptor.DisplayName
-> Show Widget
// Handle candidate lost (hide UI)
Event: OnDomainCandidateChanged (Previous, New, ChannelId)
-> Branch: IsValid(New)
False: -> Hide Interaction Widget
// Execute interaction (player input)
Input Action: Interact
-> InteractionComponent -> RequestInteraction()
// Networking handled automatically
// Handle execution result
Event: OnInteractionExecuted (InteractableActor)
-> Print: "Interaction successful"
Event: OnInteractionFailed (InteractableActor, Reason)
-> Print: "Interaction failed: {Reason}"
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<UERPPerceptionComponent>(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 on demand from IERPInteractable::GetInteractionDescriptor. State changes are automatically reflected when the candidate is re-evaluated.
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.