Reaction System

Documentation Unreal Engine AI Reactions

Event-driven AI reactions: parry, dodge, counter. They respond to game events in real time.


Actions poll on a timer. Reactions answer an event.

When to Use This

  • AI that parries incoming attacks
  • Dodge rolls triggered by projectile detection
  • Counter-attacks after blocking
  • Any respond-to-this-stimulus behavior
Two routes reach a reaction. Author trigger rules on the Reaction Set and the enemy answers events for itself, or call ExecuteReaction with a known ReactionId from your own graph. Both end in the same execution path.

Actions vs Reactions

ActionsReactions
TriggerPolled on a timer by the brainEvent-driven: a trigger rule on the Reaction Set, or a call from your Blueprint or C++
SelectionScore-based (distance, angle, context)By ID, or priority + weighted random inside a reaction category
ComponentActionEvaluationComponentReactionEvaluationComponent
Data assetActionSetReactionSet
When to use"What should I do next?""Something happened: how do I respond?"

ReactionSet (Data Asset)

Create via Right-click → Miscellaneous → Data Asset → ReactionSet.
The asset holds two arrays: Reactions, what the AI can do, and Trigger Rules, which event gets which kind of answer.
Each entry in the Reactions array is an FReactionSpec. Defaults from the plugin header:
Identity
Enabled
Reaction ID
Parry
Reaction Category
None
Execution
Ability Class
None
Ability Timeout
0
Wait For Ability End
Activation Tag
None
Cancel Current Action
Tags
Add Tags
0 Gameplay Tags
Requires Tags
0 Gameplay Tags
Block Tags
0 Gameplay Tags
Scoring
Priority
0
Selection Weight
1
Scoring
0 Array elements
Action Interaction
Cancel Current Action
Assign Ability Class and the reaction is set up. SEC grants the ability and sends it the reaction's payload (the attacker, the direction, the magnitude) as a gameplay event, so an Activate Ability From Event graph receives it.
Reaction Category, Priority, and Selection Weight on the spec matter only when you use EvaluateBestReaction. For direct ExecuteReaction("Parry", …) calls, the ID alone selects the reaction.
Reaction abilities must inherit from UGameplayAbilityBase. It handles the end-event handshake that tells the system when the ability finishes. Without it, the AI waits until timeout.

Preconditions you author

These ReactionSet fields feed PassesReactionGates (the same check runs at selection and again at execute):
Identity
Enabled
Tags
Requires Tags
0 Gameplay Tags
Block Tags
0 Gameplay Tags
Cooldown
Cooldown Duration
5
Initial Cooldown
0
Randomization (%)
0.2
Interrupt Refund (%)
0
Spawn Cooldown Chance
0
Max Consecutive Uses
-1
Unlike actions, Requires Tags and Block Tags read only the AI's ASC, not target or world tags. Add Tags is not a gate: it is applied while the reaction runs (see Triggering Reactions).

Block actions via tags

Add SEC.Action.BlockActions to Add Tags to freeze offensive action selection while the reaction runs. Actions check that tag in PassesHardGates().


Assigning the ReactionSet

SECReactionSetComponent on the pawn resolves which ReactionSet an enemy uses. EnemyCharacterBase creates it automatically; EnemyControllerBase already has ReactionEvaluationComponent.
  1. Assign the asset: Set DefaultReactionSet on EnemyAIConfig, or add role entries to RoleReactionSets (see Combat Roles). Or set DefaultReactionSet on the pawn component for zero-config testing.
  2. Custom pawns: Add SECReactionSetComponent manually if not using EnemyCharacterBase.
SECCombatControllerComponent::SyncStateForCombatRole calls SECReactionSetComponent::SyncForCombatRole, which resolves the chain below and pushes the result to ReactionEvaluationComponent::SetReactionSet. That happens when the controller possesses the pawn, and again on every combat role assignment or change. SEC grants each reaction's Ability Class to the character as the set arrives, so an enemy standing idle can answer a stimulus before it has ever picked a combat role.

Quick path

Minimum to get one parry working after assign:
// On controller, after pawn is possessed and reaction set is synced:
FSECExecutionContext Context;
Context.Target = Attacker;
ReactionComp->ExecuteReaction("Parry", Context);
In Blueprint: get Reaction Evaluation Component on the AI controller, call Execute Reaction with the ReactionId from your ReactionSet and a filled Context struct.
The resolver walks this chain and returns the first match:
PrioritySourceUse
1 (highest)Runtime OverrideSetRuntimeOverride(ReactionSet). Boss phase transitions.
2Provided SetA set pushed onto the pawn, which is how an equipped weapon's reactions arrive.
3Config RoleRole-specific ReactionSet from EnemyAIConfig → RoleReactionSets.
4Config DefaultFallback from EnemyAIConfig → DefaultReactionSet.
5Component DefaultSECReactionSetComponent → DefaultReactionSet on the pawn.
6NoneNo ReactionSet. The AI cannot react.
Provided set and override changes take effect on the next sync. Fill in Weapon Reaction Set on a weapon Blueprint to give the enemy reactions that come with what it is holding, such as a parry it can only do while carrying a sword. See Weapons.
SECActionSetComponent uses the same resolution chain for action sets.

Swap Modes

When a reaction set swap runs while a reaction is already executing:
ModeBehavior
ESECReactionSetSwapMode::ImmediateCancel the current reaction and swap now (default).
ESECReactionSetSwapMode::WaitForCompletionQueue the swap; it applies when the current reaction completes.
Pass the mode to SetRuntimeOverride, ClearRuntimeOverride, or SyncForCombatRole. There is no persistent SwapMode property on the component.
AdvancedPer-role reactions and replicated state
Weapon Reaction Set answers the same set for every role. To vary it, override Get Weapon Reaction Set For Role on the weapon and branch on the incoming role tag. A weapon deriving from something other than SEC Weapon Base adds the SEC Weapon Reaction Set Provider interface in Class Settings.
Anything can drive the slot directly, which is what the equipment component does for a weapon:
Pawn->ReactionSetComponent->SetProvidedReactionSet(ReactionSet);
Pawn->ReactionSetComponent->SetProvidedReactionSet(nullptr);  // fall back to config resolution
Replicated state on SECReactionSetComponent (client UI, VFX, debug): CurrentReactionId, bReactionExecuting, ActiveReactionSet, OnReactionExecutionStarted / OnReactionExecutionCompleted, OnReactionSetChanged, cooldown delegates, and GetRemainingCooldown / IsReactionOnCooldown / GetAllActiveCooldowns.

Triggering Reactions

A reaction starts one of two ways. Hand the enemy a stimulus and let the trigger rules on its Reaction Set work out the answer, or call the component yourself when your game already knows which reaction fits.

Trigger rules on the Reaction Set

Something reports what happened to the enemy's ReactionEvaluationComponent through Receive Stimulus: an attack winding up, a hit that landed, a spell going off. Attack Telegraphs covers where one comes from and how far it carries.
Trigger Rules on the Reaction Set turn that into an answer. The component asks them in Rule Priority order, highest first, and the first rule that fits and yields a reaction wins, so a specific rule takes a higher priority than a general one:
Trigger
Enabled
Rule ID
ParryFrontal
On Stimulus
SEC.Stimulus.AttackTelegraphed
Require Context Tags
0 Gameplay Tags
Block Context Tags
1 Gameplay Tag
SEC.Attack.Unblockable
Conditions
1 Array element
Index [ 0 ]
Incoming Angle Gate
Incoming Angle Gate
Half Angle Degrees
60.0
Pass Without Direction
Invert
Answers
Reaction Links
2 Array elements
Reaction Category
SEC.Reaction.Defensive
Rule Priority
20
Chance
0.60
Min Interval
1.5
A rule says which reactions answer it in one of two ways. Reaction Links names them one at a time, each with a Weight Multiplier saying how much this rule favours that answer over the others it names. Reaction Category names a group instead and lets the priority and scoring on each reaction pick inside it.
One rule covers a parry and a dodge either way: name both, or give both the same category, then put an Incoming Angle Gate and the higher Priority on the parry. The parry answers a blow arriving from the front, and the gate refuses it from the side, which leaves the dodge to take those.
A rule filling in both answers only with a reaction that appears in its links and carries that category. That narrows what a rule can do and never widens it, so a link pointing outside the category answers nothing. The Reaction Set canvas flags that case on the card.
On Stimulus matches down the tag tree, so a rule on SEC.Stimulus.AttackTelegraphed also answers SEC.Stimulus.AttackTelegraphed.Heavy. Require Context Tags and Block Context Tags narrow it further: a parry rule that blocks on SEC.Attack.Unblockable steps aside and lets the dodge rule below it answer. Chance and Min Interval stop an enemy parrying every swing of a fast combo, and Conditions holds gates that all have to pass for the rule to answer, for what the tags cannot express. A rule refused by one of its conditions hands the stimulus to the rule below it, which is how a blow arriving from behind reaches a stagger rule instead of a guard that cannot cover it.
A rule ends in the same Execute Reaction call as the direct route below, so the execute-time blocks and the execution order hold either way. A set with no rules answers nothing by itself, which leaves the direct call as the only way in.
Rules live on the Reaction Set, so an enemy that swaps to an Elite set gets that set's triggers with it. Receive Stimulus runs on the server.
Noticing it first. An enemy answers only what it notices, and perception filters decide that: Stimulus Filters on the EnemyAIConfig, or Fallback Stimulus Filters on ReactionEvaluationComponent for a pawn running without a config. A config listing any filter replaces the fallback list outright. With neither, the enemy notices everything that reaches it, including a blow from behind. See Attack Telegraphs.
Answering by hand instead. On Stimulus Received fires for everything the pawn notices, before the rules look at it. Call Execute Reaction from that binding and the rules stay out of it, because a rule fires only when nothing already answered. That is the opt-out for a project that routes events its own way.
AdvancedReplacing the perception or the routing step
Two BlueprintNativeEvent hooks each take over one step, in a Blueprint or C++ subclass of ReactionEvaluationComponent:
  • CanSenseStimulus replaces the perception step. The default runs every filter in force and requires all of them to pass, skipping any the event bypasses by tag.
  • SelectReactionCategory replaces the rule walk. The default returns the category of the first rule that fits, which is empty for a rule naming its reactions one at a time. Either way the walk has already chosen a reaction, so an empty tag from the default does not mean nothing answered. Return an empty tag from an override for no answer.
EvaluateBestReactionForStimulus(CategoryTag, Stimulus, TargetOverride) is the selection the rules run: the same choice EvaluateBestReaction makes, with the stimulus carried down so gates and scorers can read which way the blow is arriving.
EvaluateBestReactionAmongLinks(Links, CategoryTag, Stimulus, TargetOverride) is that same selection narrowed to a named few, with each link's Weight Multiplier applied. Pass a category as well to require both.

Firing a reaction yourself

Your game logic detects the event and calls the controller's ReactionEvaluationComponent. The usual path is a direct ID call: you already know which reaction fits (parry on block input, dodge on projectile warn, etc.).
FSECExecutionContext Context;
Context.Target = DamageInstigator;
Context.Magnitude = DamageAmount;
Context.EventTag = YourGame.Stimulus.MeleeHit;
Context.TargetData = USECTargetDataLibrary::MakeTargetDataFromDirection(HitDirection);
 
if (ReactionComp->CanReact("Parry", DamageInstigator))
{
    ReactionComp->ExecuteReaction("Parry", Context);
}
  • CanReact(ReactionId, TargetOverride) runs PassesReactionGates without executing. Pass the attacker as TargetOverride so Gates in the Scoring array (distance, attributes, etc.) evaluate against it. Scorers do not run here; they only affect optional selection.
  • ExecuteReaction(ReactionId, Context) runs the same gates again, then starts the ability, manages tags and cooldowns, and fires delegates.
Extra blocks at execute time (not ReactionSet fields):
CheckBlocks when
SEC.Reaction.BlockReactionsThe tag is on the AI's ASC (Gameplay Tags). Global stun or cutscene.
Another reaction executingA reaction is already active on this controller.
Only one reaction runs at a time. If a reaction is already executing, ExecuteReaction returns false.

Execution order

Inside ExecuteReaction (same gates as above, re-checked against Context.GetTarget()):
  1. PassesReactionGates.
  2. Cancel the current action if Cancel Current Action is enabled on the spec.
  3. Apply Add Tags to the ASC.
  4. Pack FSECExecutionContext into the gameplay event payload.
  5. Activate the ability (By Event or By Tag).
  6. On success: commit cooldown, watch for the ability to end, fire OnReactionStarted.
  7. When the ability ends, or the timeout is reached: remove Add Tags, fire OnReactionCompleted.
A failed activation tears down immediately: tags removed, no cooldown committed. Cooldown commits only after activation succeeds; a CanActivateAbility refusal leaves the reaction off cooldown.

Reaction-to-reaction blocking

While a reaction runs, its Add Tags sit on the ASC. Put the same tag in another reaction's Block Tags to prevent overlap. Both reactions below use SEC.State.ReactionActive:

Parry reaction

Adds SEC.State.ReactionActive while it runs. No block tags.

Tags
Add Tags
1 Gameplay Tag
SEC.State.ReactionActive
Requires Tags
0 Gameplay Tags
Block Tags
0 Gameplay Tags

Dodge reaction

Same add tag, plus Block Tags so it cannot fire during Parry.

Tags
Add Tags
1 Gameplay Tag
SEC.State.ReactionActive
Requires Tags
0 Gameplay Tags
Block Tags
1 Gameplay Tag
SEC.State.ReactionActive
When Parry is running, the ASC holds SEC.State.ReactionActive, so Dodge fails PassesReactionGates until Parry ends and removes the tag.

Context payload

Both actions and reactions share FSECExecutionContext. Fill what you know before ExecuteReaction; the ability reads it through GetActionContext() on UGameplayAbilityBase.
AdvancedFSECExecutionContext fields and TargetData helpers
FieldYou setSystem fills
TargetStimulus source (attacker, projectile owner)n/a
OptionalObjectWeapon, projectile, itemn/a
MagnitudeDamage, charge level, intensityn/a
EventTagStimulus categoryn/a
ContextTagsSituational tags ("Backstab", "Airborne")n/a
TargetDataSpatial payload via USECTargetDataLibraryn/a
DistanceToTarget, DirectionToTargetn/aHydrated by UGameplayAbilityBase on activation
InstigatorTags, TargetTagsn/aQueried from ASCs at activation
Context.TargetData = USECTargetDataLibrary::MakeTargetDataFromDirection(HitDirection);
See Gate Before Activation for payload gating in CanActivateAbility.

Choosing a Reaction (Optional)

When several reactions could fit the same moment (multiple parry variants, dodge left vs right), call EvaluateBestReaction instead of picking the ID yourself. The system filters by category, runs gates, scales weights with scorers, picks the highest Priority band, then weighted-random among survivors.
Reaction Evaluation Flow
Content>Plugins>SoulslikeEnemyCombat>Components
Waiting for stimulus…
Stimulus (Your Code)
OnDamageReceived, OnSenseDetected…
EvaluateBestReaction(Category)
Gates → Scorers → Priority → Weighted random
Cancel Current Action
StopCurrentAction() if bCancelCurrentAction
ExecuteReaction(Id, Context)
AddTags → Pack context → Activate ability
OnReactionCompleted
Remove AddTags → Fire delegate → Reset
Reactions are event-driven — your code decides when, the system decides what. Every 3rd cycle shows a blocked reaction.
FChosenReaction Chosen = ReactionComp->EvaluateBestReaction(
    YourGame.Reaction.Defensive,
    DamageInstigator);
if (Chosen.IsValid())
{
    ReactionComp->ExecuteReaction(Chosen.ReactionId, Context);
}
Leave the category tag empty to consider every reaction in the set.

Selection pipeline

Uses the same PassesReactionGates as execute, then adds selection-only steps:
  1. Filter by category (optional).
  2. Hard gates per reaction (identical to preconditions you author).
  3. Scorers scale weight: each survivor's Selection Weight is multiplied by its Scorers. Weight zero or below is a hard veto, even at high priority.
  4. Highest priority band only.
  5. Weighted random among survivors in that band.
Returns FChosenReaction. Check IsValid() before executing.

Priority and weight

These fields apply only on this path, not on direct ExecuteReaction by ID:
Scoring
Priority
0
Selection Weight
1
Scoring
0 Array elements
Add Gates under the Scoring array to hard-veto. Add Scorers to scale Selection Weight by distance, attributes, and more.

Scorers on reactions

Built-in Distance, Angle, Health, Speed, and Vital Scorers read live values from the AI pawn and TargetOverride (or focus actor). Pass the attacker to EvaluateBestReaction(CategoryTag, Attacker).
Distance Scorer Range at the melee-preset default:
Range
Min Value
0
Optimal Min
100
Optimal Max
250
Max Value
500
Exponent
2
Clamp To Zero
Stamina Gate reads the pawn's stamina vital live, falling back to the same 100 default as actions when the pawn carries no vitals component or no row for the tag. Vital Scorer and Vital Gate read any named vital the same way. Attribute Scorers and Gates read live GameplayAttributes. Same classes as actions: see Scorers & Gates. Custom scorers stay stateless; SeededRandom is fixed on the reaction path (Seed stays 0).
Four gates read the stimulus that set the reaction off rather than the pawn, so each one also works in a trigger rule's Conditions. An action carries no stimulus, so one added to an action has nothing to judge and passes.
Incoming Angle Gate blocks the reaction unless the blow arrives inside an arc in front of the pawn, which keeps a parry from answering a swing coming from behind. Half Angle Degrees is half the arc's width, 60 by default, so anything up to 60 degrees off the front passes and 180 turns the gate off. Pass Without Direction is on by default, letting a reaction fired by hand through when there is no direction to measure; untick it to refuse anything the gate cannot judge. The direction comes from whatever set the reaction off.
Time To Impact Gate blocks the reaction unless there is time left to answer before the blow lands. Min Seconds set to the reaction's own start-up stops a parry answering a swing already too close to beat. A hit that already landed reports no time left, so any Min Seconds above 0 refuses one, which is what keeps a parry rule off a damage report. Max Seconds holds a rule back until the swing is close, and 0 sets no upper bound.
Stimulus Magnitude Gate blocks the reaction unless the blow is worth answering, measured in whatever the producer used: incoming damage for a hit that landed, a 0 to 1 charge level for a wind-up. Min Magnitude and Max Magnitude bound it, and 0 on either sets no bound. A ceiling on one rule and a floor on the next splits a flinch from a stagger.
Source Distance Gate blocks the reaction unless whatever caused the stimulus is within reach, so a counter does not swing at an archer across the room. Min Distance and Max Distance bound it in centimetres, and 0 on either sets no bound. It measures to the source actor, or to the stimulus origin when nothing named a source. Pass Without Source is on by default, letting through what it cannot measure.

Debug Tools

ReactionEvalComp->bDebugLogReactions = true;
Console variable SEC.Debug.LogReactions 1 enables the same logging globally.

Integration Points

SystemHow It Connects
Action SystemReactions cancel actions (bCancelCurrentAction) and can block new ones via SEC.Action.BlockActions in AddTags. Same resolution chain pattern.
VitalsHealth, Stamina, Vital Scorer, and Vital Gate all read the pawn's vitals live.
Combat RolesRole changes sync ReactionSets via SECReactionSetComponent::SyncForCombatRole.
Attack TelegraphsWhere a stimulus comes from, how far it carries, and which pawns notice it.
Melee TraceOnMeleeHitResponse on SECMeleeTraceComponent is a typical hook to fire or evaluate reactions after a hit.
MultiplayerReaction execution state and cooldowns replicate through SECReactionSetComponent.
Custom character classes: ReactionEvaluationComponent resolves the ability system from the possessed pawn, through IAbilitySystemInterface when the pawn implements it and by component search otherwise, same as actions. A pawn whose ability system never had its actor info initialised fails reaction abilities silently; EnemyCharacterBase handles that for you.

Key API

AdvancedComponent and delegate reference
ComponentLocationRole
ReactionEvaluationComponentControllerExecution, optional selection, cooldowns
SECReactionSetComponentPawnResolution, replication, overrides
ReactionEvaluationComponent (Controller)
  • ExecuteReaction(ReactionId, Context): Fire a reaction (primary path).
  • ReceiveStimulus(Stimulus): Hand the AI something that happened and let its perception filters, trigger rules and scoring answer it. Server only.
  • CanReact(ReactionId, TargetOverride): Gate check without executing.
  • EvaluateBestReaction(CategoryTag, TargetOverride): Optional selection when multiple reactions compete.
  • EvaluateBestReactionForStimulus(CategoryTag, Stimulus, TargetOverride): The same selection with the stimulus carried down to gates and scorers.
  • CanSenseStimulus(Stimulus) / SelectReactionCategory(Stimulus): Override points for the perception step and the rule walk.
  • OnStimulusReceived: Everything the pawn notices, before the trigger rules see it.
  • StopCurrentReaction(bSuccess, Reason): Manually end the running reaction. Reason defaults to Cancelled and is what OnReactionCompletedWithReason reports.
  • IsReactionExecuting() / GetCurrentReactionId(): Runtime state.
  • OnReactionStarted / OnReactionCompleted: Controller-local delegates (with full context).
  • OnReactionCompletedWithReason: same completion, plus why it ended. Completed for a reaction that ran to the end, Interrupted for one something cancelled, TimedOut for one whose ability never ended. Bind this when a timeout has to be told apart from a clean finish.
SECReactionSetComponent (Pawn)
  • SetRuntimeOverride(ReactionSet, SwapMode) / ClearRuntimeOverride(SwapMode): Boss phase override.
  • SyncForCombatRole(RoleTag, SwapMode): Apply resolved set for a combat role.
  • GetReactionSetForRole(RoleTag): Query the resolution chain without applying.
  • Replicated execution and cooldown delegates; see Assigning the ReactionSet.

Gameplay Tags

TagPurpose
SEC.Reaction.BlockReactionsBlocks all reactions when on the ASC
SEC.Action.BlockActionsBlocks all actions when on the ASC
SEC.State.ReactionActiveConvenience tag for reaction-to-reaction blocking
SEC.State.InvulnerableStarting tag for i-frames, held by a montage window; your Handle Incoming Damage reads it
ReactionCategory tags are project-defined. Filter with them in EvaluateBestReaction only when using optional selection.