LCM Nav3D Free demo Get it on Fab

BT PerceptionRelay - bind OnTargetPerceptionUpdated directly

TL;DR: Unreal Engine's UAIPerceptionComponent already emits perception updates as a delegate. You don't need a custom BT service - just bind the delegate in your AI controller and write to a blackboard key.

Pattern (in your AIController subclass)

// In your AIController.h
UPROPERTY()
TObjectPtr<UAIPerceptionComponent> PerceptionComp;

UFUNCTION()
void OnPerceptionUpdate(AActor* Actor, FAIStimulus Stimulus);

// In your AIController.cpp BeginPlay
PerceptionComp = FindComponentByClass<UAIPerceptionComponent>();
if (PerceptionComp)
{
    PerceptionComp->OnTargetPerceptionUpdated.AddDynamic(
        this, &AYourAIController::OnPerceptionUpdate);
}

void AYourAIController::OnPerceptionUpdate(AActor* Actor, FAIStimulus Stimulus)
{
    if (UBlackboardComponent* BB = GetBlackboardComponent())
    {
        if (Stimulus.WasSuccessfullySensed())
        {
            BB->SetValueAsObject("ClosestThreat", Actor);
            BB->SetValueAsBool  ("bHasAnyTarget", true);
        }
        else
        {
            // Lost sight - leave ClosestThreat for memory pattern (see below)
            BB->SetValueAsBool("bHasAnyTarget", false);
        }
    }
}

That's the canonical UE5 way. Three lines in your AIController + a Blackboard decorator in the BT.

Why we didn't ship LCM_BTService_PerceptionRelay

A custom service that ticks each frame and re-queries GetCurrentlyPerceivedActors is less efficient than binding the event-driven OnTargetPerceptionUpdated delegate. The stock pattern is also more idiomatic - every UE5 AI tutorial uses this pattern.

The LCM Nav3D StateTree variant FLcmSTEval_PerceptionRelay is kept because StateTree has no per-state perception delegate (its evaluators are tick-based by design). On the BT side, delegate binding is the standard.

Memory pattern (last-known-location with age)

UE5's perception component already tracks last-known location + age per perceived actor via FActorPerceptionInfo::GetLastStimulusLocation(float* OutAge). To use it:

if (const FActorPerceptionInfo* Info = PerceptionComp->GetActorInfo(*Actor))
{
    float Age = 0.f;
    FVector LastKnown = Info->GetLastStimulusLocation(&Age);
    if (Age < 8.f) // 8 sec memory window
    {
        BB->SetValueAsVector("LastKnownThreatLoc", LastKnown);
    }
}

See BT_MemoryDecay_StockPattern.md for the full pattern. Don't roll your own decay timer - perception's age tracking already does it for you.

For perception-aware predicates that need LCM Nav3D LOS (volumetric, not raycast):