Movement System

Documentation Unreal Engine AI Movement

Tactical AI positioning: direction sampling, distance keeping, strafing, navmesh awareness, and corner detours.


Tactical positioning that weighs distance, angle, other enemies, and the navmesh every tick, then moves the AI to its best spot.
The MovementEvaluatorComponent samples directions around the AI, scores each one, and steers toward the winner. You tune the behavior with a profile per combat role, and the component handles the rest: strafing, keeping range, avoiding the pack, staying off ledges, and pathing around corners.
It runs on its own. None of this needs the other plugin systems.

How it works

Direction sampling

Each tick the component samples NumSamples directions (16 by default) around the AI and scores each one.
Viewport
LVL_SEC_Showcase
AI Pawn
Target
Ideal Range
Green arrows = High Score (Preferred Direction)
A direction's score comes from how well it serves the target distance, whether it faces the target, whether another enemy sits in the way, whether it stays on walkable navmesh, and any positioning rules you added. The best direction wins, and the top few blend for a smooth result.

Distance and the comfort zone

The AI holds a band around its Desired Distance instead of a single exact range, and the band is wider on the crowded side than the far side. Crowd Tolerance (0.45) sets how far inside the ideal range the AI puts up with a target pressing in, and Distance Tolerance (0.2) sets how far past it still counts as close enough. At a desired 400 cm that band runs 220 to 480 cm. Inside it the AI circles, closer than 220 it gives ground, past 480 it closes in.
Distance Comfort Zone
MovementBehaviorProfile
CROWDEDCOMFORT ZONETOO FAR220near edge400desired480far edgegive groundcircleclose intarget (0)AI
Comfort band = 220 to 480 cm, from a desired 400 with Crowd Tolerance 45% on the near side and Distance Tolerance 20% on the far side. Inside it the AI circles. Closer than the near edge it gives ground, at a fraction of its walk speed. Past the far edge it closes in.
Widen Crowd Tolerance for an enemy that stands its ground when you walk into it, and narrow it for one that insists on its space. Set the two equal for a band the same width on both sides.

Giving ground

An enemy pushed inside its near edge backs away at a fraction of its walk speed, set by Retreat Speed Scale (0.45). A player closing at a run gains ground the whole way, so the gap shuts instead of holding while the enemy walks backward.
How hard crowding pushes the enemy toward giving ground instead of circling comes from Close Range Retreat Bias (0.5). Raise it toward 1 for a harder push out, drop it to 0 to leave the enemy circling at any range.
It also stops backing away from a target it cannot outrun. With Yield Retreat To Closing Target on, an enemy whose target closes faster than it can retreat holds its footing and circles instead. The check weighs the target's own speed against this enemy's retreat speed, so it needs no per-enemy tuning. A target with no movement of its own leaves it inert.
AdvancedRetreat tuning knobs
  • Retreat Speed Scale (0.45) is a share of the pawn's normal walk speed, and 1 removes the cap. Backing away and running sideways do not stack their speed costs; the larger of the two applies, so a diagonal retreat is slowed once.
  • Retreat Yield Strength (1) is how completely a closing target cancels the retreat. 1 hands the frame to circling, 0.5 halves the effect, 0 keeps the enemy backing away regardless.
  • Closing Speed Smoothness (4) is how fast the enemy notices its target's speed changing. At 4 it settles in about a quarter of a second, enough to catch a committed run while one frame of a landing or a dodge barely moves it.
  • Close Range Retreat Bias (0.5) is how hard being crowded past the near edge favors giving ground over circling. It leaves tolerated crowding inside that edge alone.
  • Giving way ramps in. It starts once the target closes at half the enemy's retreat speed and completes once the two match.
A Min Analog Walk Speed set on the pawn's Character Movement component floors the speed cap, so leave that at 0 for Retreat Speed Scale to hold.

Strafing

The AI circles the target while holding distance. It swaps sides automatically when the other side scores better (bAutoStrafeSwap), and it rests after strafing too long so the motion stays natural (bEnableStrafeRest, which fires OnStrafeResting). Under sustained player attention it can swap sides on threat, configured per profile (needs Threat Detection).

Sideways speed penalty

Strafe animations read wrong at full run speed, so the pawn slows as its heading turns away from the direction it faces. StrafeSpeedPenalty (0.75) sets the cost, StrafeFreeAngle (15 degrees) is the band around straight ahead that costs nothing, and StrafeFullPenaltyAngle (40 degrees) is where the cost tops out. At the defaults a heading keeps all its speed out to 15 degrees, 82% at 22.5, and 44% at 40 or wider. It applies on the Tactical layer only; pathfinding, detour, and attack approach move the pawn along a path and never reach it.
Raise StrafeFreeAngle if an enemy closing distance looks like it is jogging. Direction scoring angles the approach a few degrees while the AI holds a strafe side or steps around another enemy, and those degrees cost speed. Set it to 0 to charge the penalty at any angle off forward.

Two movement layers

The component switches strategy by distance:
LayerWhenHow
TacticalTarget within HybridSwitchDistance (800)Direct input for responsive strafing and positioning. Still reads the navmesh per direction.
StrategicTarget far or path-blockedNavmesh pathfinding to route around obstacles.

Rounding corners: detour escalation

When a target ducks behind a corner it sits close in a straight line but far along the navmesh, so plain strafing grinds into the wall. Detour escalation hands off to pathfinding to round the corner, then resumes strafing the instant the AI regains the angle. On by default, it only acts inside HybridSwitchDistance, stays inert with no navmesh, and leaves the AI tactical when the target has no real route (walled off) rather than walking it into a dead end.
AdvancedDetour tuning knobs
Detour escalates two ways:
  • Far detour (predictive): engages when route / straight-line exceeds DetourEnterRatio (1.6) and route - straight-line exceeds DetourMinExcess (200 cm). The second gate lets the AI strafe past a thin pillar instead of pathfinding around it.
  • Tight corner (reactive): when the AI wants to close on a target it can't see but isn't gaining ground (closing slower than DetourStallSpeed, 60 cm/s), it escalates. A pillar never trips this, because the AI keeps closing past it.
Detour releases on route geometry: it holds while the route still runs long and lets go once the walk straightens out. Release uses DetourReleaseRatio (1.35) and DetourReleaseMinExcess (100 cm), both under the enter thresholds so the AI does not flip between pathfinding and strafing every recheck. Because straight-line distance never enters the test, a Waiter or Flanker keeps routing around a pit while the player stands close on the far side.
Each recheck runs at most once per DetourRecheckInterval (0.5 s) and is staggered across the pack. bDetourUseLineOfSightPreGate (on) skips the route query when the AI both sees the target and has walkable ground straight to it, checked with a navmesh raycast along that line. A pit, gap, or railing breaks the raycast, so the route still gets measured and the AI routes around it. The pre-gate cannot change the decision, only how often the query runs, so leave it on and turn it off only to measure every interval regardless.
Tuning: raise DetourEnterRatio or DetourMinExcess if minor bumps trigger pathfinding. Raise DetourStallSpeed if a slow corner grind does not register. SEC.Debug.Movement.LogMovement 1 logs each recheck (ENGAGE, HOLD, release).
Automation tests live under SoulslikeEnemyCombat.Movement.Detour.* in the Session Frontend.

Which way the pawn looks while it travels

By default an enemy keeps looking at its target however it is moving, which reads well up close and looks wrong on a long walk or around a blind corner. Each pathfinding layer can face its direction of travel instead.
SettingApplies while
Strategic Facing ModePathfinding at long range, with no detour running
Strategic Turn RateThe same, in degrees per second; 0 keeps the controller's own turn speed
Detour Facing ModePathfinding around close cover, taking over from the strategic mode whenever a detour is the reason
Detour Turn RateThe same, in degrees per second; 0 keeps the controller's own turn speed
Use Default Facing keeps the controller's normal focus tracking. Face Movement Direction turns the pawn to look where it is walking.
Turning the pawn away from its target changes what every angle-based system sees, because they all read the pawn's real facing: reaction angle gates, action angle scoring, and the awareness sight cone. An enemy rounding cover with Face Movement Direction can lose sight of the player mid-detour. That is usually the point, and it is worth knowing before you set it.
A custom controller reads the current intent through GetDesiredFacingIntent(); EnemyControllerBase consumes it for you.

Staying on the navmesh

In the tactical layer each sampled direction gets a soft walkability score, so enemies stop strafing off ledges and into walls mid-fight. It is a preference, not a veto, so a boxed-in AI still picks its safest direction instead of freezing, and a forward probe eases speed down at a rim. On by default, inert with no navmesh. It caches per navmesh poly, refreshes at 10 Hz, and staggers across enemies to stay in budget.
Hazard areas let enemies avoid a patch they can still walk on (lava, spikes):
  1. Create a Blueprint subclass of NavArea (e.g. NavArea_Lava) with a distinct draw color.
  2. Drop a Nav Modifier Volume over the patch, set its Area Class to your hazard area, and rebuild navigation (press P to verify it tints).
  3. On the component, add the area to Hazard Area Avoidance with a strength from 0 to 1: 0 ignores it, 1 treats it like a wall, ~0.75 routes around it but crosses when cornered.
Raise the area's Default Cost too, so the long-range pathfinder also avoids it. A patch authored as NavArea_Null carves a hole in the navmesh, so the AI treats it as an impassable wall with no entry needed.
AdvancedCover-break hook: detecting a walled-off target
The detour recheck already computes whether the AI can see its target and whether a full route exists. These server-only, Blueprint-pure getters on MovementEvaluatorComponent expose it, so you can react when a target hides behind something solid:
GetterMeaning
IsApproachBlocked()Sight blocked and no full route: the target is walled off. The cover-break gate.
HasLineOfSightToTarget()Clear view at the last detour recheck. Reports true when out of range or not rechecking.
IsTargetReachable()A full navmesh route exists at the last route measurement. Refresh runs when detour measures the route, not only when sight is blocked. Read it through IsApproachBlocked, not alone.
IsApproachStalled()Tactical steering is grinding cover without gaining ground. Transient, tactical-range only.
Delegates:
EventFires
OnTargetLineOfSightChangedSight flips, clear to blocked or back. Carries the new value.
OnApproachBlockedRising edge only: once when the target becomes walled off.
The signal refreshes on the detour cadence (DetourRecheckInterval, 0.5 s), inside HybridSwitchDistance, while bEnableDetourEscalation is on.
Cover-break pattern: bind OnTargetLineOfSightChanged and start a timer when it reports false. On expiry, gate on IsApproachBlocked(); if still true, line-trace from the pawn to the player on your cover channel, and the first blocking hit is the obstacle to attack. Clear the timer when sight returns. ResetApproachBlockedState() clears the signal by hand; it also runs on target loss, detour disable, and owner death.
AlternativeRoot-motion output (evaluate-only mode)
To drive locomotion with root motion instead of direct movement input, turn off Apply Movement Input. The component keeps scoring and publishing a direction each tick without driving the pawn. You forward that direction into your Anim Blueprint, which blends the locomotion animations, and their root motion moves the character. No C++ required.
Each tick, read one of these Blueprint-callable getters and feed it to your locomotion blendspace:
  • Get Current Direction Angle: signed yaw in degrees (0 ahead, +90 right, -90 left, +/-180 behind). Good for a 1D turn/strafe blendspace.
  • Get Current Direction Local: a 2D vector (X = forward, Y = right) for a standard 2D locomotion blendspace.
  • Get Current Direction: the world-space direction, if you need it raw.
In this mode the component stays tactical-only and issues no path-following moves. It still scores directions against the navmesh when nav-aware sampling is on.

Setup

The automated path: the combat role picks the profile.
  1. In your EnemyAIConfig, assign a MovementBehaviorProfile to each combat role (e.g. Attacker gets an aggressive profile, Waiter gets a patient one).
  2. SECCombatControllerComponent::SyncStateForCombatRole calls MovementEvaluatorComponent::SyncForCombatRole when the role changes, applying the matching profile.
No wiring needed on EnemyControllerBase. See Combat Roles.

Data asset profile

Create a MovementBehaviorProfile (Right-click → Miscellaneous → Data Asset → MovementBehaviorProfile). A profile holds only the role-swappable fields:
Distance
Desired Distance
400
Distance Tolerance
0.2
Crowd Tolerance
0.5
Rest Behavior
Enable Strafe Rest
Strafe Rest Time Limit
10
Strafe Rest Duration
1.5
Threat Response
Swap Strafe On High Threat
Adjust Distance By Threat
Threat Distance Scale
1
Positioning Rules
Positioning Rules
0 Array elements
Desired Distance is the ideal range in cm, Distance Tolerance and Crowd Tolerance are the far and near halves of the comfort band as fractions of it, the Rest Behavior fields govern strafe fatigue, and the Threat Response fields feed off Threat Detection. Positioning Rules are covered below. Everything else lives on the component and is the same for every role (next section).
Adjust at runtime. These are Blueprint-callable nodes on the component: Apply Behavior Profile swaps the whole profile, Set Desired Distance changes range, Set Strafe Side and Swap Strafe Side force a direction, and Set Distance Multiplier scales range (threat detection drives this one).
C++Preset configs
FMovementBehaviorConfig factory methods fill the profile-level fields for common archetypes, applied with ApplyBehaviorConfig:
MovementEvaluator->ApplyBehaviorConfig(FMovementBehaviorConfig::MakeAttacker());
  • MakeDefault(): balanced (400 cm).
  • MakeAttacker(): close, aggressive, minimal rest (300 cm).
  • MakeWaiter(): far, patient, frequent rest (600 cm).
  • MakeFlanker(): medium, quick repositioning pauses (400 cm).
  • MakeSupporter(): medium-far, moderate rest (500 cm).
  • MakeElite(): relentless pressure, very short rest (350 cm).
The Make* helpers are C++ only. In Blueprint, use a MovementBehaviorProfile data asset instead, or build a MovementBehaviorConfig struct by hand and pass it to Apply Behavior Config.

Tuning the component

Beyond the profile, the component exposes the full tuning surface: strafe feel, avoidance, navmesh sampling, hybrid switching, blending, and stuck detection. Set these on the component defaults; they apply to every role.
Movement Evaluator
Apply Movement Input
Num Samples
16
Distance Tolerance
0.2
Crowd Tolerance
0.5
Direction Smoothness
8
Direction Change Penalty
1
Speed Dead Zone
0.1
Target Position Smoothness
5
Use Pivot Smoothing
Strafe
Auto Strafe Swap
Strafe Swap Threshold
3
Strafe Swap Cooldown
2
Strafe Speed Penalty
0.8
Strafe Free Angle
15
Strafe Full Penalty Angle
40
Strafe Preference Weight
1
Retreat
Close Range Retreat Bias
0.5
Retreat Speed Scale
0.5
Yield Retreat To Closing Target
Retreat Yield Strength
1
Strafe Time Limits
Enable Strafe Time Limit
Strafe Time Limit
5
Strafe Time Limit Fluctuation
2.5
Rest After Strafe Swap
Strafe Swap Rest Time
0.5
Strafe Swap Rest Fluctuation
0.3
Strafe Rest
Enable Strafe Rest
Strafe Rest Time Limit
10
Strafe Rest Time Limit Fluctuation
4
Strafe Rest Duration
1.5
Strafe Rest Duration Fluctuation
0.5
Avoidance
Enable Avoidance
Avoidance Radius
100
Avoidance Weight
0.4
Avoidance Slip Bonus
0.8
Velocity Aware Avoidance
Momentum Bias
0.7
Nav Sampling
Enable Nav Aware Sampling
Nav Probe Distance
120
Nav Score Weight
1
Nav Blocked Floor
0.1
Guard Ledge Drops
Nav Edge Speed Floor
0.1
Stuck Detection
Enable Stuck Detection
Stuck Velocity Threshold
50
Stuck Time Threshold
0.5
Stuck Check Interval
0.2
Direction Blending
Direction Blend Count
3
Direction Blend Min Score Ratio
0.7
Hybrid Movement
Enable Hybrid Movement
Hybrid Switch Distance
800
Hybrid Switch Hysteresis
100
Detour
Enable Detour Escalation
Detour Enter Ratio
1.6
Detour Min Excess
200
Detour Recheck Interval
0.5
Detour Use Line Of Sight Pre Gate
Detour Line Of Sight Channel
Visibility
Detour Escalate On Stall
Detour Stall Speed
60
Detour Release Ratio
1.4
Detour Release Min Excess
100
Detour Facing Mode
Use Default Facing
Detour Turn Rate
0
Strategic Facing Mode
Use Default Facing
Strategic Turn Rate
0
See the Configuration Reference for the profile data asset.

Positioning rules

Positioning rules nudge direction scores to shape where the AI wants to be. Add them to a profile's Positioning Rules array; each has a Weight (0 to 1) and they stack additively.
The built-in UAnglePreferenceRule pushes the AI toward or away from an angle relative to the target's facing:
  • PreferredAngle = 0: frontal.
  • PreferredAngle = 90: flanking.
  • PreferredAngle = 180: behind the target.
  • bAvoidInstead = true: invert it to avoid that angle.
  • AngleTolerance (30) is the green zone where the rule stops nudging; DirectionCommitment (45) stops the AI flip-flopping between orbit directions.
AdvancedWrite a custom rule
UPositioningRule is Blueprintable, so you can author a rule in Blueprint or C++.
Blueprint: create a Blueprint class deriving from PositioningRule, override Evaluate Direction, and return -1 (discourage) to +1 (encourage). The context gives you the sample direction, the controller, and the desired distance; use the controller's Get Pawn for the AI and Get Focus Actor for the target and its facing. Add an instance to the profile's Positioning Rules array.
C++: subclass and override EvaluateDirection_Implementation:
UCLASS(Blueprintable)
class UMyFlankingRule : public UPositioningRule
{
    GENERATED_BODY()
 
    virtual float EvaluateDirection_Implementation(
        const FDirectionEvaluationContext& Context) const override
    {
        // Context.SampleDirection, Context.Controller, Context.DesiredDistance
        // Return -1.0 to +1.0
    }
};

Behavior Tree tasks

For movement outside the main evaluation loop:
TaskPurpose
Move Until DistancePathfind toward a blackboard target until within a set distance. Supports timeout, max chase distance, and debug draw.
Make DistanceRetreat from a target with direct input until reaching a distance. Includes stuck detection and timeout.
Debug SEC ValuesLogs the SEC_ blackboard values (target, distance, action ID) to log and screen.

Debug

Console commands toggle drawing and logging at runtime:
SEC.Debug.Movement.DrawScoring 1      // Direction scores
SEC.Debug.Movement.DrawAvoidance 1    // Avoidance radii
SEC.Debug.Movement.DrawNav 1          // Navmesh validity and the ledge guard
SEC.Debug.Movement.LogMovement 1      // Discrete events (layer, suspend, pathfinding, stuck, detour)
SEC.Debug.Movement.LogTick 1          // Per-tick state dump (distance, error, speed)
SEC.Debug.Movement.LogScoring 1       // Direction scoring detail
SEC.Debug.Movement.LogAvoidance 1     // Avoidance detail
SEC.Debug.Movement.LogStrafeSwap 1    // Strafe swap decisions
SEC.Debug.Movement.LogStrafeState 1   // Strafe state changes
LogTick is separate on purpose: it fires every tick for every enemy, so it buries the discrete events. Turn on LogMovement to follow events, and add LogTick only when you need the raw stream. The tick stream writes to its own log category, so you can mute it while one enemy floods the console:
Log LogMovementEvaluatorTick off
AdvancedDebug one enemy from the Details panel
The console commands are global. To isolate a single enemy, each one carries a matching checkbox on its MovementEvaluatorComponent, under Movement Evaluator → Debug in the Details panel: bDebugDrawScoring, bDebugDrawAvoidance, bDebugDrawNav, bDebugLogMovement, bDebugLogTick, bDebugLogScoring, bDebugLogAvoidance, bDebugLogStrafeSwap, and bDebugLogStrafeState. They are Blueprint-readable and writable too, for toggling at runtime.
AdvancedAll movement events
The component broadcasts these delegates, bindable in Blueprint or C++:
EventWhen
OnStrafeStartedStrafing begins (carries the side)
OnStrafeEndedStrafing stops
OnStrafeSwappedDirection changes (carries the new side)
OnStrafeRestingStrafe fatigue rest begins
OnMovementSuspendedMovement suspended (carries duration)
OnMovementResumedSuspension ends
OnArrivedAtDesiredDistanceAI enters the comfort zone
OnDepartedDesiredDistanceAI leaves the comfort zone
OnMovementLayerChangedLayer switches (carries new and previous)
OnSwitchToPathfindingSwitched to Strategic
OnSwitchToDirectInputSwitched to Tactical
OnPathfindingFailedPathfinding blocked or unreachable
OnAttackApproachCompleteAttack approach reached its distance
OnAttackApproachFailedAttack approach failed
OnTargetLineOfSightChangedDetour-recheck sight flips (carries the new value)
OnApproachBlockedTarget becomes walled off (rising edge)

Integration

SystemHow movement uses it
Threat DetectionSECCombatControllerComponent auto-wires it. Threat response (bSwapStrafeOnHighThreat, bAdjustDistanceByThreat) is set per profile and switches with role.
Combat RolesEach role applies a different MovementBehaviorProfile.
Action SystemActions can request an attack approach to close distance before executing.