APSDocs
Guide

Custom senses#

Anything you can express as how sure am I that this target is there? can be a sense: thermal, radar, magnetism, a motion sensor, psychic awareness. A custom sense joins the same pipeline as the built-in ones: it combines with them, drives the seven states, feeds threat, and shows in the Visualizer.

The 30-second version
  • Blueprint Class › All Classes › Sense Unit as the parent.
  • Override Get Sense ID (a name) and Evaluate (fill in Out Result for one target).
  • Add the class to the profile's Sense Classes. Done.

In Blueprint#

1. Make the class. Content Browser › right-click › Blueprint Class › All Classes › search Sense Unit › pick it as the parent. Name it BP_Sense_Thermal.

You can also parent to a built-in sense (Smell Sense, Vision Sense...) to change its own settings in Class Defaults: that is how the Senses page's bloodhound gets a stronger nose (Smell).

2. Get Sense ID. Override it and return a unique name, Thermal. It's what Get Sense Contributions and the Visualizer show.

3. Evaluate. Override it: it is called for one target at a time, each time the sense updates.

InputWhat it is
ContextThe Perception Context: the AI's location, rotation, eye location and eye forward, the delta time, light and weather where it stands, the target's location and velocity, and whether the AI is off-screen
ProfileThe AI's profile: read any setting
TargetThe actor being evaluated

Fill in Out Result (a Perception Sense Result): drag from the Return Node's Out Result pin and pick Make Perception Sense Result.

FieldMeaning
Is ActiveRequired. True when the sense detects the target now; false and the rest is ignored
Confidence0 to 1: how sure this sense is
Estimated LocationWhere it thinks the target is: a vague sense should be off by a bit
Location Accuracy0 to 1: how precise that is (the position of a belief comes from the most precise active sense)
Raw Signal StrengthFor your own debugging
Sense IDYour sense's name
Evaluate (Context, Profile, Target)
  Distance   = Vector Distance (Context › Owner Location, Context › Target Location)
  Branch: Distance > 3000 → return (Out Result left empty)
  Heat       = the target's heat (a tag, a component, a variable: your game)
  Confidence = (1 − Distance ÷ 3000) × Heat
  Out Result: Is Active = Confidence > 0.1, Confidence, Estimated Location = Target Location,
              Location Accuracy = 0.8, Sense ID = Thermal

4. Add it to the profile's Sense Classes (up to 8 senses). Optionally weight it in Sense Weights and set how often it runs in Sense Intervals.

Its class defaults: the Sense Contract#

A sense made directly from Sense Unit declares how it behaves in its Class Defaults, Sense Contract:

SettingDefaultWhat it does
Default Interval0.1Seconds between updates, unless the profile's Sense Intervals says otherwise
Max Sensing Range0How far it reaches, in cm. Set it if it reaches further than vision and hearing: only targets inside the largest range of the AI's senses are evaluated at all
Loss Grace Time0.3Seconds of silence before the target can be lost
Allows Direct Lost CutOffOn: straight to Lost when the grace ends, like vision. Off: steps down through the states
Sense Loss ReasonSensorDropoutThe loss reason written when this sense is the one that lost the target
Subscribed Stimulus Tags(none)Stimulus bus prefixes it listens to (receiving them needs C++: below)

A custom sense isn't in the profile's Loss settings, so these are its loss rules; its belief fades at the normal rate.

Vision, Hearing, Smell, Touch, Vibration, Damage, Pain and Echolocation answer these in C++ and ignore the Sense Contract. On a Blueprint child of Vision Sense, setting them changes nothing; use the profile's settings instead.

What a Blueprint sense can't do#

BlueprintC++
Detection (Evaluate), Get Sense ID, your own settingsYesYes
Update rate, weight, range, loss rules, stimulus tagsYes (Sense Contract, profile)Yes
React when a stimulus arrivesNoOnStimulusReceived
Event-driven ("Report X" style) sensesNoYes
Senses about the AI itself (no target loop), like PainNoYes
Fire a sense event (On AI See, ...)No: the state events (On AI Suspect and the rest) still fireGetSenseDelegatePayload
Beliefs about placesNoCollectLocationObservations

A custom sense's Stimulus on On AI Suspect and On AI Detect reads Unknown: tell it apart with Get Sense Contributions.

In C++#

Make a child of USenseUnit and override what you need:

UCLASS(BlueprintType, Blueprintable, DisplayName = "Radar Sense")
class MYGAME_API USenseUnit_Radar : public USenseUnit
{
    GENERATED_BODY()
public:
    virtual FName GetSenseID_Implementation() const override { return FName("Radar"); }
    virtual float GetDefaultInterval()        const override { return 0.5f; }
    virtual float GetMaxSensingRange()        const override { return 8000.f; }
    virtual float GetLossGraceTime()          const override { return 1.0f; }
    virtual bool  AllowsDirectLostCut()       const override { return true; }

    virtual void Evaluate_Implementation(const FPerceptionContext& Context,
        const UPerceptionProfile* Profile, const AActor* Target,
        FPerceptionSenseResult& OutResult) override;
};

Each update, in this order:

FunctionWhenFor
PreTick (DeltaTime, Profile)Once per AI, before the targetsBuild-up, held state, reading the AI itself
CollectLocationObservationsOnce per AIThings perceived with nobody behind them: they become beliefs about places
EvaluateOnce per targetThe detection
GetSenseDelegatePayloadWhen it's active for a targetWhich sense event to fire (a payload with no type fires none)
PostTickFlushAfter all targetsClear per-update caches

The APS Core never casts to a particular sense: everything sense-specific lives in the sense class, so adding one changes nothing else. Other overrides:

OverrideFor
IsEventDriven, HasPendingEvent, ConsumePendingEventSenses fed by reports instead of polling
ConsumePendingEventFor (Target, OutResult)Hand a pending event only to the target that caused it (Damage and Touch do)
HasPendingStimulusFrom (Actor)Give that actor a target slot ahead of a crowd, so its stimulus isn't lost
GetLossReasonFor (LastResult)A loss reason per target, from the sense's last answer (vision uses what blocked it)
IsOwnerInternal, ConsumePendingOwnerPayloadA sense about the AI itself, like Pain
GetStimulusTags, OnStimulusReceivedThe stimulus bus
TickFatigue, GetFatigueMultiplierA sense that tires with use
CountWallsBetweenA helper: solid things between two points, each counted once

The stimulus bus#

A broadcast channel for your own signals: Emit Stimulus (callable from any Blueprint, no target needed) with an APSStimulus Event, and every sense that listens for a matching tag prefix receives it.

FieldDefaultMeaning
Stimulus TagA dotted name; a sense listening for Stimulus.Custom gets Stimulus.Custom.Psychic
LocationWhere it happened
SourceWho caused it (may be none)
Strength10 to 1
Radius1500AI further than this are skipped
Max LODTier2AI at a higher (cheaper) detail tier are skipped
Requires Clear PathOffTrace from it to each AI and skip blocked ones: costly, for things walls should stop
Team FilterOffSkip AI on the source's team

Delivery runs cheapest first: radius, detail tier, team, then the wall trace.

Who listens: only Vibration Sense among the built-in senses (Stimulus.Vibration, and Stimulus.Sound.Explosion: blasts shake the ground), and it turns them into beliefs about the place. Hearing doesn't listen to the bus: it hears only Emit Sound and Emit Sound at Location (Hearing). Your own C++ sense subscribes like this:

virtual TArray<FName> GetStimulusTags() const override
{
    return { FName(TEXT("Stimulus.Custom.Psychic")) };
}

virtual void OnStimulusReceived(const FAPSStimulusEvent& Event) override
{
    PendingPsychicEvents.Add(Event);   // use it in the next PreTick or Evaluate
}

A Blueprint sense can list tags in Subscribed Stimulus Tags, but receiving is C++ only, so nothing happens when one arrives. For a Blueprint reaction to your own signal, call Set Target Confidence or Report Location Belief on the AI you want to affect.

Ideas#

SenseHow
ThermalDistance falloff × a heat value on the target; ignores light
Motion sensorOnly targets above a speed (Context › Target Velocity). Freeze and you vanish
Mana sightReads a mana value off the target: a spellcaster glows, a rogue doesn't
PsychicIgnores range and walls, but with a very low Location Accuracy: it knows you exist, not where
RadarLong range, low confidence, poor accuracy: an early warning for a squad
Blood scentConfidence grows with the target's missing health
FootprintsFootprint actors with APS Target Components and a short-range sense: the AI follows a real trail
APS Advanced Perception System, by AuraGame. Unreal Engine 5.2 and later. These docs are for APS 3.0.
Questions and bug reports