Multiplayer Support
Documentation Unreal Engine AI Multiplayer Replication
Server-authoritative AI with replicated pawn state for client UI, animation, and feedback.
AAIControllerand its SEC components exist only on the server. Clients read replicated pawn fields for telegraphs, combat roles, equipped weapons, and GAS montages.
When to Use This
- Listen server or dedicated server co-op.
- Client HUD for enemy health, current action, or combat role.
- Server-side hit resolution with client-facing attack animation through GAS.
Single-player needs no extra replication wiring.
How It Works
Unreal runs AI on the server. The plugin splits work three ways:
- Server brain: score actions, move, assign roles, trace melee, run awareness, evaluate reactions.
- Bridge: on possession,
SECCombatControllerComponentbinds eval delegates to pawn notify methods and pushes role tags throughSetCombatRole. - Client mirror: bind UI and VFX to replicated pawn delegates and GAS montage playback.
Server Side
AI controller
These components live on
AEnemyControllerBase (or your controller). AI controllers exist only on the server, so none of this replicates to clients:| Component | Role |
|---|---|
SECCombatControllerComponent | AIConfig cache, role sync, delegate bridge to pawn |
ActionEvaluationComponent | Action selection and execution |
ReactionEvaluationComponent | Reaction selection and execution |
MovementEvaluatorComponent | Tactical movement input |
USECAwarenessComponent | Perception memory (header: server-only) |
ThreatDetectionComponent | Player stare detection |
USECBrainComponent | StateTree or native loop |
World subsystems (server authority, not pawn components):
| Subsystem | Role |
|---|---|
UAICombatRoleSubsystem | Role assignment, combat targets |
USECWorldTagSubsystem | World tag writes |
Pawn (authority only)
These stay on the possessed pawn but skip client simulation:
| Piece | Notes |
|---|---|
USECMeleeTraceComponent | StartTracing and tick gate on ShouldDetectHits, a virtual defaulting to HasAuthority. Override it in a subclass to sweep on another machine. See Melee Trace. |
| Melee damage | ISECDamageable::HandleIncomingDamage or legacy ApplyPointDamage on authority. |
Clients see swings through GAS montage replication.
OnMeleeHitResponse fires on authority.A landed hit runs its damage config's Hit Effects on whichever machine ran the trace, and each effect decides its own reach from there. SEC Play Gameplay Cues goes through the ability system and travels to every client; SEC Apply Physics Impulse stays on the machine that ran it. Calling Run Hit Effects yourself from both the server and a predicting client fires the cues twice, so gate the call when you drive damage by hand.
Replicated on the Pawn
AEnemyCharacterBase also creates USECMeleeTraceComponent by default (authority-only; see above). These five replicate combat state to clients:| Component | Clients receive |
|---|---|
SECActionSetComponent | Current action id, executing flag, active action set, equipped weapon ref, cooldown events |
SECReactionSetComponent | Current reaction id, executing flag, active reaction set, cooldown events |
SECCombatRoleComponent | CombatRole gameplay tag |
UAbilitySystemComponent | Gameplay cues, replicated tags and montages. Its replication mode is Minimal, which keeps effect bookkeeping on the server while the cues an effect fires still reach every client. |
USECVitalsComponent | Per-vital anchors (value, server timestamp, regen rate); every machine extrapolates the same smooth GetVitalValue from them. See Vitals. |
Panels below show replicated runtime fields on each pawn component (what clients receive during play, not Blueprint defaults you set once in the editor):
Details
SEC Action Set Component (SECActionSetComponent)
SEC|Action State
Current Action Id
None
Action Executing
Active Action Set
DA_EnemyActions
SEC|ActionSet
Provided Action Set
DA_GreatswordActions
Replicated runtime state on the pawn. The server writes via NotifyActionStarted and role sync; clients read fields or bind OnActionExecutionStarted.
Details
SEC Combat Role Component (SECCombatRoleComponent)
AI|Combat Role
Combat Role
SEC.Role.Attacker
Replicated runtime state on the pawn. SECCombatControllerComponent calls SetCombatRole on the server; clients bind OnCombatRoleChanged.
SECCombatControllerComponent wires controller delegates into pawn notify methods: NotifyActionStarted, NotifyActionCompleted, NotifyCooldownStarted, NotifyCooldownExpired, and USECCombatRoleComponent::SetCombatRole. Reaction evaluation uses the same bridge with NotifyReactionStarted, NotifyReactionCompleted, and matching cooldown notifies on SECReactionSetComponent.Clients bind
OnActionExecutionStarted, OnCombatRoleChanged, OnReactionExecutionStarted, and related cooldown delegates on the pawn.ASECWeaponBase sets bReplicates = true. Attachment replicates with the character mesh.UGameplayAbilityBase (used by GA_SEC_Attack) defaults to server-initiated execution with replicated montages. Exact enum values sit in the GameplayAbilityBase defaults collapsible.Setup
1. Register player targets
Call
RegisterCombatTarget for each player pawn from GameMode or player spawn:UAICombatRoleSubsystem* Roles = GetWorld()->GetSubsystem<UAICombatRoleSubsystem>();
Roles->RegisterCombatTarget(PlayerPawn, /*bAutoAssignUnassigned=*/ true);Co-op needs one entry per player. See Targeting System.
2. Bind client UI to pawn components
USECActionSetComponent* Actions = Enemy->FindComponentByClass<USECActionSetComponent>();
Actions->OnActionExecutionStarted.AddDynamic(this, &UMyHUD::OnEnemyAction);
USECCombatRoleComponent* Role = Enemy->FindComponentByClass<USECCombatRoleComponent>();
Role->OnCombatRoleChanged.AddDynamic(this, &UMyHUD::OnRoleChanged);Mirror the same pattern on
SECReactionSetComponent with OnReactionExecutionStarted (and completion/cooldown delegates as needed).3. Vitals for client health UI
AEnemyCharacterBase ships with USECVitalsComponent (Vitals), authored with one Health row. Bind OnVitalChanged for a client health bar, or poll GetVitalValue / GetVitalFraction directly: every machine extrapolates the same value from the last replicated anchor, so a client reads it with no extra RPC. See Vitals for the full mutator, reader, and event surface.SECCombatControllerComponent binds HandleVitalDepleted to the pawn's OnVitalDepleted and calls HandleDeath when the depleted vital is the configured health tag, while bAutoHandleDeathOnHealthDepleted is true (default). Disable that flag if you drive death from GAS or a custom system.ISECDamageable on EnemyCharacterBase is the preferred damage path for SEC actors.4. Dedicated server animation
AEnemyCharacterBase::BeginPlay sets AlwaysTickPoseAndRefreshBones on dedicated servers. Without it, DS socket positions stay at bind pose and melee traces miss.Integration
| System | Multiplayer note |
|---|---|
| Action System | Execution on server; state on SECActionSetComponent. |
| Reaction System | Same bridge via SECReactionSetComponent. |
| Combat Roles | Subsystem on server; tag on SECCombatRoleComponent. |
| Melee Trace | Traces and damage on authority only. |
| Awareness System | Server-only perception. |
| Movement System | MovementEvaluatorComponent on server; CharacterMovementComponent replicates pose. |
AdvancedGameplayAbilityBase defaults
UGameplayAbilityBase constructor sets:NetExecutionPolicy = ServerInitiated;
ReplicationPolicy = ReplicateYes;
InstancingPolicy = InstancedPerActor;Server activates abilities; GAS replicates montage playback to clients.
AdvancedClient HUD example
void UEnemyHUD::BindToPawn(APawn* Enemy)
{
if (auto* Vitals = Enemy->FindComponentByClass<USECVitalsComponent>())
{
Vitals->OnVitalChanged.AddDynamic(this, &UEnemyHUD::OnVitalChanged);
}
if (auto* Actions = Enemy->FindComponentByClass<USECActionSetComponent>())
{
Actions->OnActionExecutionStarted.AddDynamic(this, &UEnemyHUD::ShowTelegraph);
}
if (auto* Role = Enemy->FindComponentByClass<USECCombatRoleComponent>())
{
Role->OnCombatRoleChanged.AddDynamic(this, &UEnemyHUD::SetRoleIcon);
}
}
// OnVitalChanged fires for every vital on the pawn; filter by tag for the health bar.
void UEnemyHUD::OnVitalChanged(FGameplayTag VitalTag, float NewValue, float OldValue, float MaxValue)
{
if (VitalTag != USoulslikeEnemyCombatSettings::GetHealthVitalTag())
{
return;
}
RefreshBar(NewValue / MaxValue);
if (NewValue < OldValue)
{
FlashDamage();
}
}