3.4 KiB
ADR-005: Enemy AI Decision Model
Status: Accepted Date: 2026-04-27 Engine: Unity 2022.3.62f3c1 GDD Requirements Addressed: Enemy AI Logic, Boss AI Logic (systems-index.md #4, #5)
Context
Nightborn's enemy AI doesn't need complex tactical reasoning, but has a special requirement: each enemy type must strongly favor a specific form, creating "I need to switch" pressure. MVP scope is 3-5 enemy types. The AI model must be simple to implement, produce predictable behavior, and be easily understood and tested by external developers.
Decision
Use lightweight per-type Finite State Machines. Reject Behavior Trees and Utility AI.
Base FSM Structure (shared by all enemy types)
Idle ──→ Chase ──→ Attack ──→ Cooldown
↑ │ │
└────────────────────┴──────────┘
If too far, return to Chase
Per-Type Differentiation in the Attack State
| Enemy Type | Attack Behavior | Encouraged Form | Why |
|---|---|---|---|
| Swarm | Many simultaneous small-area hits | Mist | Mist's circular AOE clears groups at once |
| Brute | Slow, high-damage single strike | Human | Human's parry window is clear and precise against it |
| Stalker | Fast rush + point-blank flurry | Wolf | Wolf's rectangular dash collides for a stagger counter |
| Shooter | Ranged projectile barrage from periphery | Mist/Wolf | Mist phases through to close, or Wolf bursts to eliminate |
| Boss | Multi-phase mixed patterns | All forms | Phase transitions require form adaptation |
Why Not Behavior Trees
BTs excel at complex decision chains (e.g., FPS cover-shooter AI). Nightborn enemies need only 4 states. The tree's maintenance cost exceeds its value.
Why Not Utility AI
Scoring systems produce "optimal" behavior but aren't predictable enough — players can't learn enemy patterns, violating the "Battlefield is Information" pillar.
Implementation
// L0 — Pure C#, returns one AICommand per frame
public enum EnemyStateType { Idle, Chase, Attack, Cooldown }
public class EnemyAILogic
{
public EnemyTypeConfig Config;
public AICommand ComputeAction(EnemyState self, PlayerState player)
{
TransitionState(self, player);
return CurrentState switch
{
Idle => ScanForPlayer(player),
Chase => MoveToward(player),
Attack => ExecuteAttack(self.AttackPattern, player),
Cooldown => WaitAndReset(self.CooldownTimer),
};
}
// Per-type override for unique transition logic
protected virtual void TransitionState(EnemyState self, PlayerState player) { }
}
Boss Extension
Boss AI extends the base FSM with:
BossPhaseenum (additional state dimension)- Phase transition conditions (health thresholds, timer triggers)
OnPhaseChangedevent for L1 VFX/Audio synchronization
Consequences
- External developers can understand an enemy's behavior from
EnemyTypeConfigalone — no need to parse behavior trees - Enemy behavior is predictable and learnable — satisfies "Battlefield is Information" pillar
- Each enemy type's Attack pattern naturally points to one form counter — reinforces "Form is Tactics"
- If future enemies need more intelligence (cover, coordination), the FSM base can be extended without replacement