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.0Available presets:
FRangeEval::MakeMeleeRange()- Close-range attacksFRangeEval::MakeRangedRange()- Long-range attacksFRangeEval::MakeFrontalAngle()- Forward-facing coneFRangeEval::MakeLowHealthRange()- Triggers at low healthFRangeEval::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
| Property | Description |
|---|---|
Duration | Base time before the action can be reused. |
InitialCooldown | Cooldown applied on spawn (prevents instant nukes). |
Randomization | Adds ±% variance (e.g., 0.2 = ±20%). |
SpawnCooldownChance | Chance to start on cooldown (desyncs groups of enemies). |
MaxConsecutiveUses | Limit 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.
| Gate | Blocks the action when |
|---|---|
RequiresTags | The combined Self/Target/World tags don't hold all of them. |
BlockTags | Any of them sits on Self, Target, or World. |
StaminaCost | The AI's stamina falls below the cost. |
| Require Line Of Sight | The box is ticked and the AI has no clear view of its target. |
| Custom Gates | Any 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.| Property | Default | Effect |
|---|---|---|
MotionWarpingTargetName | Target | Warp 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. |
MotionWarpingOffset | 100.0 | Warp 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. |
MaxWarpDistance | 0.0 | Skip 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). |
bLockAIRotation | true | Stops 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:
- Add a
UMotionWarpingComponentto your character, in the Blueprint through Add Component or in a C++ subclass throughCreateDefaultSubobject. - Place a Motion Warp notify window over the attack's windup in the montage.
- Set the ability's
MotionWarpingTargetNameto 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:| Mode | How It Works |
|---|---|
| ByTag (default) | Calls TryActivateAbilitiesByTag using AbilityTag. The ability must have matching AbilityTags in its class defaults. |
| ByEvent | Sends 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 tagByEvent 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 Key | Type | Value |
|---|---|---|
SEC_ActionId | Name | The action's ActionId |
SEC_AbilityTag | String | The AbilityTag from the ActionSpec |
SEC_AbilityEndTag | String | The AbilityEndTag from the ActionSpec |
SEC_ActivationMode | Name | "ByTag" or "ByEvent" |
SEC_TargetActor | Object | The current target (focus) actor |
SEC_SelfActor | Object | The AI pawn |
SEC_Distance | Float | Distance 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) - readsSEC_ActivationModefrom 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 Field | Maps To |
|---|---|
Target | ActionContext.Target |
OptionalObject | ActionContext.OptionalObject |
Magnitude | ActionContext.Magnitude |
ContextTags | ActionContext.ContextTags |
[!NOTE] IfTargetis 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:- Runtime Override -
SetRuntimeOverride(ActionSet). Highest priority. Use for boss phase transitions. - Equipped Weapon - If the weapon actor implements
ISECWeaponActionSetProvider, the system callsGetWeaponActionSetForRole(RoleTag)on it. - Config Role - Role-specific ActionSet from
EnemyAIConfig(e.g., a different set for "Attacker" vs "Flanker"). - Config Default - Fallback ActionSet from
EnemyAIConfig. - Component Default -
SECActionSetComponent → DefaultActionSetproperty (set in Blueprint on the pawn). - 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 bySECReactionSetComponentfor 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 grantedWorld 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});| Variant | Behavior |
|---|---|
AddWorldTag | Permanent until RemoveWorldTag. |
AddWorldTagForDuration | Removes after N seconds. Re-adding refreshes the timer. |
AddWorldTagUntil | Removes 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:| Type | Base Class | Returns | Effect |
|---|---|---|---|
| Scorer | USECScorer | A multiplier (1.0 = no effect) | Folds into the action score. Above 1 favors, below 1 disfavors. |
| Gate | USECGate | true / false | A 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
| Class | Kind | Use |
|---|---|---|
| Attribute Scorer | Scorer | Scale by a GameplayAttribute through an FRangeEval curve. Optional normalize-by attribute (e.g. Mana / MaxMana). |
| Attribute Gate | Gate | Allow 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
- Create Asset: Right-click → Miscellaneous → Data Asset → ActionSet.
- Define Actions: Each entry needs an
ActionId,SelectionWeight,DistanceEval,AngleEval, and an execution mode. - 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
| Component | Location | Role |
|---|---|---|
ActionEvaluationComponent | Controller | Scoring, evaluation, execution |
SECActionSetComponent | Pawn | Resolution, replication, weapon/override management |
SECCombatControllerComponent | Controller | Orchestrates 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 flowIntegration Points
| System | How It Connects |
|---|---|
| Movement System | Provides distance/angle for scoring context |
| Combat Roles | Role changes trigger automatic ActionSet swaps |
| Threat Detection | Threat level feeds into FDecisionContext |
| Multiplayer | Action state replicates to clients via SECActionSetComponent |
Custom character classes:ActionEvaluationComponentresolves theAbilitySystemComponentfrom the possessed pawn viaIAbilitySystemInterfaceon possession. Pawns that do not implement this interface cause ability-based actions to silently fail. See Getting Started for setup details.