Skip to main content

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): UERPInteractionComponent extends UERPDomainComponent, validates eligibility using IERPInteractable interface 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 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 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:

  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. 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 OnInteractionFocused in 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 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<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:

  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 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.