Combat Roles
Documentation Unreal Engine AI Combat Roles
Coordinate multiple enemies with role slots, fitness scoring, and per-role action/movement/reaction sets.
Slot limits cap how many enemies attack at once. Fitness scoring fills open slots. A role change swaps ActionSet, ReactionSet, and movement profile.
When to Use This
- You run several enemies against one player and want staggered pressure.
- You want Soulslike-style turn-taking without scripting each swap by hand.
- One controller class should drive different archetypes (Attacker up front, Waiter at range) via tags.
- You tune difficulty by raising or lowering Attacker caps per target pool.
Set
bAutoRegisterForCombatRoles to false on bosses, ambient critters, or any AI that should skip slot coordination.Built-in Roles
Roles use
SEC.Role.* gameplay tags. Define custom tags under SEC.Role in Project Settings → Gameplay Tags, then add them to Plugins → Soulslike Enemy Combat → Additional Roles.| Role | Tag | Typical use |
|---|---|---|
| None | SEC.Role.None | No coordinated role; assigned when no slot fits. |
| Attacker | SEC.Role.Attacker | Primary melee pressure. |
| Waiter | SEC.Role.Waiter | Holds distance, waits for a slot. |
| Flanker | SEC.Role.Flanker | Circles for angle attacks. |
| Supporter | SEC.Role.Supporter | Ranged or support from the back. |
| Elite | SEC.Role.Elite | Boss-tier enemy; unlimited slots by default. |
The subsystem reassigns roles on
RoleReassignmentInterval. Each tick it scores (AI, role) pairs per combat target pool, applies slot limits, and notifies controllers on change.Viewport
LVL_SEC_Showcase>Combat Roles
Player
Attacker
Waiter
Waiter
Flanker
Combat Slots
Attackers:1 / 1
(Others must wait)
Enemies dynamically swap roles based on slot availability and priority.
Project Settings: Slots and Timing
Open Project Settings → Plugins → Soulslike Enemy Combat.
Role limits
Role Limits is an array of { Role, Limit }. Any role omitted from the array has unlimited slots.Defaults from
USoulslikeEnemyCombatSettings:| Role | Default limit |
|---|---|
| Attacker | 3 |
| Flanker | 2 |
| Supporter | 2 |
| Waiter | unlimited |
| Elite | unlimited |
| None | unlimited |
Call
SetRoleLimitOverride at runtime to change a cap (-1 = unlimited, 0 = disable the role). Full API: Runtime control API collapsible.Reassignment timing
Timing
Role Reassignment Interval
8
Min Time In Role
8
Re-evaluate Targets On Reassignment
RoleReassignmentInterval(default8s): interval forEvaluateRoleDistribution. Set0and drive reassignment withForceReassignment.MinTimeInRole(default8s): hysteresis window. If the AI is still in its current role andTimeInCurrentRoleis below this value, the evaluator adds+0.3to that role's adjusted fitness.bReevaluateTargetsOnReassignment: with two or more registered combat targets, re-run each AI'sTargetSelectorat the start of each timer tick, before role evaluation. Ignored with a single target. Per-enemy opt-out:bIgnoreTargetRedistributiononEnemyAIConfig.
EnemyAIConfig Setup
Create Right-click → Miscellaneous → Data Asset → EnemyAIConfig. Assign it on the pawn via
IEnemyAIConfigProvider, or set Default AI Config on SECCombatControllerComponent.Combat role fields
Combat Role
Auto-Register for Combat Roles
Allowed Roles (empty = any)
0 Gameplay Tags
Priority
0
Preferred Role
None
Fitness Evaluators
0 Array elements
Target Selector
None
Ignore Target Redistribution
| Field | Notes |
|---|---|
| Auto-Register for Combat Roles | On possess, SECCombatControllerComponent calls RegisterCombatant. The config flag overrides the component default. |
| Allowed Roles | Empty allows any built-in or custom role. Restrict an archer to Waiter and Supporter. |
| Priority | Tiebreaker on equal adjusted fitness. Grunt 0, elite 50, mini-boss 100. |
| Preferred Role | Adds +0.05 to adjusted fitness while scoring that role tag. |
| Fitness Evaluators | Instanced URoleEvaluator objects. Empty list uses distance-only fallback (closer = higher score, 2000 cm cap). |
| Target Selector | Picks a registered combat target when several exist. Falls back to project Default Target Selector. |
| Ignore Target Redistribution | Skips periodic target re-evaluation on this AI. Use for bosses pinned to one player. |
Map roles to behavior assets
On role change,
SECCombatControllerComponent::SyncStateForCombatRole resolves and applies:| Asset | Config section | Managed by |
|---|---|---|
| ActionSet | RoleActionSets / DefaultActionSet | bManageActionSetsAutomatically |
| ReactionSet | RoleReactionSets / DefaultReactionSet | bManageReactionSetsAutomatically |
| Movement profile | RoleMovementProfiles / DefaultMovementProfile | bManageMovementProfilesAutomatically |
Unlisted roles use the default asset for that category. Equipped weapon overrides beat config role entries (same order as Action and Reaction resolution).
Register a Combat Target
RegisterCombatant runs on possess. AssignRoleToNewCombatant does nothing until the combatant has an AssignedTarget. Periodic evaluation skips combatants with no target.On the server, when the player enters combat:
UAICombatRoleSubsystem* Roles = GetWorld()->GetSubsystem<UAICombatRoleSubsystem>();
Roles->RegisterCombatTarget(PlayerPawn, /*bAutoAssignUnassigned=*/true);RegisterCombatTarget: adds the actor to the target pool.bAutoAssignUnassigned: assigns unassigned combatants to this target (skips team-friendly pairs), then callsForceReassignmentandEvaluateRoleDistribution.bReevaluateExisting: callsReevaluateAllTargets()so assigned combatants re-runTargetSelector(honorsbIgnoreTargetRedistribution).
SetCombatTarget: deprecated; callsRegisterCombatTarget(NewTarget, true)with nobReevaluateExisting.AddCombatTarget: registers the target only. UseRegisterCombatTargetunless you own assignment yourself.
Each target pool enforces role limits independently (two players can each field three Attackers). See Multi-target control for the rest of the API.
Runtime Flow
- On possess,
SECCombatControllerComponentresolvesEnemyAIConfigfrom the pawn'sIEnemyAIConfigProvider, elseDefaultAIConfig. - With auto-register enabled, it calls
UAICombatRoleSubsystem::RegisterCombatant(AAIControllerrequired). - The combatant receives a target from
InitialTarget(runtime-only),TargetSelector, or the first target registered viaRegisterCombatTarget. - With a target set,
AssignRoleToNewCombatantslots the newcomer without reshuffling the whole pool. - Each
RoleReassignmentIntervaltick,EvaluateRoleDistributiongroups by target, scores (AI, role) pairs, sorts by adjusted fitness then Priority, and greedily fills slots. NotifyRoleChangecallsSECCombatControllerComponent::HandleCombatRoleAssignedwhen present, replicates toSECCombatRoleComponent, and runsSyncStateForCombatRole.
No open slot in the greedy pass →
SEC.Role.None. Before the first assignment, CurrentRole may be FGameplayTag::EmptyTag.When the Role Changes
EnemyControllerBase ships with SECCombatControllerComponent. On role change:HandleCombatRoleAssignedwrites the tag toSECCombatRoleComponenton the pawn (replicated).SyncStateForCombatRolepushes the matching action set, reaction set, and movement profile.
Add
SECCombatControllerComponent to custom AAIController classes, or call USECCombatRoleSyncLibrary yourself. Without that, NotifyRoleChange still fires on the global delegate but does not swap assets.AlternativeManual sync for custom controllers
USECCombatRoleSyncLibrary matches SyncStateForCombatRole:| Function | Syncs |
|---|---|
SyncAllForCombatRole(Controller, Pawn, RoleTag, AIConfig) | Action + reaction + movement |
SyncActionSetForRole(Pawn, RoleTag) | SECActionSetComponent only |
SyncReactionSetForRole(Pawn, RoleTag) | SECReactionSetComponent only |
SyncMovementProfileForRole(Controller, RoleTag, AIConfig) | MovementEvaluatorComponent only |
Bind
UAICombatRoleSubsystem::OnCombatRoleChanged or SECCombatControllerComponent::OnCombatRoleChanged, then sync with the new tag and resolved EnemyAIConfig.Client Role Visibility
UAICombatRoleSubsystem runs on the server. Controllers do not exist on clients. SECCombatRoleComponent on the pawn replicates the role tag for UI, VFX, or debug overlays.EnemyCharacterBaseadds it by default.- The server sets it in
HandleCombatRoleAssignedviaSetCombatRole. - Clients read
GetCombatRole()or bindOnCombatRoleChanged. SetCombatRoleon clients returns without doing work.
Bosses and Opt-Out AI
| Goal | Setting / API |
|---|---|
| Skip slot coordination | bAutoRegisterForCombatRoles = false on EnemyAIConfig. |
| Pin to one player while targets shuffle | bIgnoreTargetRedistribution = true (skips ReevaluateAllTargets and periodic target re-evaluation). |
| Script a fixed role | ForceAssignRole(Controller, Role, bLockRole = true). |
| Local role tag without consuming a slot | ApplyLocalCombatRole on SECCombatControllerComponent (logs a warning if the AI stays registered). |
Unregistered AI can wire ActionSets and movement profiles by hand. They do not consume role slots.
Integration
| System | Link |
|---|---|
| Action System | RoleActionSets swap on role change. |
| Reaction System | RoleReactionSets swap on role change. |
| Movement System | RoleMovementProfiles swap on role change. |
| Targeting System | TargetSelector picks the combat target pool. |
| Multiplayer | Role tag replicates via SECCombatRoleComponent. |
AdvancedBuilt-in fitness evaluators
Add instanced evaluators under Role Settings → Fitness Evaluators on
EnemyAIConfig.Distance Evaluator (UDistanceRoleEvaluator)
Scores distance to the combat target. The C++ constructor sets
Role Influence Weights to { SEC.Role.Attacker: 1.0 }:Affected Roles
Role Influence Weights
SEC.Role.Attacker → 1.0
Influence On Unlisted Roles
0
Scoring
Score Mode
Higher Score = Better Fit
Distance Settings
Ideal Distance
0
Effective Range
2000
Closer enemies as Attackers: Ideal Distance
0, Effective Range 2000, Score Mode Higher Score = Better Fit, Role Influence Weights { Attacker: 1.0 }.Far enemies as Waiters: same distance fields, Score Mode Lower Score = Better Fit, weights
{ Waiter: 1.0 }.Cooldown Evaluator (UCooldownRoleEvaluator)
Scores cooldown load on the active ActionSet ("fatigue"):
Affected Roles
Role Influence Weights
0 Map elements
Influence On Unlisted Roles
0
Scoring
Score Mode
Higher Score = Better Fit
Cooldown Settings
Current Role Penalty
0.5
Returns
0.5 when CurrentRole is absent from Role Influence Weights. With a listed current role, a high cooldown ratio lowers the score; Current Role Penalty applies when scoring "stay in current role" while abilities are on cooldown.AdvancedCustom URoleEvaluator subclass
URoleEvaluator is Abstract, Blueprintable, EditInlineNew. Override EvaluateFitness (raw 0.0–1.0; the subsystem applies weights and Score Mode through GetFinalScore).FRoleEvaluationContext fields:| Field | Use |
|---|---|
Controller | Pawn, components, GetCombatantTarget for the assigned target. |
Role | Role tag under evaluation. |
CurrentRole | Role tag currently assigned. |
TimeInCurrentRole | Seconds in CurrentRole. |
The subsystem combines evaluators with a weighted average (per-evaluator influence for each role tag).
AdvancedRuntime control API
Subsystem:
UAICombatRoleSubsystem (world subsystem).Configuration
| Function | Purpose |
|---|---|
SetReassignmentInterval | Seconds between auto evaluations (0 = off). |
SetMinTimeInRole | Hysteresis window. |
SetReevaluateTargetsOnReassignment | Target re-pick before role eval (multi-target). |
SetRoleLimitOverride / ClearRoleLimitOverride / ClearAllRoleLimitOverrides | Runtime slot caps. |
UpdateConfig(FAICombatRoleConfig, bLimitAttritionEnabled, bForceReevaluation) | Bulk update; attrition mode keeps existing holders when limits shrink. |
ResetToDefaultSettings | Clear runtime overrides. |
Manual assignment
| Function | Purpose |
|---|---|
ForceAssignRole(Controller, Role, bLockRole) | Bypass evaluation; optional lock. |
UnlockRole | Re-enable evaluation for a locked combatant. |
ForceReassignment / ForceReassignmentForTarget | Immediate re-eval. |
PauseReassignment / ResumeReassignment | Freeze role changes (cutscenes). |
StartReassignmentTimer / StopReassignmentTimer | Timer control when interval is 0. |
Query
| Function | Purpose |
|---|---|
GetRole / GetRoleCount / GetCombatantsWithRole | Current assignments. |
IsRegistered / IsRoleLocked / GetAllCombatants | Pool membership. |
// Enrage: allow three Attackers, then re-evaluate immediately
UAICombatRoleSubsystem* RoleSys = GetWorld()->GetSubsystem<UAICombatRoleSubsystem>();
RoleSys->SetRoleLimitOverride(
USoulslikeEnemyCombatSettings::GetRoleAttacker(), 3);
RoleSys->ForceReassignment();AdvancedMulti-target control
| Function | Purpose |
|---|---|
RegisterCombatTarget / UnregisterCombatTarget / GetAllCombatTargets | Target registry. |
AssignCombatantToTarget(Controller, Target, bTriggerRoleEvaluation) | Move AI to a target pool (nullptr = unassigned). |
GetCombatantTarget / GetCombatantsForTarget | Query assignments. |
GetRoleCountForTarget / GetCombatantsWithRoleForTarget | Per-target slot usage. |
ReassignCombatantTarget / ReevaluateAllTargets | Re-run TargetSelector. |
TransferCombatantsToTarget / BalanceCombatantsAcrossTargets | Bulk redistribution. |
SetPrimaryTargetAndAssignAll | Assign all combatants to one target (boss/VIP). |
SelectTargetFor(Controller, CandidateTargets) | Custom candidate list + configured selector. |
Built-in
UTargetSelector subclasses: UFirstTargetSelector, UClosestTargetSelector, URandomTargetSelector, UBalancedTargetSelector, UAwarenessFilteredTargetSelector (perception-gated aggro).AdvancedSubsystem delegates
| Delegate | Fires when |
|---|---|
OnCombatRoleChanged | Any role change (global). |
OnCombatRoleChangedForTarget | Role change with target context. |
OnCombatantRegistered / OnCombatantUnregistered | Combatant joins or leaves the pool. |
OnCombatTargetRegistered / OnCombatTargetUnregistered | Target added or removed. |
OnCombatTargetChanged | SetPrimaryTargetAndAssignAll and similar primary-target handoff. |
OnCombatantsOrphaned | Target lost; orphaned controllers reset to SEC.Role.None. Reassign in this handler. |