Hi everyone,
I’m implementing a melee combat slot system entirely in UE5 C++ (no Blueprint logic except assigning assets), and I’ve reached a point where I’m stuck debugging the AI state transitions.
Current architecture
- Unreal Engine 5
- C++
- AI Perception (Sight)
- Behavior Tree + Blackboard
- Custom BT Tasks
- Custom BT Decorator
- Custom AIController
I originally used a Vector ChaseTarget that was updated by a BT Service every 0.1s, but after many issues (MoveTo(Vector) constantly restarting and stale positions), I removed that system completely.
Now my setup is:
- Each enemy claims a combat slot (Front, Back, Left, etc.) from a GameMode slot manager.
- Every enemy spawns its own hidden
SlotActor. - Every Tick, the SlotActor updates its position relative to the player’s location and facing direction.
- The Behavior Tree uses MoveTo(SlotActor) instead of MoveTo(Player).
So there is no UpdateChaseTarget BT Service anymore.
Behavior Tree
Selector
|
|-- Attack
| PlayerActor != nullptr
| IsInAttackRange (custom decorator)
| EnemyAttack
|
|-- Chase
| PlayerActor != nullptr
| MoveTo(SlotActor)
|
|-- Search
| PlayerActor != nullptr
| bCanSeePlayer == false
| FindSearchLocation
| MoveTo(SearchLocation)
|
|-- Patrol
FindPatrolLocation
MoveTo(PatrolLocation)
Attack decorator
Instead of checking distance to the player, I now check distance to the assigned SlotActor.
Distance = FVector::Dist(
Enemy->GetActorLocation(),
SlotActor->GetActorLocation());
return Distance <= AttackRange + SlotTolerance;
The decorator is set to:
- bNotifyTick = true
- Observer Aborts = Both
Current problem
The enemy correctly:
- detects the player
- claims the correct slot
- moves behind / beside the player
However, once it reaches the slot, it usually does not attack.
Instead one of these happens:
- It stands behind the player doing nothing.
- After a few seconds it suddenly switches to the Patrol branch.
- It walks to a random patrol location.
- Then comes back to the slot.
- Then repeats.
If I move the player, sometimes the enemy starts following again.
If I stand still, eventually Patrol takes over again.
Things I’ve already checked
- PlayerActor remains valid.
- SlotActor is valid.
- SlotActor updates every Tick.
- AI Perception detects the player correctly.
- Attack decorator is evaluating every tick.
- I removed the old Blackboard bool (
bIsInAttackRange) and only use the custom decorator now. - There are no obstacles between the player and enemy.
- Acceptable Radius on MoveTo(SlotActor) is 80.
What confuses me
If PlayerActor is still set, I would expect:
Attack
↓
Chase
to keep running.
Instead, the Selector eventually chooses:
Patrol
even though the player is standing directly in front of the enemy.
My questions
- Can
MoveTo(SlotActor)be considered “finished” repeatedly because the SlotActor moves every Tick? - Is using a continuously moving SlotActor as the MoveTo target a bad design?
- Would you recommend switching to an EQS-based solution (generate points around the player and reserve them) instead of updating a SlotActor every Tick?
- Is there something fundamentally wrong with my Behavior Tree flow that could cause Patrol to run while PlayerActor is still valid?
I’m happy to share code or a short video if that would help.