Skip to main content

Quick Reference

Quick reference and cheat sheet for ElysAwareness.

Common Patterns

Basic Targeting Setup

1. Add ERPAwarenessComponent to PlayerController
2. Configure Target Pipeline:
- Channel Id: "Target"
- Sampler: ERPSphereOverlapSampler (range: 2000)
- Filter: ERPTargetableFilter
- Scorer: ERPDistanceScorer (normalize: true), Weight: 1.0
- ResolverPolicy: LowestScore (default)
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
2. Add ERPInteractionComponent (Channel: "Interact")
3. Implement IERPInteractable on interactables
4. Call StartInteractionAttempt() on input
5. (Optional) Add ERPBaseInteractionFeedbackComponent on interactables
→ auto-notified, implement OnInteractionFocused / OnDescriptorUpdated
6. OR: Bind to OnDescriptorChanged / OnDomainCandidateChanged for custom UI

Basic Instanced Interaction Setup

1. Create DataTable (row type: FERPInstancedMeshConfig)
2. Add rows: Mesh → ProxyClass → StateSteps
- Mesh: the static mesh used in ISMCs (e.g., SM_Pine_1)
- ProxyClass: Blueprint child of ERPInstanceProxy
- StateSteps: ordered lifecycle (Normal → Harvested → Regrowing)
3. Add ERPInstancedInteractionComponent to PlayerController/Pawn
- Set MeshConfigTable to your DataTable
- SwapRadius: 600 (must be > awareness SamplingRange)
- DespawnMargin: 200 (hysteresis to prevent flickering)
- ScanInterval: 0.2
4. Create proxy Blueprint (parent: ERPInstanceProxy)
- Override OnInstanceInteracted for loot/VFX
- Override OnStateChanged for transition effects (call Parent)
5. Configure StateSteps per row:
- State: GameplayTag (ERP.Instance.Normal, .Harvested, etc.)
- Mesh: what to show on the proxy actor
- DistantMesh: what to show in ISMC when player leaves
- Descriptor: interaction actions (empty = non-interactable)
- Duration: 0 = manual, >0 = auto-transition (seconds)
- bLoopToFirst: true on last step to cycle back to step 0

Registry-Based Perception (Map Markers, POIs)

1. Configure Pipeline:
- Channel Id: "MapMarkers"
- Sampler: ERPRegistrySampler
- Scorer: ERPDistanceScorer (weight: 1.0)
- ResolverPolicy: LowestScore (default)
2. On target actors: Add ERPRegistrationComponent
- Set ChannelIds: ["MapMarkers"]
3. Bind to OnChannelEvaluated for ALL candidates (minimap)
or OnChannelCandidateAcquired for best only

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

ERPAwarenessComponent

// 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);

// Bind to pipeline evaluation (ALL scored candidates — for minimap/radar)
Perception->OnChannelEvaluated.AddDynamic(this, &AMyPC::OnEvaluated);
// void OnEvaluated(FName ChannelId, const TArray<FERPPipelineScoredCandidate>& Candidates)

// 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->StartInteractionAttempt();

// Force descriptor refresh (same candidate changed its data)
InteractionComp->ForceRefreshDescriptor();

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

ERPTargetingComponent

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

// Get descriptor
const FERPTargetDescriptor& 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.ObjectName = FText::FromString(TEXT("Door"));
FERPInteractionAction Action;
Action.ActionName = FText::FromString(bIsOpen ? TEXT("Close") : TEXT("Open"));
OutDescriptor.Actions.Add(Action);
}

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

Pipeline Configurations

Distance-Based Targeting

Sampler: ERPSphereOverlapSampler (range: 3000)
Filter: ERPTargetableFilter
Scorer: ERPDistanceScorer (weight: 1.0)
ResolverPolicy: LowestScore
StickyBias: 0.1

Camera-Aware Targeting

Sampler: ERPSphereOverlapSampler
Filter: ERPTargetableFilter
Scorers:
- ERPDistanceScorer (weight: 1.0)
- ERPConeScorer (cone: 45, weight: 1.5)
ResolverPolicy: LowestScore
StickyBias: 0.1

Screen-Space Targeting

Sampler: ERPSphereOverlapSampler
Filter: ERPTargetableFilter
Scorer: ERPViewportEllipseScorer (weight: 1.0)
-> Enable bProvideViewportInfoWhenAvailable
ResolverPolicy: LowestScore
StickyBias: 0.1

Debugging

Enable Debug Logging

Set bDebugPerception = true on ERPAwarenessComponent.

Console Variables

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

Performance

stat GAME  // Show component tick times

Performance Tips

Tick Interval

// On ERPAwarenessComponent
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.

Aggregation is always weighted average (sum / count) -- built-in, no configuration needed. Resolution uses ResolverPolicy (default: LowestScore) + StickyBias (default: 0.1) on the pipeline struct.


Class Hierarchy

UActorComponent
+-- UERPAwarenessComponent (perception engine)
+-- UERPChannelListenerComponent (lightweight listener)
+-- UERPDomainComponent (abstract base)
| +-- UERPInteractionComponent (interaction domain)
| +-- UERPTargetingComponent (targeting domain)
| +-- YourCustomDomain (extend for custom gameplay)
+-- UERPInstancedInteractionComponent (ISMC instance swapping)
+-- UERPRegistrationComponent (registry auto-registration)
+-- UERPBaseInteractionFeedbackComponent (visual feedback, optional, auto-wired)

AActor
+-- AERPInstanceProxy (ISMC instance proxy with state machine + interaction)

UWorldSubsystem
+-- UERPInstanceSwapSubsystem (instance state persistence)

UUserWidget
+-- UERPInteractionWidgetBase (interaction prompt)
| +-- UERPBaseWorldInteractionWidget (world-space prompt)
+-- UERPChallengeWidgetBase (challenge progress)
| +-- UERPBaseMultiPressChallengeWidget (press counter)
| +-- UERPBaseRadialHoldWidget (radial fill)
+-- UERPTargetingWidgetBase (reticle / target info)
+-- UERPBaseTargetFrameWidget (nameplate)
| +-- UERPBaseTargetHealthBarWidget (nameplate + health)
+-- UERPBaseTargetReticleWidget (lock-on reticle)

Widget Quick Reference

// Interaction prompt
InteractionWidget->UpdateFromDescriptor(Descriptor);
InteractionWidget->SetFocused(true);
FText Key = InteractionWidget->GetPrimaryActionDisplayKey();

// Challenge progress
ChallengeWidget->SetChallengeProgress(0.75f);
ChallengeWidget->NotifyChallengeComplete(true);
ChallengeWidget->ResetChallenge();

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

// Health bar (extends target frame)
HealthBarWidget->SetHealthPercent(0.75f);

Next Steps

Full guides: