Skip to main content

Quick Reference

Quick reference and cheat sheet for ElysPerceptionPlugin.

Common Patterns

Basic Targeting Setup

1. Add ERPPerceptionComponent to PlayerController
2. Configure Target Pipeline:
- Channel Id: "Target"
- Sampler: ERPSphereOverlapSampler (range: 2000)
- Filter: ERPTargetableFilter
- Scorer: ERPDistanceScorer (normalize: true), Weight: 1.0
- Resolver: ERPResolverBase
3. Add ERPTargetingComponent (Channel: "Target")
4. Bind to OnDomainCandidateChanged / OnDescriptorChanged
5. Implement IERPTargetable on targets

Basic Interaction Setup

1. Configure Interaction Pipeline (range: 300)
- Channel Id: "Interact"
- Sampler, Filter (InteractableFilter), Scorer, Resolver
2. Add ERPInteractionComponent (Channel: "Interact")
3. Bind to OnDescriptorChanged, OnInteractionExecuted/Failed
4. Implement IERPInteractable on interactables
5. Call RequestInteraction() on input

Custom Domain Setup

1. Create Blueprint/C++ class extending ERPDomainComponent
2. Override IsCandidateEligible (validation)
3. Override HandleCandidateUpdated (reactions)
4. Configure a perception pipeline with matching ChannelId
5. Add your domain component to the same actor

Component Quick Reference

ERPPerceptionComponent

// Get candidate for channel
AActor* Target = Perception->GetChannelCandidate("Target");

// Check if has candidate
if (Perception->HasChannelCandidate("Interact")) { ... }

// Bind to generic events
Perception->OnChannelCandidateAcquired.AddDynamic(this, &AMyPC::OnAcquired);

// Validate actor for anti-cheat
bool bValid = Perception->ValidateActorForChannel(Actor, "Interact");

ERPDomainComponent (and subclasses)

// Get current candidate
AActor* Candidate = DomainComp->GetCurrentCandidate();

// Check if has candidate
if (DomainComp->HasCandidate()) { ... }

// Bind to domain events
DomainComp->OnDomainCandidateChanged.AddDynamic(this, &AMyPC::OnChanged);

ERPInteractionComponent

// Configure
InteractionComp->ChannelId = "Interact";

// Get descriptor
FERPInteractionDescriptor Desc;
InteractionComp->GetCurrentDescriptor(Desc);

// Execute interaction (handles networking)
InteractionComp->RequestInteraction();

// Bind to events
InteractionComp->OnDescriptorChanged.AddDynamic(this, &AMyPC::OnDescriptor);
InteractionComp->OnInteractionExecuted.AddDynamic(this, &AMyPC::OnExecuted);

ERPTargetingComponent

// Configure
TargetingComp->ChannelId = "Target";

// Get descriptor
const UERPTargetDescriptor* Desc = TargetingComp->GetCurrentDescriptor();

// Bind to events
TargetingComp->OnDomainCandidateChanged.AddDynamic(this, &AMyPC::OnTargetChanged);
TargetingComp->OnDescriptorChanged.AddDynamic(this, &AMyPC::OnDescriptor);

ERPChannelListenerComponent

// Lightweight event-only listener
Listener->ChannelId = "Target";
Listener->OnCandidateAcquired.AddDynamic(this, &AMyPC::OnAcquired);
Listener->OnCandidateLost.AddDynamic(this, &AMyPC::OnLost);
AActor* Current = Listener->GetCurrentCandidate();

Interface Implementation

IERPTargetable (C++)

class AMyEnemy : public ACharacter, public IERPTargetable
{
virtual bool CanBeTargetedBy_Implementation(AActor* TargetingActor) const override
{
return bIsAlive;
}
};

IERPInteractable (C++)

class AMyDoor : public AActor, public IERPInteractable
{
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 = FText::FromString(bIsOpen ? TEXT("Close") : TEXT("Open"));
}

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

Pipeline Configurations

Distance-Based Targeting

Sampler: ERPSphereOverlapSampler (range: 3000)
Filter: ERPTargetableFilter
Scorer: ERPDistanceScorer (weight: 1.0)
Resolver: ERPResolverBase

Camera-Aware Targeting

Sampler: ERPSphereOverlapSampler
Filter: ERPTargetableFilter
Scorers:
- ERPDistanceScorer (weight: 1.0)
- ERPConeScorer (cone: 45, weight: 1.5)
Resolver: ERPResolverBase

Screen-Space Targeting

Sampler: ERPSphereOverlapSampler
Filter: ERPTargetableFilter
Scorer: ERPViewportEllipseScorer (weight: 1.0)
-> Enable bProvideViewportInfoWhenAvailable
Resolver: ERPResolverBase

Debugging

Enable Debug Logging

Set bDebugPerception = true on ERPPerceptionComponent.

Console Variables

elys.perception.SamplerCacheDebug 1  // Log sampler cache hits/misses

Performance

stat GAME  // Show component tick times

Performance Tips

Tick Interval

// On ERPPerceptionComponent
PerceptionTickInterval = 0.1f; // 10 Hz instead of every frame

Reduce Range

DefaultSamplingRange: 1500 (instead of 3000)

Scorer Weights

Use weights to express importance:
Distance (weight: 2.0) > Angle (weight: 1.0)
Instead of normalizing scores differently

Score Convention

Lower score = better (by convention)

ScorerBest (0.0)Worst (1.0)
DistanceVery closeAt max range
ConeCenter of viewEdge of cone
Viewport EllipseScreen centerEllipse boundary

All scores are multiplied by their weight before aggregation.

Default aggregator: sum of weighted scores. Default resolver: lowest total score wins.


Class Hierarchy

UActorComponent
+-- UERPPerceptionComponent (perception engine)
+-- UERPChannelListenerComponent (lightweight listener)
+-- UERPDomainComponent (abstract base)
| +-- UERPInteractionComponent (interaction domain)
| +-- UERPTargetingComponent (targeting domain)
| +-- YourCustomDomain (extend for custom gameplay)
+-- UERPBaseInteractionFeedbackComponent (visual feedback)

UUserWidget
+-- UERPInteractionWidgetBase (simple prompt)
| +-- UERPHoldInteractionWidgetBase (hold-to-interact with progress)
+-- UERPTargetingWidgetBase (reticle / target info)

Widget Quick Reference

// Simple interaction prompt
InteractionWidget->UpdateFromDescriptor(Descriptor);
InteractionWidget->SetFocused(true);
FText Key = InteractionWidget->GetCurrentInputDisplayKey();

// Hold-to-interact
HoldWidget->UpdateFromDescriptor(Descriptor);
HoldWidget->SetHoldProgress(AccumulatedTime / Descriptor.HoldDuration);
HoldWidget->ResetHold(); // On release

// Targeting
TargetWidget->UpdateFromDescriptor(TargetDescriptor);
TargetWidget->SetTargetActive(true);
TargetWidget->SetTargetActor(TargetActor);

Next Steps

Full guides: