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 fire from events you trigger.
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
The system does not decide when to react: you do. Most setups callExecuteReactionwith a knownReactionId. UseEvaluateBestReactiononly when several reactions in the same category should compete.
Actions vs Reactions
| Actions | Reactions | |
|---|---|---|
| Trigger | Polled on a timer by the brain | Event-driven from your Blueprint or C++ |
| Selection | Score-based (distance, angle, context) | Usually by ID; optional priority + weighted random |
| 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.
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
Activation Mode
By Event
Ability Tag
None
Ability End Tag
None
Ability Timeout
0
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 to a
UGameplayAbilityBase subclass: the editor auto-fills Ability Tag and Ability End Tag. Activation Mode defaults to By Event on reactions (actions default to By Tag).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
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.
On every combat role assignment or change (including the first assignment after a combat target is registered),
SECCombatControllerComponent::SyncStateForCombatRole calls SECReactionSetComponent::SyncForCombatRole, which resolves the chain below and pushes the result to ReactionEvaluationComponent::SetReactionSet. Until that sync runs, the controller has no active reactions even if the config asset is set.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 | Equipped Weapon | Weapon on the sibling SECActionSetComponent implementing ISECWeaponReactionSetProvider. |
| 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. |
Weapon and override changes take effect on the next sync. Set the equipped weapon on
SECActionSetComponent (SetEquippedWeapon); the reaction component reads it from there.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.AdvancedWeapon reaction set provider and replicated state
Implement
ISECWeaponReactionSetProvider on your weapon actor and override GetWeaponReactionSetForRole(RoleTag).Pawn->ActionSetComponent->SetEquippedWeapon(WeaponActor);
Pawn->ActionSetComponent->SetEquippedWeapon(nullptr); // revert to config resolutionReplicated state on
SECReactionSetComponent (client UI, VFX, debug): CurrentReactionId, bReactionExecuting, ActiveReactionSet, OnReactionExecutionStarted / OnReactionExecutionCompleted, OnReactionSetChanged, cooldown delegates, and GetRemainingCooldown / IsReactionOnCooldown / GetAllActiveCooldowns.Triggering Reactions
Your game logic detects the stimulus 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, listen for Ability End Tag (or timeout), fire
OnReactionStarted. - On end tag or timeout: 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, and Speed 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 default stamina snapshot (
100) unless you use an Attribute Gate. 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).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. |
| Combat Roles | Role changes sync ReactionSets via SECReactionSetComponent::SyncForCombatRole. |
| 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 ASC from the possessed pawn via IAbilitySystemInterface, same as actions. Pawns without it cause reaction abilities to fail silently.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).CanReact(ReactionId, TargetOverride)- Gate check without executing.EvaluateBestReaction(CategoryTag, TargetOverride)- Optional selection when multiple reactions compete.StopCurrentReaction(bSuccess)- Manually end the running reaction.IsReactionExecuting()/GetCurrentReactionId()- Runtime state.OnReactionStarted/OnReactionCompleted- Controller-local delegates (with full context).
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.End | Common default for AbilityEndTag |
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 |
ReactionCategory tags are project-defined. Filter with them in EvaluateBestReaction only when using optional selection.