Custom Domains
Create your own perception domains by extending UERPDomainComponent.
Overview
A "domain" is a gameplay interpretation of perception data. The plugin ships with two built-in domains:
- Interaction (
UERPInteractionComponent) - proximity interactions with RPC support - Targeting (
UERPTargetingComponent) - combat targeting with descriptor tracking
You can create additional domains for any gameplay system that needs to react to perception candidates: stealth detection, loot highlighting, dialogue triggers, quest markers, etc.
UERPDomainComponent Base Class
UERPDomainComponent handles all the shared boilerplate:
- Auto-finds
UERPPerceptionComponenton the owner at BeginPlay - Binds to
OnChannelCandidateAcquired/OnChannelCandidateLost - Filters by
ChannelId - Tracks
CurrentCandidatewith change detection - Broadcasts
OnDomainCandidateChanged(Previous, New, ChannelId)
Your subclass only needs to override two virtual hooks:
IsCandidateEligible(AActor*)- return false to reject candidatesHandleCandidateUpdated(Previous, Current)- react to candidate changes
Both are BlueprintNativeEvent, so you can override in C++ or Blueprint.
Blueprint Tutorial: Loot Highlighting Domain
Step 1: Create Blueprint Class
- Content Browser -> New Blueprint Class
- Parent Class:
ERPDomainComponent - Name:
BP_LootDomainComponent
Step 2: Configure Defaults
In the Class Defaults:
- Channel Id:
Loot
Step 3: Override IsCandidateEligible
- Override
IsCandidateEligible - Logic:
Function: Is Candidate Eligible (Candidate)
-> Does Implement Interface: ILootable (your interface)
-> Branch:
True: -> Cast to ILootable -> Can Be Looted?
-> Return result
False: -> Return false
Step 4: Override HandleCandidateUpdated
- Override
HandleCandidateUpdated - Logic:
Function: Handle Candidate Updated (Previous, Current)
// Unhighlight previous
-> Branch: IsValid(Previous)
True: -> Previous -> SetRenderCustomDepth(false)
// Highlight current
-> Branch: IsValid(Current)
True: -> Current -> SetRenderCustomDepth(true)
Step 5: Configure Perception Pipeline
On your PlayerController or Pawn:
-
Add a channel pipeline to
ERPPerceptionComponent:- Channel Id:
Loot - Sampler:
ERPSphereOverlapSampler(range: 500) - Filter: Your custom loot filter (or none)
- Scorer:
ERPDistanceScorer - Resolver:
ERPResolverBase
- Channel Id:
-
Add
BP_LootDomainComponentto the same actor- Channel Id:
Loot
- Channel Id:
C++ Tutorial: Stealth Detection Domain
Step 1: Create Header
#pragma once
#include "CoreMinimal.h"
#include "Core/ERPDomainComponent.h"
#include "ERPStealthDomainComponent.generated.h"
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(
FOnStealthTargetChanged,
AActor*, PreviousTarget,
AActor*, NewTarget);
UCLASS(ClassGroup=(Custom), Blueprintable, meta=(BlueprintSpawnableComponent))
class YOURGAME_API UERPStealthDomainComponent : public UERPDomainComponent
{
GENERATED_BODY()
public:
UERPStealthDomainComponent();
UPROPERTY(BlueprintAssignable, Category = "Stealth")
FOnStealthTargetChanged OnStealthTargetChanged;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Stealth")
float MinAwarenessToDetect = 0.5f;
protected:
virtual bool IsCandidateEligible_Implementation(AActor* Candidate) const override;
virtual void HandleCandidateUpdated_Implementation(AActor* Previous, AActor* Current) override;
};
Step 2: Create Implementation
#include "ERPStealthDomainComponent.h"
#include "YourStealthInterface.h"
UERPStealthDomainComponent::UERPStealthDomainComponent()
{
ChannelId = FName("Stealth");
}
bool UERPStealthDomainComponent::IsCandidateEligible_Implementation(AActor* Candidate) const
{
if (!Candidate || !GetOwner())
{
return false;
}
// Check if candidate implements your stealth interface
if (!Candidate->GetClass()->ImplementsInterface(UYourStealthInterface::StaticClass()))
{
return false;
}
// Check awareness level
float Awareness = IYourStealthInterface::Execute_GetAwarenessLevel(Candidate, GetOwner());
return Awareness >= MinAwarenessToDetect;
}
void UERPStealthDomainComponent::HandleCandidateUpdated_Implementation(AActor* Previous, AActor* Current)
{
OnStealthTargetChanged.Broadcast(Previous, Current);
}
Step 3: Use It
// In your PlayerController
StealthDomain = CreateDefaultSubobject<UERPStealthDomainComponent>(TEXT("StealthDomain"));
// ChannelId defaults to "Stealth"
// Bind events
StealthDomain->OnStealthTargetChanged.AddDynamic(this, &AMyPC::OnStealthDetection);
Base Class API
Properties
| Property | Type | Description |
|---|---|---|
PerceptionComponent | UERPPerceptionComponent* | Auto-found on owner at BeginPlay |
ChannelId | FName | Perception channel to listen to |
Events
| Event | Signature | Description |
|---|---|---|
OnDomainCandidateChanged | (Previous, New, ChannelId) | Candidate changed |
Methods
| Method | Description |
|---|---|
GetCurrentCandidate() | Returns current candidate (or nullptr) |
HasCandidate() | True if a candidate is active |
SetCandidate(AActor*) | Manually set candidate (validates eligibility) |
ClearCandidate() | Clear current candidate |
Virtual Hooks
| Hook | Default | Description |
|---|---|---|
IsCandidateEligible(AActor*) | Returns true | Override to add validation |
HandleCandidateUpdated(Previous, Current) | No-op | Override to react to changes |
Tips
- Keep
IsCandidateEligiblelightweight - it runs on every perception event - Use
HandleCandidateUpdatedfor heavier operations (resolve descriptors, update UI) - Domain components don't tick - they react to perception events only
- You can have multiple domain components listening to different channels on the same actor
- The base class handles null checks and same-candidate detection for you
Next Steps
Learn about pipeline customization in Customization.
Check complete API in API Reference.