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.
RoleTagTypical use
NoneSEC.Role.NoneNo coordinated role; assigned when no slot fits.
AttackerSEC.Role.AttackerPrimary melee pressure.
WaiterSEC.Role.WaiterHolds distance, waits for a slot.
FlankerSEC.Role.FlankerCircles for angle attacks.
SupporterSEC.Role.SupporterRanged or support from the back.
EliteSEC.Role.EliteBoss-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:
RoleDefault limit
Attacker3
Flanker2
Supporter2
Waiterunlimited
Eliteunlimited
Noneunlimited
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 (default 8 s): interval for EvaluateRoleDistribution. Set 0 and drive reassignment with ForceReassignment.
  • MinTimeInRole (default 8 s): hysteresis window. If the AI is still in its current role and TimeInCurrentRole is below this value, the evaluator adds +0.3 to that role's adjusted fitness.
  • bReevaluateTargetsOnReassignment: with two or more registered combat targets, re-run each AI's TargetSelector at the start of each timer tick, before role evaluation. Ignored with a single target. Per-enemy opt-out: bIgnoreTargetRedistribution on EnemyAIConfig.

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
FieldNotes
Auto-Register for Combat RolesOn possess, SECCombatControllerComponent calls RegisterCombatant. The config flag overrides the component default.
Allowed RolesEmpty allows any built-in or custom role. Restrict an archer to Waiter and Supporter.
PriorityTiebreaker on equal adjusted fitness. Grunt 0, elite 50, mini-boss 100.
Preferred RoleAdds +0.05 to adjusted fitness while scoring that role tag.
Fitness EvaluatorsInstanced URoleEvaluator objects. Empty list uses distance-only fallback (closer = higher score, 2000 cm cap).
Target SelectorPicks a registered combat target when several exist. Falls back to project Default Target Selector.
Ignore Target RedistributionSkips 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:
AssetConfig sectionManaged by
ActionSetRoleActionSets / DefaultActionSetbManageActionSetsAutomatically
ReactionSetRoleReactionSets / DefaultReactionSetbManageReactionSetsAutomatically
Movement profileRoleMovementProfiles / DefaultMovementProfilebManageMovementProfilesAutomatically
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 calls ForceReassignment and EvaluateRoleDistribution.
    • bReevaluateExisting: calls ReevaluateAllTargets() so assigned combatants re-run TargetSelector (honors bIgnoreTargetRedistribution).
  • SetCombatTarget: deprecated; calls RegisterCombatTarget(NewTarget, true) with no bReevaluateExisting.
  • AddCombatTarget: registers the target only. Use RegisterCombatTarget unless 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

  1. On possess, SECCombatControllerComponent resolves EnemyAIConfig from the pawn's IEnemyAIConfigProvider, else DefaultAIConfig.
  2. With auto-register enabled, it calls UAICombatRoleSubsystem::RegisterCombatant (AAIController required).
  3. The combatant receives a target from InitialTarget (runtime-only), TargetSelector, or the first target registered via RegisterCombatTarget.
  4. With a target set, AssignRoleToNewCombatant slots the newcomer without reshuffling the whole pool.
  5. Each RoleReassignmentInterval tick, EvaluateRoleDistribution groups by target, scores (AI, role) pairs, sorts by adjusted fitness then Priority, and greedily fills slots.
  6. NotifyRoleChange calls SECCombatControllerComponent::HandleCombatRoleAssigned when present, replicates to SECCombatRoleComponent, and runs SyncStateForCombatRole.
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:
  1. HandleCombatRoleAssigned writes the tag to SECCombatRoleComponent on the pawn (replicated).
  2. SyncStateForCombatRole pushes 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:
FunctionSyncs
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.
  • EnemyCharacterBase adds it by default.
  • The server sets it in HandleCombatRoleAssigned via SetCombatRole.
  • Clients read GetCombatRole() or bind OnCombatRoleChanged.
  • SetCombatRole on clients returns without doing work.

Bosses and Opt-Out AI

GoalSetting / API
Skip slot coordinationbAutoRegisterForCombatRoles = false on EnemyAIConfig.
Pin to one player while targets shufflebIgnoreTargetRedistribution = true (skips ReevaluateAllTargets and periodic target re-evaluation).
Script a fixed roleForceAssignRole(Controller, Role, bLockRole = true).
Local role tag without consuming a slotApplyLocalCombatRole 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

SystemLink
Action SystemRoleActionSets swap on role change.
Reaction SystemRoleReactionSets swap on role change.
Movement SystemRoleMovementProfiles swap on role change.
Targeting SystemTargetSelector picks the combat target pool.
MultiplayerRole 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:
FieldUse
ControllerPawn, components, GetCombatantTarget for the assigned target.
RoleRole tag under evaluation.
CurrentRoleRole tag currently assigned.
TimeInCurrentRoleSeconds 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
FunctionPurpose
SetReassignmentIntervalSeconds between auto evaluations (0 = off).
SetMinTimeInRoleHysteresis window.
SetReevaluateTargetsOnReassignmentTarget re-pick before role eval (multi-target).
SetRoleLimitOverride / ClearRoleLimitOverride / ClearAllRoleLimitOverridesRuntime slot caps.
UpdateConfig(FAICombatRoleConfig, bLimitAttritionEnabled, bForceReevaluation)Bulk update; attrition mode keeps existing holders when limits shrink.
ResetToDefaultSettingsClear runtime overrides.
Manual assignment
FunctionPurpose
ForceAssignRole(Controller, Role, bLockRole)Bypass evaluation; optional lock.
UnlockRoleRe-enable evaluation for a locked combatant.
ForceReassignment / ForceReassignmentForTargetImmediate re-eval.
PauseReassignment / ResumeReassignmentFreeze role changes (cutscenes).
StartReassignmentTimer / StopReassignmentTimerTimer control when interval is 0.
Query
FunctionPurpose
GetRole / GetRoleCount / GetCombatantsWithRoleCurrent assignments.
IsRegistered / IsRoleLocked / GetAllCombatantsPool membership.
// Enrage: allow three Attackers, then re-evaluate immediately
UAICombatRoleSubsystem* RoleSys = GetWorld()->GetSubsystem<UAICombatRoleSubsystem>();
RoleSys->SetRoleLimitOverride(
    USoulslikeEnemyCombatSettings::GetRoleAttacker(), 3);
RoleSys->ForceReassignment();
AdvancedMulti-target control
FunctionPurpose
RegisterCombatTarget / UnregisterCombatTarget / GetAllCombatTargetsTarget registry.
AssignCombatantToTarget(Controller, Target, bTriggerRoleEvaluation)Move AI to a target pool (nullptr = unassigned).
GetCombatantTarget / GetCombatantsForTargetQuery assignments.
GetRoleCountForTarget / GetCombatantsWithRoleForTargetPer-target slot usage.
ReassignCombatantTarget / ReevaluateAllTargetsRe-run TargetSelector.
TransferCombatantsToTarget / BalanceCombatantsAcrossTargetsBulk redistribution.
SetPrimaryTargetAndAssignAllAssign 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
DelegateFires when
OnCombatRoleChangedAny role change (global).
OnCombatRoleChangedForTargetRole change with target context.
OnCombatantRegistered / OnCombatantUnregisteredCombatant joins or leaves the pool.
OnCombatTargetRegistered / OnCombatTargetUnregisteredTarget added or removed.
OnCombatTargetChangedSetPrimaryTargetAndAssignAll and similar primary-target handoff.
OnCombatantsOrphanedTarget lost; orphaned controllers reset to SEC.Role.None. Reassign in this handler.