Action System

Documentation Unreal Engine AI Actions

Intelligent action selection that scores every available move and picks the best one for the moment.


The brain behind enemy decisions. It scores every action against live battlefield data: distance, angle, health, threat, and context tags.
ActionEvaluationComponent
EvaluateBestAction()
Gameplay Ability
(Simple Action)
Behavior Tree Sequence
(Complex Logic)
The system dynamically chooses the execution method. Behavior Trees can be chained for complex sequences.

When to Use This

  • Any AI that needs to choose between multiple attacks or behaviors
  • Enemies with distance-dependent movesets (melee vs ranged)
  • Boss fights with phase-based action sets
  • Group AI where different roles use different attack patterns
This system can be used independently of the other plugin systems.

Core Concepts

Scoring

On every evaluation, all valid actions compete. The highest score wins.
Final Score = SelectionWeight 
            × Range Score (Distance, Angle, Health, Speed)
            × Context Multipliers (Tags)
            × Novelty Bonus (Variety)
            × Chain Bonus (Combos)
            × Custom Scorers (your own factors)

Range & Angle Evaluation

Each action uses an FRangeEval curve that defines its optimal zone. An attack might be valid from 0-500cm, but its score peaks at 150-250cm.
// Light Attack: Perfect at mid-close range
Distance.MinValue = 0;
Distance.OptimalMin = 100;   // Score ramps up to 1.0
Distance.OptimalMax = 250;   // Score stays at 1.0
Distance.MaxValue = 400;     // Score ramps down to 0.0
Available presets:
  • FRangeEval::MakeMeleeRange() - Close-range attacks
  • FRangeEval::MakeRangedRange() - Long-range attacks
  • FRangeEval::MakeFrontalAngle() - Forward-facing cone
  • FRangeEval::MakeLowHealthRange() - Triggers at low health
  • FRangeEval::MakeAlwaysOne() - Always returns 1.0 (no range preference)
Beyond distance and angle, each action also scores against Health (HealthEval) and Movement Speed (SpeedEval, the AI's current speed in cm/s). Both are FRangeEval curves that default to MakeAlwaysOne() (no preference). Use SpeedEval for moves that depend on pace, such as a running attack that should only score while the enemy is sprinting.

Cooldowns & Variance

PropertyDescription
DurationBase time before the action can be reused.
InitialCooldownCooldown applied on spawn (prevents instant nukes).
RandomizationAdds ±% variance (e.g., 0.2 = ±20%).
SpawnCooldownChanceChance to start on cooldown (desyncs groups of enemies).
MaxConsecutiveUsesLimit repeats before the AI must switch (e.g., 1 = can't use twice in a row).
An action commits its cooldown only after the ability activates. If a CanActivateAbility override (or any other GAS gate) refuses the activation, the action stays off cooldown and is free to retry next tick.

Novelty & Chains

  • Novelty: Recently used actions get a score penalty. This creates diverse behavior without rigid randomization.
  • Chains: Actions can define a PreferredNextActionId. After a "Light Attack", the "Heavy Finisher" gets a massive score boost - creating natural combos.

Organic Combos

You don't script 'Hit-Hit-Smash': you make 'Smash' very likely after 'Hit'. If the player rolls out of range, the AI breaks the combo instead of swinging at air.

Preconditions (Hard Gates)

Before scoring, every action runs a set of hard gates. Fail one and the action drops out of the running for that tick.
GateBlocks the action when
RequiresTagsThe combined Self/Target/World tags don't hold all of them.
BlockTagsAny of them sits on Self, Target, or World.
StaminaCostThe AI's stamina falls below the cost.
Require Line Of SightThe box is ticked and the AI has no clear view of its target.
Custom GatesAny USECGate in the action's Custom Scoring returns false. See Custom Scorers & Gates.
Require Line Of Sight reads FDecisionContext::bHasLOS, which the Build Decision Context task traces from the AI's eyes to the target's every tick. Tick it for moves that need a clean line (ranged shots, gap-closers) and leave it off for attacks that land blind, like a radial slam or a taunt.

Execution Modes

Actions execute in one of two ways depending on complexity.

Gameplay Ability (Simple)

Activates a Gameplay Ability and listens for the end tag.
ExecutionMode = EActionExecutionMode::GameplayAbility;
AbilityClass = UGA_LightAttack::StaticClass();
AbilityTag = "SEC.Action.LightAttack";
AbilityEndTag = "SEC.Action.End";
Your ability class must inherit from UGameplayAbilityBase. It handles the end-event handshake that tells the Action System when the ability finishes. Without it, the AI gets stuck waiting.
UGameplayAbilityBase provides:
  • Rotation Lock (bLockAIRotation): the AI commits to the attack direction with no tracking mid-swing.
  • Motion Warping (MotionWarpingTargetName): sets up a warp target from the resolved action context so attacks can steer toward their target. See Motion Warping.
  • Action Context (FSECExecutionContext): passes target, distance, direction, magnitude, and context tags to the ability on activation.

Motion Warping

UGameplayAbilityBase can steer an attack toward its target with Unreal's Motion Warping. On activation the ability registers a warp target from the resolved action context: the event payload's target for event-driven activations, or AIController::GetFocusActor() as the fallback for tag or direct activations with no payload target. The plugin only registers the target; the montage's Motion Warp anim notify performs the warp.
The pawn must carry a UMotionWarpingComponent. AEnemyCharacterBase does not add one, so warping no-ops on pawns without it.
PropertyDefaultEffect
MotionWarpingTargetNameTargetWarp target key. Must match the Warp Target Name on the montage's Motion Warp notify. Set to None to disable warp setup for the ability.
MotionWarpingOffset100.0Warp point offset in cm, placed in front of the target toward the AI. Doubles as a switch-off distance: once the AI is closer than this, root-motion warping pauses so the attack does not overshoot. The pause condition is created only while the offset is above 0.
MaxWarpDistance0.0Skip gate. If the AI is closer than this when the ability activates, no warp target is set up. The default of 0 leaves the gate off (any distance warps).
bLockAIRotationtrueStops the controller from yawing the pawn toward focus during the ability. Warping runs on a separate path and still rotates the pawn during the notify window.
Setup:
  1. Add a UMotionWarpingComponent to your character, in the Blueprint through Add Component or in a C++ subclass through CreateDefaultSubobject.
  2. Place a Motion Warp notify window over the attack's windup in the montage.
  3. Set the ability's MotionWarpingTargetName to the notify's Warp Target Name.
Where the AI tracks versus commits is down to the notify window: rotation warps toward the target across the window, then the swing commits past it. Because bLockAIRotation and warping run on separate paths, a committed attack can still track during the warp window while controller facing stays locked.
AM_SEC_TwoHanded_TripleAttack_Montage in the showcase content has a working Motion Warp setup. Copy its notify and warp values as a starting point.

Activation Modes

Each action can choose how its ability is triggered via the ActivationMode property:
ModeHow It Works
ByTag (default)Calls TryActivateAbilitiesByTag using AbilityTag. The ability must have matching AbilityTags in its class defaults.
ByEventSends a Gameplay Event using AbilityTag as the event tag. The ability must have a matching Trigger entry (Gameplay Event) in its class defaults, or use WaitGameplayEvent.
ByEvent mode sends a payload containing the instigator and the current target actor, so your ability can access them immediately.
// Standard tag-based activation (default)
ActivationMode = EAbilityActivationMode::ByTag;
AbilityTag = "SEC.Action.Attack.Light";
 
// Event-based activation
ActivationMode = EAbilityActivationMode::ByEvent;
AbilityTag = "SEC.Action.Attack.SweepEvent";  // Used as the event tag
ByEvent abilities do not need AbilityTags in their class defaults. Instead, add a Trigger entry with the matching tag and set its source to Gameplay Event. The AbilityTag field serves double duty: tag activation in ByTag mode, event tag in ByEvent mode.

Behavior Tree (Complex)

For multi-stage behaviors - circling, investigating, complex boss moves. Runs one or more Behavior Trees in sequence.
ExecutionMode = EActionExecutionMode::BehaviorTreeSequence;
BehaviorTreeSequence = { BT_ChargeWindup, BT_ChargeRelease };
BehaviorTreeTimeout = 5.0f;
When an action uses BehaviorTreeSequence mode, the system writes several blackboard keys before the tree starts, so your BT tasks can read them immediately:
Blackboard KeyTypeValue
SEC_ActionIdNameThe action's ActionId
SEC_AbilityTagStringThe AbilityTag from the ActionSpec
SEC_AbilityEndTagStringThe AbilityEndTag from the ActionSpec
SEC_ActivationModeName"ByTag" or "ByEvent"
SEC_TargetActorObjectThe current target (focus) actor
SEC_SelfActorObjectThe AI pawn
SEC_DistanceFloatDistance to target
These keys must exist in your Blackboard Data Asset for the writes to succeed. If a key is missing from the asset, the write is silently ignored. Add all SEC_ keys to your Blackboard asset.

BT Tasks for Ability Activation

The plugin provides two Behavior Tree tasks for activating abilities inside a BT:
Activate Blackboard Ability - Reads ability class and tags from the blackboard keys written by the Action System. Has an ActivationMode property with three options:
  • FromBlackboard (default) - reads SEC_ActivationMode from the blackboard. Falls back to ByTag if the key is empty.
  • ByTag - always uses tag-based activation.
  • ByEvent - always uses event-based activation.
Activate Ability - A standalone task where you set the ability class directly. Has ActivationMode (ByTag / ByEvent) and an EventTag field that appears only in ByEvent mode. Use this when you want to activate a specific ability from a BT without going through the Action System.

Contextual Execution

Sometimes you need to tell an action exactly what to do - "Attack that specific target", "Use this item", or "Apply strength 0.5".
Creating a new ActionId for every variation (e.g., Attack_TargetA, Attack_TargetB) is impossible. Instead, use Context.

1. Execute with Context

Call this function from Blueprint or C++ to pass dynamic data:
FSECExecutionContext Context;
Context.Target = CustomTargetActor;
Context.OptionalObject = SomeItem;
Context.Magnitude = 0.5f;
Context.ContextTags.AddTag(Tag_QuickVariant);
 
ActionEvaluationComponent->ExecuteActionWithContext("SpecialAttack", Context);

2. Receive in Ability

Your ability (inheriting from UGameplayAbilityBase) automatically captures this data.
  • Event: On Action Context Received (Blueprint)
  • Accessor: GetActionContext() (Blueprint Pure)
The system maps the context as follows:
Context FieldMaps To
TargetActionContext.Target
OptionalObjectActionContext.OptionalObject
MagnitudeActionContext.Magnitude
ContextTagsActionContext.ContextTags
[!NOTE] If Target is not provided in context, the system falls back to the AI's current Focus Actor.

3. Gate Before Activation

SEC hydrates the context before the ability activates, so you can read the payload inside a standard CanActivateAbility override and refuse the move there. Override CanActivateAbility (Blueprint or C++) on your UGameplayAbilityBase, read GetActionContext(), and return false to block.
  • Event-triggered activations (ByEvent, Execute With Context, reactions) fill the context from the payload.
  • Tag and direct activations fill it from the AI's focus target.
// Inside your ability's CanActivateAbility override:
const FSECExecutionContext& Ctx = GetActionContext();
if (!Ctx.GetTarget() || Ctx.Magnitude < RequiredCharge)
{
    return false; // refuse before the ability runs
}
A refusal here costs nothing: SEC commits no cooldown and interrupts no running action. Reach for it when your rule needs the payload; use CanExecuteAction for self or world gates that don't.

How Action Sets Are Managed

The plugin uses a push-based architecture. Combat role changes trigger SECCombatControllerComponent to sync everything.
AICombatRoleSubsystem assigns new role
  → SECCombatControllerComponent::HandleCombatRoleAssigned()
    → SyncStateForCombatRole()
      → SECActionSetComponent::SyncForCombatRole()
        → GetActionSetForRole()  // Resolution chain below
        → ActionEvaluationComponent::SetActionSetAndResetState()

Resolution Priority

When GetActionSetForRole() is called, it walks this chain and returns the first match:
  1. Runtime Override - SetRuntimeOverride(ActionSet). Highest priority. Use for boss phase transitions.
  2. Equipped Weapon - If the weapon actor implements ISECWeaponActionSetProvider, the system calls GetWeaponActionSetForRole(RoleTag) on it.
  3. Config Role - Role-specific ActionSet from EnemyAIConfig (e.g., a different set for "Attacker" vs "Flanker").
  4. Config Default - Fallback ActionSet from EnemyAIConfig.
  5. Component Default - SECActionSetComponent → DefaultActionSet property (set in Blueprint on the pawn).
  6. None - No ActionSet found. AI will only move, never attack.
Changes to weapon or override take effect immediately, with no role change required.
This is the same resolution chain used by SECReactionSetComponent for reaction sets. Learn one, know both.

Weapon-Driven AI

Because 'Equipped Weapon' outranks Config, you can change an enemy's entire brain by giving them a different item. A skeleton with a Bow becomes a sniper. Disarm it, and it falls back to its Config set, becoming a brawler.

Weapon Action Set Provider

To make a weapon provide action sets, implement the ISECWeaponActionSetProvider interface on your weapon actor (Blueprint or C++). Override GetWeaponActionSetForRole(RoleTag) to return the appropriate UActionSet*.
// Tell the pawn about the weapon:
Pawn->ActionSetComponent->SetEquippedWeapon(WeaponActor);
 
// Clear to revert to Config-based resolution:
Pawn->ActionSetComponent->SetEquippedWeapon(nullptr);

Dynamic Actions (Runtime)

Grant and revoke individual actions without changing the entire set.
Pawn->ActionSetComponent->GrantAction(ThrowGrenadeSpec);
Pawn->ActionSetComponent->RevokeAction("ThrowGrenade");
Granted actions persist across role changes and action set swaps. They participate in evaluation alongside the base ActionSet: same scoring, same cooldowns.
For bulk operations:
Pawn->ActionSetComponent->GrantActionsFromSet(BonusActionSet);  // Add all from set
Pawn->ActionSetComponent->RevokeActionsFromSet(BonusActionSet); // Remove all from set
Pawn->ActionSetComponent->ClearGrantedActions();                // Remove all granted

World State Tags

WorldTags carries global game state into AI scoring. Push them through USECWorldTagSubsystem (or the one-node USECWorldTagLibrary helper); the build task copies them into FDecisionContext::WorldTags each tick. Use them for boss phases, weather, arena state, or any condition that affects multiple enemies at once.
The plugin ships three example world tags: SEC.World.Combat.Active, SEC.World.Boss.Active, and SEC.World.Boss.Casting. Use them as starters or define your own under any namespace.
// Server BP, anywhere
USECWorldTagLibrary::AddWorldTag(this, SEC.World.Combat.Active);
USECWorldTagLibrary::AddWorldTagForDuration(this, SEC.World.Boss.Casting, 3.0f);
USECWorldTagLibrary::AddWorldTagUntil(this, SEC.World.Boss.Active, {YourGame.Boss.Defeated});
VariantBehavior
AddWorldTagPermanent until RemoveWorldTag.
AddWorldTagForDurationRemoves after N seconds. Re-adding refreshes the timer.
AddWorldTagUntilRemoves when any sentinel tag is added.
Mutators run on the server only (BlueprintAuthorityOnly); client calls no-op silently.
Client-side reads (UI, audio): drop USECWorldTagComponent on GameState. The component replicates the subsystem's tags and broadcasts OnTagsChanged on clients. Without the component, USECWorldTagLibrary::GetWorldTags returns empty on clients and warns once.

Custom Scorers & Gates

Built-in factors cover distance, angle, health, speed, and tags. For anything else (mana, ally count, terrain, faction state), attach your own Scorers and Gates to an action. Each FActionSpec has a Custom Scoring group with two arrays:
TypeBase ClassReturnsEffect
ScorerUSECScorerA multiplier (1.0 = no effect)Folds into the action score. Above 1 favors, below 1 disfavors.
GateUSECGatetrue / falseA false drops the action from selection, like a built-in precondition.
Add an entry, then pick a built-in class or your own Blueprint/C++ subclass and set its parameters inline. This is the same authoring pattern as Role Evaluators and Positioning Rules.

Built-in Classes

ClassKindUse
Attribute ScorerScorerScale by a GameplayAttribute through an FRangeEval curve. Optional normalize-by attribute (e.g. Mana / MaxMana).
Attribute GateGateAllow only while an attribute sits between a min and max.
For anything the built-ins miss, subclass USECScorer or USECGate in Blueprint and override its one function (ScoreMultiplier or PassesGate). The FSECScoringContext passed in carries the controller, target, owning ASC, and the seed that SeededRandom uses.
Keep scorer and gate subclasses stateless. A single instance is shared across every AI using the asset, so mutable fields would alias across enemies. For randomness, use the SeededRandom helper.
SeededRandom draws from the per-action seed in FSECScoringContext. On the reaction path that seed is 0, so SeededRandom returns a fixed value there. Vary reaction randomness through a built-in factor or your own context read instead.

Examples

Cast a spell only above a mana threshold. Add an Attribute Gate, set Attribute = Mana, MinValue = 30. The spell drops out of selection whenever mana is below 30.
Favor a heavy attack as rage builds. Add an Attribute Scorer, set Attribute = Rage, NormalizeBy = MaxRage, and shape the curve to peak near full. The attack scores higher as rage fills.
The same Scorers and Gates work on reactions. See Reaction System.

Custom Scoring Hooks

The ActionEvaluationComponent provides two BlueprintNativeEvent hooks for project-wide logic that applies to every action. For per-action rules, prefer Custom Scorers & Gates.
CanExecuteAction - Veto an action after all built-in gates pass.
bool CanExecuteAction(FName ActionId, const FDecisionContext& Context);
// Return false to block the action.
ModifyActionScore - Adjust the score after the pipeline computes it.
float ModifyActionScore(FName ActionId, float BaseScore, const FDecisionContext& Context);
// Return a modified score. Return BaseScore for no change.
Override these in a Blueprint or C++ subclass of UActionEvaluationComponent.

Quick Setup

  1. Create Asset: Right-click → MiscellaneousData AssetActionSet.
  2. Define Actions: Each entry needs an ActionId, SelectionWeight, DistanceEval, AngleEval, and an execution mode.
  3. Assign: Drop the ActionSet into your EnemyAIConfig, or set it directly for testing:
ActionEvaluationComponent->ActiveActionSet = MyActionSet;
See Configuration Reference for the complete EnemyAIConfig structure.

Key API

ComponentLocationRole
ActionEvaluationComponentControllerScoring, evaluation, execution
SECActionSetComponentPawnResolution, replication, weapon/override management
SECCombatControllerComponentControllerOrchestrates sync on role changes
ActionEvaluationComponent (Controller)
  • EvaluateBestAction(Context, Time, OutChosen) - Run the scoring pipeline.
  • ExecuteAction(ActionId) - Force-execute a specific action.
  • ExecuteActionWithContext(Id, Context) - Execute with custom data (Target, etc.).
  • SetActionOverride(ActionId, Multiplier) - Runtime buff/nerf a specific action's score.
  • CanExecuteAction() / ModifyActionScore() - Override hooks (see above).
SECActionSetComponent (Pawn)
  • SetEquippedWeapon(Actor) - Weapon-driven action set override.
  • SetRuntimeOverride(ActionSet) / ClearRuntimeOverride() - Boss phase override.
  • GrantAction() / RevokeAction() - Runtime action management.
  • OnActionExecutionStarted / OnActionExecutionCompleted - Replicated delegates for client UI/FX.
  • OnActionSetChanged - Fires when the active ActionSet changes (replicated).
  • OnActionCooldownStarted(ActionId, Duration) / OnActionCooldownExpired(ActionId) - Cooldown lifecycle delegates (replicated).
  • GetRemainingCooldown(ActionId) / IsActionOnCooldown(ActionId) / GetAllActiveCooldowns() - Query current cooldown state.

Debug Tools

// On ActionEvaluationComponent:
ActionEvalComp->bDebugLogDecisions = true;  // Log scoring breakdown
ActionEvalComp->bDebugLogExecution = true;  // Log execution flow

Integration Points

SystemHow It Connects
Movement SystemProvides distance/angle for scoring context
Combat RolesRole changes trigger automatic ActionSet swaps
Threat DetectionThreat level feeds into FDecisionContext
MultiplayerAction state replicates to clients via SECActionSetComponent
Custom character classes: ActionEvaluationComponent resolves the AbilitySystemComponent from the possessed pawn via IAbilitySystemInterface on possession. Pawns that do not implement this interface cause ability-based actions to silently fail. See Getting Started for setup details.