Skip to main content

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 UERPPerceptionComponent on the owner at BeginPlay
  • Binds to OnChannelCandidateAcquired / OnChannelCandidateLost
  • Filters by ChannelId
  • Tracks CurrentCandidate with change detection
  • Broadcasts OnDomainCandidateChanged(Previous, New, ChannelId)

Your subclass only needs to override two virtual hooks:

  1. IsCandidateEligible(AActor*) - return false to reject candidates
  2. HandleCandidateUpdated(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

  1. Content Browser -> New Blueprint Class
  2. Parent Class: ERPDomainComponent
  3. Name: BP_LootDomainComponent

Step 2: Configure Defaults

In the Class Defaults:

  • Channel Id: Loot

Step 3: Override IsCandidateEligible

  1. Override IsCandidateEligible
  2. 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

  1. Override HandleCandidateUpdated
  2. 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:

  1. Add a channel pipeline to ERPPerceptionComponent:

    • Channel Id: Loot
    • Sampler: ERPSphereOverlapSampler (range: 500)
    • Filter: Your custom loot filter (or none)
    • Scorer: ERPDistanceScorer
    • Resolver: ERPResolverBase
  2. Add BP_LootDomainComponent to the same actor

    • Channel Id: Loot

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

PropertyTypeDescription
PerceptionComponentUERPPerceptionComponent*Auto-found on owner at BeginPlay
ChannelIdFNamePerception channel to listen to

Events

EventSignatureDescription
OnDomainCandidateChanged(Previous, New, ChannelId)Candidate changed

Methods

MethodDescription
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

HookDefaultDescription
IsCandidateEligible(AActor*)Returns trueOverride to add validation
HandleCandidateUpdated(Previous, Current)No-opOverride to react to changes

Tips

  • Keep IsCandidateEligible lightweight - it runs on every perception event
  • Use HandleCandidateUpdated for 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.