1. Architecture 2. Behavior tree structure 3. Combat decision 4. Attack extensions 5. Measured issues and fixes

1. Architecture overview

AIController + BehaviorTree + Blackboard + Blueprint-derived Pawn + AI child Blueprints.

An AI enemy Pawn is not newly built from scratch; it is a child Blueprint derived from the character Blueprint (preserving the full character Mesh / animation / montage / skill graph; only the controller and input properties change). The whole AI system is composed of 6 core assets + 4 attack-extension assets:

AssetDuty
CharacterEnemyBlueprint (child Blueprint)AIControllerClass=EnemyAIController; AutoPossessPlayer=Disabled (critical: do not let the PIE player controller seize the Pawn); Tags += "Enemy"
EnemyAIControllerRunBehaviorTree entry; writes blackboard (HomeLocation / bEngaged=false) in OnPossess
EnemyBehaviorTreeSelector schedules three branches (return home / combat pursue / idle), 0.5 s BT service
EnemyBlackboardBlackboard keys: bEngaged, bWantsAttack, DistanceToTarget, HomeLocation, TargetActor
EnemyCombatActionTask (BTTask)Executes the attack action: basic-attack five-hitter montage / lightning skill / teleport flight
EnemyTargetService (BTService)Refreshes DistanceToTarget every 0.5 s, sticky-lock roll on bEngaged

Navigation system: the level's NavMeshBoundsVolume was originally disabled (enable_navigation_system=False). This task enabled it and also created a large volume of 30000×30000×2000 (tile=11163); otherwise the AI MoveTo would not have been available.

2. Behavior tree structure

Selector single root; three branches with decreasing priority left → right.

Root
└ Selector【服务 EnemyTargetService,Interval=0.5,挂根层 ⇒ 待机时也检测】
   ├ 分支1 回家
   │   ├ 装饰器:DistanceToTarget > 35000(cm = 350m)
   │   │   FlowAbortMode=Both:真→抢占下面所有分支;假→放弃本分支
   │   ├ MoveTo:HomeLocation,AcceptableRadius=300
   │   └ Wait 2.0
   ├ 分支2 战斗/追击
   │   ├ 装饰器:bEngaged 为 true(Abort=LowerPriority)
   │   └ Selector
   │       ├ 攻击序列
   │       │   ├ 装饰器:bWantsAttack 为 true(抢占追击 MoveTo)
   │       │   └ EnemyCombatActionTask
   │       └ MoveTo:TargetActor,AcceptableRadius=600(追击)
   └ 分支3 待机:Wait 3.0

Key mechanism. bEngaged is sticky — it locks to true when entering 100 m and is released only when distance exceeds 350 m (prevents enemies from flipping state at the boundary repeatedly). bWantsAttack is written by EnemyTargetService (Random < 0.35 and cooldown done), which solves the classic problem "a persistently running pursue MoveTo starves the attack branch." EnemyCombatActionTask must clear bWantsAttack=false immediately at the top, otherwise the cooldown gate becomes a formality.

3. Combat decision logic (distance-band selection)

EnemyCombatActionTask selects actions automatically by distance; master-gate cooldown 4~8 s prevents the attack frequency from climbing abnormally.

Distance bandCandidate actionImplementationCooldown
< 1500 cm (15 m)Basic-attack five-hitterCall the character Blueprint event → play the 5-segment montage 13_combo_1~5 in order; each segment fires a homing bullet after 0.25 s (EnemyTrackBullet, homing 6000, damage 20); ApplyDamage only to GetPlayerPawn(0), distance ≤ 60 m (never chase or damage enemy Blueprints)3~6 s
1500~5000Lightning skill → basic attackPlay 04_beam_all (no damage, pure presentation) → wait 1 s → chain into the basic attack10~15 s
5000~15000Teleport → flightTeleport = play the teleport montage + ProjectPointToNavigation (the success bool must be checked; failure → no displacement) + SetActorLocation; flight = MOVE_Flying + Launch(0,0,500), hold for 3.5 s then MOVE_Falling8~12 s / 20~30 s

4. Light Flight chase + player hit (added 2026-09-29 to 2026-10-01)

Four extensions, PIE measured all green; 1, 2, 3 implemented, 4 shield block pending manual verification.

ExtensionImplementation
Light Flight chase (AI version of right-click Light Flight)C++ added ExecuteEnterTransformForAI / ExecuteExitTransformForAI (BlueprintCallable on CharacterTransformBase, fully replicating the player right-click Light Flight timing: dissolve 0.2 s → flash → 0.4 s → enter Light Flight state; exit is the reverse). BTTask EnemyDashChaseTask calls Enter → straight-line pursuit (4500 cm/s, set location + Sweep) → ≤ 12 m → calls Exit → polls for exit completion (bIsInTransformedForm==false, 1.5 s timeout fallback) → writes bWantsAttack=true once ready
Full basic-attack five-hitterEnemyCombatActionTask plays montages 1~5 in order (one homing bullet every 0.25 s per segment); after the 5 segments SetAttackCooldowns(now+4~7 s)
Player can hit AIAI must be "hittable by sword qi / Q-key Six Swords / basic attack / lightning." The generalization: new interface EnemyTargetInterface (FakerHP / CanTick / LightningPick / CE_LightningLink), implemented by both EnemyBlueprint (originally the drone) and CharacterEnemyBlueprint; three Tagged attack asset copies are duplicated (BezierBullet_TagEnemy / SwordBullet_TagEnemy / Lightning_Branch_TagEnemy, enemy search changed to Actor Has Tag("Enemy")); 2 SpawnActor spots in the player Attack graph point to the copies. Original assets untouched (zero regression on drone behavior)
Shield blockBullet ProjectileCollision Block on WorldDynamic → hitting the shield is blocked. Shield side = E-key shield (Tick polls the physical E key); automation cannot inject the key input, manual E-key verification pending

5. Measured issues and fixes (all verified end-to-end in PIE)

  1. Level navigation system was originally disabled (enable_navigation_system=False + NullNavSysConfig) — the old 100×100 m NavMeshBoundsVolume never took effect. This task enabled it and added a large volume.
  2. AutoPossessPlayer=Player0 inherited by the character Blueprint would let the PIE player controller seize the Pawn. The child Blueprint must set AutoPossessPlayer=Disabled, otherwise the AI could not be controlled by the AIC.
  3. OnPossess must run RunBehaviorTree before writing the blackboard: writing before the tree starts means the blackboard is not yet assembled, and the write is silently lost (read back as FLT_MAX at runtime).
  4. The persistently running pursue MoveTo starves the attack branch to its left on the same layer (a behavior tree does not rescan already-activated branches) → introduce the bWantsAttack blackboard key + the Abort=LowerPriority decorator preempt; and the BTTask must clear the key at execution start, otherwise the cooldown gate becomes the only throttle, causing an attack-rate spike and the roll to fail.
  5. CharacterMovement DefaultLandMovementMode=MOVE_Flying in TransformBase (hovering-form default on the inheriting class) — when AI holds it directly, it spawns as Flying and never lands; plus the Launch after the flight action has no gravity brake → hovering at 44 m "invisible". Fix: the AI child Blueprint forces SetMovementMode(Walking) in BeginPlay (verified in PIE 105 s: C++ does not push it back).
  6. Two problems in the teleport action: 1. ProjectPointToNavigation returns a zero vector on failure; the success bool branch must be taken (failure → Finish(false)); 2. when the player Pawn is out of bounds and destroyed, GetPlayerPawn=None, and reading the location returns a zero vector → projection onto the origin is judged "success" → teleported to (0,0,0). The position source must be validated.
  7. The BTTask synchronous FinishExecute starves ReceiveTickAI: the attack branch originally "finishes as soon as execution is done" → Tick never runs → the 0.4 s hit decision becomes dead code. Task-style actions must rely on ActionDuration counting up to the limit before finishing.
  8. The damage pipeline built into the character Blueprint only recognizes DroneTarget (the BeginOverlap of EF_BP_BezierBullet with OtherActor==DroneTarget) — the original character attack target is the drone, so no damage can be dealt to the player pawn. AI damage must be called by the AI itself (current plan: only GetPlayerPawn(0), distance ≤ 6000, naturally satisfying "damage only the character, never EnemyBlueprint").
  9. Rate-limit failure root cause: the bullet spawn source never writes the cooldown variable (probe measured the value frozen for 26 s) → the entry gate became a formality → 21 shots/s crashed the GPU. Fix: call SetAttackCooldowns after spawn (now 4~7 s). Troubleshooting method: add temporary Print probes to measure the count per link; do not rely on speculation.
  10. Attack and exit VFX not shown during Light Flight: 1. the attack intent is written on the same frame right after Exit in the arrival chain, so the attack runs before the 0.1+0.4 s transition; 2. when the player is 12~18 m away, arrival falls inside the enter transition window (0.4 s), and Exit is blocked by the C++ guard (SKIPPED in logs is hard proof) → stuck in Light Flight state. Fix: wait for the exit phase (poll + retry + timeout fallback) + double safety at the CombatAction entry.

The Enemy AI system: 6 core assets + 4 attack-extension assets + 2 newly added C++ interface functions (h:419-432 / cpp:962-1026). Rollback = delete all new Blueprint assets + delete the C++ ForAI interface. The character Blueprint body is untouched.