Customization Guide
Create custom pipeline components and extend ElysAwareness for advanced use cases.
When to Customize
Pipeline components (samplers, filters, scorers): when default components don't fit your needs.
Domain components: when you need a new gameplay system that reacts to perception. See Custom Domains for a dedicated guide.
Extension Points
The three configurable pipeline stages each have an abstract base class you can extend in Blueprint or C++:
UERPSamplerBase- Custom candidate gatheringUERPFilterBase- Custom filtering logicUERPScorerBase- Custom scoring logic
Aggregation (weighted average) and resolution (lowest/highest score + sticky bias) are built into the pipeline struct and configured via properties -- no custom classes needed.
Custom Samplers
Purpose
Samplers gather potential candidates. The plugin ships with spatial samplers (sphere/box overlap) and a registry sampler (declarative). Customize when you need:
- Different spatial queries (raycast, cone sweep)
- Specialized non-spatial queries beyond the built-in registry
- Cached/optimized candidate lists
Interface
UFUNCTION(BlueprintNativeEvent, Category = "Pipeline")
void Sample(const FERPAwarenessContext& Context, float Range, TArray<AActor*>& OutCandidates) const;
C++ Example: Line Trace Sampler
UCLASS(Blueprintable, EditInlineNew, DefaultToInstanced)
class YOURGAME_API UERPLineTraceSampler : public UERPSamplerBase
{
GENERATED_BODY()
public:
UPROPERTY(EditAnywhere) TEnumAsByte<ECollisionChannel> TraceChannel = ECC_Visibility;
virtual void Sample_Implementation(const FERPAwarenessContext& Context, float Range,
TArray<AActor*>& OutCandidates) const override
{
if (!Context.ContextActor) return;
UWorld* World = Context.ContextActor->GetWorld();
if (!World) return;
FCollisionQueryParams Params;
Params.AddIgnoredActor(Context.ContextActor);
FHitResult Hit;
if (World->LineTraceSingleByChannel(Hit, Context.Origin,
Context.Origin + Context.Forward * Range, TraceChannel, Params))
{
if (AActor* A = Hit.GetActor()) OutCandidates.Add(A);
}
}
};
Custom Filters
Purpose
Filters reject invalid candidates. Customize for:
- Line of sight checks
- Team/faction filtering
- State validation (alive, active)
Interface
UFUNCTION(BlueprintNativeEvent, Category = "Pipeline")
bool Passes(const FERPAwarenessContext& Context, AActor* Candidate) const;
C++ Example: Line of Sight Filter
UCLASS(Blueprintable, EditInlineNew, DefaultToInstanced)
class YOURGAME_API UERPLineOfSightFilter : public UERPFilterBase
{
GENERATED_BODY()
public:
UPROPERTY(EditAnywhere) TEnumAsByte<ECollisionChannel> TraceChannel = ECC_Visibility;
virtual bool Passes_Implementation(const FERPAwarenessContext& Context,
AActor* Candidate) const override
{
if (!Context.ContextActor || !Candidate) return false;
UWorld* World = Context.ContextActor->GetWorld();
if (!World) return false;
FCollisionQueryParams Params;
Params.AddIgnoredActor(Context.ContextActor);
Params.AddIgnoredActor(Candidate);
FHitResult Hit;
bool bHit = World->LineTraceSingleByChannel(Hit, Context.Origin,
Candidate->GetActorLocation(), TraceChannel, Params);
return !bHit;
}
};
Custom Scorers
Purpose
Scorers assign numerical values. Each scorer is paired with a weight in the pipeline configuration (FERPScorerEntry).
Interface
UFUNCTION(BlueprintNativeEvent, Category = "Pipeline")
float Score(const FERPAwarenessContext& Context, AActor* Candidate) const;
Convention: Lower score = better. Return ERPPipelineScore::RejectScore to reject.
C++ Example: Health Priority Scorer
UCLASS(Blueprintable, EditInlineNew, DefaultToInstanced)
class YOURGAME_API UERPHealthScorer : public UERPScorerBase
{
GENERATED_BODY()
public:
UPROPERTY(EditAnywhere) bool bPrioritizeLowHealth = true;
virtual float Score_Implementation(const FERPAwarenessContext& Context,
AActor* Candidate) const override
{
UHealthComponent* HC = Candidate->FindComponentByClass<UHealthComponent>();
if (!HC) return 1.0f;
float HP = HC->GetHealthPercent();
return bPrioritizeLowHealth ? HP : (1.0f - HP);
}
};
Using Weights
Configure in the pipeline:
Scorers:
- ERPDistanceScorer, Weight: 2.0 (distance matters more)
- ERPHealthScorer, Weight: 1.0 (health is secondary)
- ERPConeScorer, Weight: 0.5 (angle is tertiary)
Each raw score is multiplied by its weight before aggregation.
Aggregation and Resolution
Aggregation and resolution are built into the pipeline and configured via properties on FERPChannelPipeline:
| Property | Type | Default | Description |
|---|---|---|---|
ResolverPolicy | EERPResolverPolicy | LowestScore | Whether the lowest or highest aggregated score wins |
StickyBias | float | 0.1 | Hysteresis bias to prevent flickering between similar candidates |
Aggregation is always weighted average (sum of weighted scores / number of scorers). There is no custom aggregator class.
Resolution selects the winner based on ResolverPolicy:
LowestScore-- candidate with the lowest aggregated score wins (good for distance-based scoring)HighestScore-- candidate with the highest aggregated score wins (good for priority-based scoring)
StickyBias adds hysteresis: the current best candidate gets a score bonus, so a new candidate must beat it by at least StickyBias to take over. Set to 0.0 to disable stickiness.
Best Practices
- Keep Stages Pure: Don't modify candidates or context
- Normalize Scores: Use 0.0-1.0 range when possible
- Handle Edge Cases: Null checks, empty arrays, invalid context
- Optimize: Filters run per-candidate, keep fast. Cache expensive calculations.
- Document Parameters: Use UPROPERTY meta tags and tooltips
Next Steps
Create custom domains with Custom Domains.
Check complete API in API Reference.
See Quick Reference for cheat sheet.