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 callExecuteReactionwith a knownReactionIdfrom your own graph. Both end in the same execution path.
Actions vs Reactions
| Actions | Reactions | |
|---|---|---|
| Trigger | Polled on a timer by the brain | Event-driven: a trigger rule on the Reaction Set, or a call from your Blueprint or C++ |
| Selection | Score-based (distance, angle, context) | By ID, or priority + weighted random inside a reaction category |
| Component | ActionEvaluationComponent | ReactionEvaluationComponent |
| Data asset | ActionSet | ReactionSet |
| 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.- Assign the asset: Set
DefaultReactionSetonEnemyAIConfig, or add role entries toRoleReactionSets(see Combat Roles). Or setDefaultReactionSeton the pawn component for zero-config testing. - Custom pawns: Add
SECReactionSetComponentmanually if not usingEnemyCharacterBase.
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:
| Priority | Source | Use |
|---|---|---|
| 1 (highest) | Runtime Override | SetRuntimeOverride(ReactionSet). Boss phase transitions. |
| 2 | Provided Set | A set pushed onto the pawn, which is how an equipped weapon's reactions arrive. |
| 3 | Config Role | Role-specific ReactionSet from EnemyAIConfig → RoleReactionSets. |
| 4 | Config Default | Fallback from EnemyAIConfig → DefaultReactionSet. |
| 5 | Component Default | SECReactionSetComponent → DefaultReactionSet on the pawn. |
| 6 | None | No 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.SECActionSetComponentuses the same resolution chain for action sets.
Swap Modes
When a reaction set swap runs while a reaction is already executing:
| Mode | Behavior |
|---|---|
ESECReactionSetSwapMode::Immediate | Cancel the current reaction and swap now (default). |
ESECReactionSetSwapMode::WaitForCompletion | Queue 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 resolutionReplicated 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:CanSenseStimulusreplaces the perception step. The default runs every filter in force and requires all of them to pass, skipping any the event bypasses by tag.SelectReactionCategoryreplaces 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)runsPassesReactionGateswithout executing. Pass the attacker asTargetOverrideso 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):
| Check | Blocks when |
|---|---|
SEC.Reaction.BlockReactions | The tag is on the AI's ASC (Gameplay Tags). Global stun or cutscene. |
| Another reaction executing | A 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()):PassesReactionGates.- Cancel the current action if Cancel Current Action is enabled on the spec.
- Apply Add Tags to the ASC.
- Pack
FSECExecutionContextinto the gameplay event payload. - Activate the ability (By Event or By Tag).
- On success: commit cooldown, watch for the ability to end, fire
OnReactionStarted. - 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
| Field | You set | System fills |
|---|---|---|
Target | Stimulus source (attacker, projectile owner) | n/a |
OptionalObject | Weapon, projectile, item | n/a |
Magnitude | Damage, charge level, intensity | n/a |
EventTag | Stimulus category | n/a |
ContextTags | Situational tags ("Backstab", "Airborne") | n/a |
TargetData | Spatial payload via USECTargetDataLibrary | n/a |
DistanceToTarget, DirectionToTarget | n/a | Hydrated by UGameplayAbilityBase on activation |
InstigatorTags, TargetTags | n/a | Queried 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:- Filter by category (optional).
- Hard gates per reaction (identical to preconditions you author).
- 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.
- Highest priority band only.
- 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
| System | How It Connects |
|---|---|
| Action System | Reactions cancel actions (bCancelCurrentAction) and can block new ones via SEC.Action.BlockActions in AddTags. Same resolution chain pattern. |
| Vitals | Health, Stamina, Vital Scorer, and Vital Gate all read the pawn's vitals live. |
| Combat Roles | Role changes sync ReactionSets via SECReactionSetComponent::SyncForCombatRole. |
| Attack Telegraphs | Where a stimulus comes from, how far it carries, and which pawns notice it. |
| Melee Trace | OnMeleeHitResponse on SECMeleeTraceComponent is a typical hook to fire or evaluate reactions after a hit. |
| Multiplayer | Reaction 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
| Component | Location | Role |
|---|---|---|
ReactionEvaluationComponent | Controller | Execution, optional selection, cooldowns |
SECReactionSetComponent | Pawn | Resolution, 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 whatOnReactionCompletedWithReasonreports.IsReactionExecuting()/GetCurrentReactionId(): Runtime state.OnReactionStarted/OnReactionCompleted: Controller-local delegates (with full context).OnReactionCompletedWithReason: same completion, plus why it ended.Completedfor a reaction that ran to the end,Interruptedfor one something cancelled,TimedOutfor 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
| Tag | Purpose |
|---|---|
SEC.Reaction.BlockReactions | Blocks all reactions when on the ASC |
SEC.Action.BlockActions | Blocks all actions when on the ASC |
SEC.State.ReactionActive | Convenience tag for reaction-to-reaction blocking |
SEC.State.Invulnerable | Starting 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.