Getting Started with SoulslikeCombat
Documentation Unreal Engine AI Tutorial
Install the plugin and build a working enemy from scratch: pawn, controller, ability, ActionSet, and config.
What this plugin is
SoulslikeCombat gives Unreal Engine enemies tactical souls-like behavior through a set of drop-in components. The AI maintains distance, strafes, scores its attacks, reacts to hits, and coordinates with other enemies so they take turns instead of swarming.
You build enemies from data assets and Blueprints. Shipping a working enemy takes no C++. Three kinds of collapsed drawers appear throughout this guide, all closed by default so the main path stays clean:
- Alternative drawers show a different way to reach the same result, no code required.
- Advanced drawers cover deeper options and tuning.
- C++ drawers hold code internals for programmers extending the plugin.
Runs on Unreal Engine 5.6, 5.7, and 5.8. Works in single-player and multiplayer (listen and dedicated server). Current plugin version: 2.3.0.
Install
1. Install to your engine version
Get SoulslikeCombat on Fab, open the Epic Games Launcher, and click Install to Engine. Pick the Unreal Engine version you want to use it with (5.6, 5.7, or 5.8). The plugin installs into that engine with prebuilt binaries.
2. Enable it in your project
Open your project, go to Plugins, search "SoulslikeCombat", and tick it on. Restart the editor when prompted.
3. Confirm the dependencies
The plugin depends on four engine plugins and turns them on for you: GameplayAbilities, StateTree, GameplayStateTree, and MotionWarping. Check Edit → Plugins to confirm they are enabled. MotionWarping drives the built-in attack warping; leave it on unless you replace the ability base.
4. Try the showcase
Open
Content/SoulslikeEnemyCombat/Showcase/Maps/LVL_SEC_Showcase and press Play. You get a working enemy to study before building your own.Build your first enemy
You need two Blueprints and two data assets:
| Piece | Made from | Role |
|---|---|---|
| Enemy pawn | EnemyCharacterBase | The body: mesh, collision, combat components |
| AI controller | EnemyControllerBase | The brain: movement, action selection, reactions |
| Attack ability | GA_SEC_Attack | One attack (plays a montage) |
| ActionSet | Data Asset | The movelist the AI scores and picks from |
The steps below go in order, so nothing references something you have not made yet.
Step 1: Create the enemy pawn
Right-click in the Content Browser → Blueprint Class → search
EnemyCharacterBase. Name it BP_MyEnemy.Set it up like any character:
- Assign your skeletal mesh
- Size the capsule collision
- Set a movement speed (400 is a good start)
EnemyCharacterBase already ships these components:| Component | Class |
|---|---|
| Ability System Component | UAbilitySystemComponent |
| Melee Trace Component | USECMeleeTraceComponent |
| Action Set Component | USECActionSetComponent |
| Reaction Set Component | USECReactionSetComponent |
| Combat Role Component | USECCombatRoleComponent |
AlternativeUse your own character, no C++
Already have a character Blueprint? Reparent it to
EnemyCharacterBase instead of rebuilding it. No code required.- Open your character Blueprint.
- File → Reparent Blueprint, then pick
EnemyCharacterBase. - Your existing components, variables, and graphs stay. You inherit the ability system component, the four SEC components (melee trace, action set, reaction set, combat role), the three combat interfaces, and the dedicated-server socket fix.
- If your Blueprint already added its own
AbilitySystemComponentor any SEC component, delete the duplicates so only the inherited ones remain. - Set AIConfig on the Blueprint (Step 5 covers this), then assign the mesh and controller as usual.
The action and reaction loops read the pawn's ability system component through
IAbilitySystemInterface, and a pure Blueprint cannot implement that interface. Reparenting to EnemyCharacterBase is how you get it without code, so reach for it whenever your character can inherit from EnemyCharacterBase.C++Replicate EnemyCharacterBase in C++
Reparenting fails when your character must inherit from a different C++ class. Subclass
ACharacter (or your own base) in C++ and reproduce these five things:- Implement three interfaces:
IAbilitySystemInterface,IEnemyAIConfigProvider, andISECDamageable. - Create the components as default subobjects in the constructor:
UAbilitySystemComponentUSECMeleeTraceComponentUSECActionSetComponentUSECCombatRoleComponentUSECReactionSetComponent
- Return the ability system component from
GetAbilitySystemComponent(). The action and reaction loops read the pawn's ASC through this interface, so this step is mandatory. - Return the config from
GetAIConfig_Implementation(). Expose anEditAnywhere TObjectPtr<UEnemyAIConfig>property and return it, so the controller reads a per-pawn config on possession. Returnnullptrto fall back to the controller's config. - Keep dedicated-server sockets correct in
BeginPlay: whenGetNetMode() == NM_DedicatedServer, set the mesh'sVisibilityBasedAnimTickOptiontoAlwaysTickPoseAndRefreshBones. A dedicated server never renders, so without this the mesh holds its bind pose and socket-based melee traces fire from the wrong location.
The base also disables actor tick (
PrimaryActorTick.bCanEverTick = false); the combat components run their own updates, so the pawn needs no tick. ISECDamageable::ApplyDamage receives structured hits from the melee trace pipeline; override it to apply damage the way your game needs. Skip the interface and the actor still takes damage through Unreal's ApplyPointDamage fallback.Step 2: Create the AI controller
Right-click → Blueprint Class → search
EnemyControllerBase. Name it BP_MyEnemyController.It comes pre-wired with every component the AI needs:
| Component | Purpose |
|---|---|
SECCombatControllerComponent | Core plumbing: config, roles, subsystem registration |
MovementEvaluatorComponent | Tactical positioning and strafing |
ActionEvaluationComponent | Scores and runs actions |
ReactionEvaluationComponent | Runs event-driven reactions (parry, flinch) |
ThreatDetectionComponent | Tracks when the player looks at the AI (disabled by default) |
AwarenessComponent | Per-AI perception memory (sight, hearing, damage) |
SECBrainComponent | Runs your StateTree, or a native combat loop if none is set |
HelperBTComponent | Runs short behavior-tree sequences for Do Action (not the brain) |
AlternativeUse your own AI controller, no C++
Already have an AI controller Blueprint? Reparent it to
EnemyControllerBase. No code required.- Open your controller Blueprint.
- File → Reparent Blueprint, then pick
EnemyControllerBase. - You inherit the combat evaluators,
HelperBTComponent(Do Action BT helper), the committed-attack rotation lock, smooth focus, and awareness-based facing.
Adding the SEC components to a bare
AAIController is not equivalent. EnemyControllerBase overrides UpdateControlRotation to hold the AI's facing during an attack and to face a target's last-known location, and the attack abilities lock rotation by casting the controller to AEnemyControllerBase. A plain controller keeps tracking the target mid-swing and loses that facing logic, since none of it is exposed to Blueprint. Reparent to EnemyControllerBase, or subclass it in C++ if you need a different base.Step 3: Create the attack ability
Right-click → Blueprint Class → search
GA_SEC_Attack. Name it GA_LightAttack. GA_SEC_Attack is a ready-made attack ability: it plays a montage, forwards the montage's notify events, and ends the ability when the montage finishes.Set it up:
- Assign your attack montage. Assign several montages and the ability plays a random one each activation.
- Wire On Notify Begin and On Notify End on the montage task (
USECAbilityTask_PlayMontageinGA_SEC_Attack) to run logic at notify windows: enable the melee trace, spawn VFX, apply effects.
That covers a basic attack.
GA_SEC_Attack inherits from GameplayAbilityBase, so the action system detects when the attack starts and ends.AlternativeStart from GameplayAbilityBase instead
For an attack that does not fit the montage flow, build one straight from
GameplayAbilityBase. Create a Blueprint from GameplayAbilityBase, then in the graph:ActivateAbilityPlay Montage and Waitwith your attack montageEnd Ability
GameplayAbilityBase broadcasts its end tag (SEC.Action.End) on EndAbility, and the action system waits for that tag to know the attack finished. GA_SEC_Attack is this same wiring plus montage randomization and the notify events, so reach for the base class only when you need something the ready-made ability does not do.C++What GameplayAbilityBase does for you
Inherit from
GameplayAbilityBase (not the raw UGameplayAbility) so the action system integrates automatically. It provides:- Ability end tag.
AbilityEndTagdefaults toSEC.Action.End, broadcast onEndAbility. The execution method waits on this to complete the action. - Rotation lock.
bLockAIRotationfreezes facing during the swing, so the AI commits instead of tracking the player mid-attack. - Motion warping. Sets a warp target from the AI's focus actor (
MotionWarpingTargetName,MotionWarpingOffset), so attacks land at range. - Action context.
GetActionContext()returns the target, distance, direction, and magnitude, valid fromCanActivateAbilitythrough activation.
Step 4: Create the ActionSet
Right-click → Miscellaneous → Data Asset →
ActionSet. Name it DA_MyEnemyActions. Add one entry to Actions and fill it in:| Field | Value |
|---|---|
| Action ID | LightAttack |
| Execution Method | Gameplay Ability → set Ability Class to GA_LightAttack |
| Cooldown → Duration | 2.0 |
| Selection Weight | 1.0 |
Then add a scorer so the AI only picks this attack at the right distance. In the action's Scoring → Scorers list, add a Distance Scorer. Its
Range defaults to a melee band (0 / 100 / 250 / 500), meaning full score between 100 and 250 cm, fading to zero by 500. Add an Angle Scorer too if you want the AI to attack only when facing the target; its range defaults to a frontal 0 / 0 / 30 / 90.Ability Tag and Ability End Tag on the execution method fill in automatically from the ability's class defaults. You only set them by hand for a custom override.AdvancedHow an action is scored
Each action produces a score, and the highest valid one wins:
FinalScore = SelectionWeight × CtxMult × Novelty × Chain × (1 / RiskPenalty) × RuntimeMod × CustomMod × JitterCustomMod folds every scorer you add (distance, angle, health, speed, custom). Distance, angle, health, and speed are not automatic: each needs its own scorer in the Scoring → Scorers list.
Other factors:
- Attribute Scorer / Attribute Gate / Stamina Gate for GAS-backed rules.
- RuntimeMod from
SetGlobalMultiplierand per-action overrides onActionEvaluationComponent. - Jitter is a deterministic ±5% tie-breaker from the decision context seed.
Actions scoring zero or failing a gate are skipped. Chaining (
Preferred Follow-Up Action on the source action plus Chain Bonus Multiplier on the follow-up) boosts a combo once the current action completes.Step 5: Create the AI config
Right-click → Miscellaneous → Data Asset →
EnemyAIConfig. Name it DA_MyAIConfig. Set:- Default Action Set →
DA_MyEnemyActions - Default State Tree → leave empty to run the native combat loop (build context, move, then pick and run the best action on the brain's evaluation cadence, default 0.1 s). Set it to
Content/SoulslikeEnemyCombat/Showcase/AI/StateTree/StateTree_SEC_Coreto drive the AI with that StateTree instead. - Default Reaction Set → optional. Assign one if you built a
ReactionSet. See the Reaction System.
AdvancedPer-role overrides and config resolution
EnemyAIConfig resolves per combat role. RoleActionSets, RoleReactionSets, and RoleMovementProfiles override the defaults for a specific role (for example a heavier set for SEC.Role.Attacker); anything not listed falls back to DefaultActionSet, DefaultReactionSet, and DefaultMovementProfile.The controller reads the config on possession. When the pawn implements
IEnemyAIConfigProvider (which EnemyCharacterBase does), it uses the pawn's config, so one controller class serves many enemy types. Without the interface, the controller falls back to its own config.Step 6: Connect the pieces
Two links wire everything together:
- In
BP_MyEnemy→ Class Defaults, set AI Controller Class toBP_MyEnemyController. - In
BP_MyEnemy→ Class Defaults, set AIConfig toDA_MyAIConfig.
Step 7: Register the target and play
The AI needs to know who to fight. Register the player once, on spawn:
- In your player pawn's BeginPlay, get the AICombatRoleSubsystem.
- Call RegisterCombatTarget with
Selfas the target actor.
BP_Dummy in Content/SoulslikeEnemyCombat/Showcase/AI/Characters/Dummy shows this exact wiring.Drop
BP_MyEnemy into a level with a built NavMesh and press Play. The enemy should:- close to its scored distance (100–250 cm for the melee band above)
- attack when in range and facing the target
- wait out the cooldown, reposition, and repeat
No NavMesh means no pathfinding. If the enemy will not move, add a NavMeshBoundsVolume and build navigation.
AdvancedWatch the decision-making
Turn on the component debug flags to watch the AI decide. On
MovementEvaluatorComponent, enable bDebugDrawScoring (or SEC.Debug.Movement.DrawScoring 1) to draw scored movement directions. On ActionEvaluationComponent, enable bDebugLogDecisions (or SEC.Debug.LogActionDecisions 1) to print each action's score breakdown. Enable these in the editor only, never in a shipping build.