Enemy AI System Programming
NavMeshAgent.SetDestination(player.position) in Update() every frame—that's not AI, that's a puppet on strings. An enemy that always knows where the player is, always walks toward them directly, and attacks as soon as reached creates neither interesting gameplay nor sense of intelligence. Real AI system is built around perception, decision-making, and memory—not just navigation.
Perception: How Enemy Sees the World
The first AI layer is sensors. An enemy shouldn't "know" about the player via direct reference in Update(). Perception is modeled:
Field of View is a cone-shaped detection zone. Not just Physics.OverlapSphere: first distance (Vector3.Distance < detectionRange), then angle (Vector3.Angle(forward, dirToPlayer) < fovAngle / 2), then Physics.Linecast to check line of sight without obstacles. Without this enemies see through walls.
Hearing is radius without angle check, but with sound type filter. Footsteps on metal audible at 15 units, on soft floor—at 5. Implemented via AIPerceptionEvent published by NoiseEmitter components on moving objects.
Last Known Position is critically important concept. When player leaves view, enemy doesn't "forget" instantly. Stored: Vector3 lastKnownPosition and float lastSeenTime. In Investigation state enemy walks to LKP, looks around, only after timeout switches to Patrol.
Behaviour Tree vs State Machine: What to Choose
FSM (Finite State Machine) is for simple enemies with 3–5 states. Implemented as enum EnemyState + switch in Update() or State pattern with classes. Writes quickly, debugs easily. Problem: adding new states—transition quantity grows quadratically and FSM turns into "spaghetti" at 8–10 states.
Behaviour Tree (BT) is for complex enemies. Tree of Selector, Sequence, and Leaf nodes. Selector executes children left to right, stops at first success. Sequence executes all children, stops at first failure. Leaf nodes are atomic actions (MoveToTarget, AttackPlayer, PlayAnimation) and conditions (IsPlayerVisible, IsHealthLow).
Example tree for patrol enemy:
Selector
├── Sequence (attack)
│ ├── IsPlayerInAttackRange
│ └── AttackAction
├── Sequence (chase)
│ ├── IsPlayerVisible
│ └── MoveToPlayerAction
├── Sequence (investigate)
│ ├── HasLastKnownPosition
│ └── MoveToLKPAction
└── PatrolAction
Popular BT implementations for Unity: NodeCanvas, Behavior Designer, Unity Muse Behavior (official package 2023+). Custom implementation justified only for specific needs—ready tools save weeks.
NavMesh: Navigation and Common Problems
NavMeshAgent is Unity component for automatic navigation on baked NavMesh. Basic use: agent.SetDestination(target). But there are nuances.
NavMeshAgent gets stuck at junction of two NavMeshSurfaces—classic problem with additive scene loading. Each scene has its surface, connections via NavMeshLink need explicit tuning. In Unity 2022+ NavMeshSurface component from AI Navigation package replaces old baked NavMesh and supports runtime update for dynamic obstacles via NavMeshObstacle.
SetDestination every frame is unnecessary load. Path recalculation takes several milliseconds. Recommended to update destination no more than once per 0.1–0.2 seconds via InvokeRepeating or distance check: if target moved less than 0.5f—recalc not needed.
Stopping distance and arrive behavior. agent.stoppingDistance sets distance to goal where agent stops. For attacking enemy this is attackRange - 0.5f. On state change (Patrol to Chase) need to change stoppingDistance and speed—different states need different agent parameters.
AI Memory and Group Behavior
Simple enemy memory: AIMemory component with List<MemoryEntry> where each entry holds position, type (player, sound, corpse), time. Old entries delete by timeout. On decisions, BT or FSM query memory via GetMostRecentEntry(MemoryType.Player).
Group alerting happens when one enemy spots player and alerts neighbors. Implemented via Physics.OverlapSphere at alertRadius with Enemy tag filter, call enemy.GetComponent<AIPerception>().ReceiveAlert(lastKnownPosition) on each. Doesn't need manager, works decentralized.
Flanking and coordination for tactical AI. One technique: NavMeshAgent.SamplePathPosition() finds positions on player flank, enemies distributed to these positions via group manager. Implementation details depend on genre.
Timeline Guidelines
| AI Complexity | Components | Timeframe |
|---|---|---|
| Simple FSM | Patrol, Chase, Attack, 3 states | 3–5 days |
| Medium | Perception, LKP, Investigation, Flee | 1–2 weeks |
| Full BT | NodeCanvas/Behavior Designer, groups, coordination | 3–6 weeks |
| Complex tactical | Flanking, cover system, squad AI | 2–3 months |
Process and Debugging
AI debugged via Gizmos: draw FOV cone, LKP point, current agent path, active state above enemy head. Without editor visualization, understanding runtime AI behavior is practically impossible.
Profiling: NavMesh.pathfindingTimeSlice (path time per frame), count of active agents. On mobile platforms, more than 20 active NavMeshAgent simultaneously start noticeably loading CPU. Solution—AI LOD: at distance enemies switch to simplified behavior without path recalculation.





