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.
Related LCM Nav3D-unique services
For perception-aware predicates that need LCM Nav3D LOS (volumetric, not raycast):
LCM_BTDecorator_ThreatVisibleViaPerception- composite: perception sees + LCM Nav3D Lazy Theta* LOS is clearLCM_BTService_SquadSnapshot- aggregates perceived allies into squad centroid / cohesion radius