An error in the enemy perception system — enemies see through walls due to missing Linecast check. Or an empty NavMeshAgent stuck at a scene seam. Such bugs break immersion and add weeks to debugging time. We build enemy AI on three layers: sensors, decision-making (Behaviour Tree or FSM), and memory. This eliminates predictability and delivers realistic behavior. Our team has implemented AI for 20+ projects on Unity and Unreal, including mobile and PC. Order enemy AI development — get an engineer consultation.
Enemy Perception Architecture
Field of View (FOV) is the standard method: check distance (up to 20 units for a medium enemy), angle (typically 90–120 degrees), and Linecast for direct line of sight. Skipping Linecast makes the enemy react to the player behind obstacles — a bug common in indie projects.
Field of View Implementation
Field of View is a cone-shaped check zone. Not Physics.OverlapSphere alone: first distance (Vector3.Distance < detectionRange), then angle (Vector3.Angle(forward, dirToPlayer) < fovAngle / 2), then Physics.Linecast to check line of sight without obstacles. Without the last one, enemies see through walls.
Hearing — radius without angle check, but with a filter by sound type. Footsteps on metal are audible at 15 units, on soft floor at 5. Implemented via AIPerceptionEvent published by NoiseEmitter components on moving objects.
Last Known Position — a critical concept. When the player disappears from view, the enemy does not 'forget' instantly. A Vector3 lastKnownPosition and float lastSeenTime are stored. In the Investigation state, the enemy goes to LKP, looks around, and only after a timeout (e.g., 5 seconds) transitions to Patrol.
Step-by-Step Implementation
- Create an
AIPerceptioncomponent on the enemy. Add public fieldsdetectionRange,fovAngle,hearingRadius. - In
Update(), callCheckVision()andCheckHearing(). - For vision:
Vector3.Distanceto player, thenVector3.Anglefor angle, thenPhysics.Linecastfrom enemy's eyes to player. - For hearing: subscribe to
AIPerceptionEventfromNoiseEmitter. UsePhysics.OverlapSpherewith layermask for noises. - Update
lastKnownPositionon detection. If player lost, remember time. - For group alerting: on detection, call
Physics.OverlapSphereonalertRadius(e.g., 30 units), find all enemies withAIPerceptioncomponent, and callReceiveAlert()on them.
Behaviour Tree vs Finite State Machine: Which to Choose?
FSM (Finite State Machine) — for simple enemies with 3–5 states. Implemented as enum EnemyState + switch in Update() or State pattern with classes. Quick to write, easy to debug. Problem: adding new states increases transitions quadratically, and FSM becomes spaghetti at 8–10 states.
Behaviour Tree (BT) — for complex enemies. Behaviour Tree allows modeling behavior 2–3 times faster than FSM when scaling to 10+ states. The tree consists of Selector, Sequence, and Leaf nodes. Selector executes children left to right, stops on first success. Sequence executes all children, stops on first failure. Leaf nodes are atomic actions (MoveToTarget, AttackPlayer, PlayAnimation) and conditions (IsPlayerVisible, IsHealthLow).
Example tree for a patrolling 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). Custom implementation is justified only for specific needs — ready tools save weeks.
| Characteristic | FSM | Behaviour Tree |
|---|---|---|
| Expansion complexity | Quadratic | Linear |
| Visualization | Code, diagrams | Visual editor (usually) |
| Reusability | Low | High (subtrees) |
| Suitable for | 3–5 states | 10+ states |
NavMesh: Navigation and Typical Issues
NavMeshAgent — Unity component for automatic navigation on baked NavMesh. Basic usage: agent.SetDestination(target). But there are nuances.
NavMeshAgent gets stuck at the seam of two NavMeshSurface — a classic problem with additive scene loading. Each scene has its own surface, connections via NavMeshLink must be set explicitly. The NavMeshSurface component from the AI Navigation package replaces the old baked NavMesh and supports runtime updating for dynamic obstacles via NavMeshObstacle. More details in the official Unity AI Navigation documentation.
SetDestination every frame — unnecessary overhead. Path recalculation takes several milliseconds. It is recommended to update destination no more than once per 0.1–0.2 seconds via InvokeRepeating or by checking distance change of target position: if target moved less than 0.5f, no recalculation needed.
Stopping distance and arrive behavior. agent.stoppingDistance determines the distance to the target at which the agent stops. For an attacking enemy, this is attackRange - 0.5f. When switching states (Patrol to Chase), you need to change stoppingDistance and speed — different states require different agent parameters.
Why AI Debugging via Gizmos Is Critical?
AI is debugged via Gizmos: draw FOV cone, LKP point, current agent path, active state above enemy's head. Without visualization in the editor, understanding AI behavior at runtime is nearly impossible.
Profiling: NavMesh.pathfindingTimeSlice (path time per frame), number of active agents. On mobile platforms, more than 20 active NavMeshAgent simultaneously start to noticeably load the CPU. Solution — AI LOD: enemies at distance switch to simplified behavior without path recalculation.
AI Memory and Group Behavior
Simple enemy memory: AIMemory component with List<MemoryEntry> where each entry stores position, type (player, sound, corpse), time. Old entries are removed after timeout (e.g., 10 entries, each lives 30 seconds). When making decisions, BT or FSM queries memory via GetMostRecentEntry(MemoryType.Player).
Group alerting — when one enemy detects the player, it should notify neighbors. Implemented via Physics.OverlapSphere on radius alertRadius (30 units) with filter by tag Enemy, call enemy.GetComponent<AIPerception>().ReceiveAlert(lastKnownPosition). This decentralized solution does not require a manager.
Flanking and coordination — for tactical AI. One technique: NavMeshAgent.SamplePathPosition() is used to find positions on the player's flank, enemies distribute to these positions via a group manager. Implementation details depend on genre.
Proper enemy AI implementation reduces debugging time by 30–50%, saving the lead's budget in man-hours. Quality AI can save up to $500 in early development stages by reducing the number of test iterations.
What Is Included in the Work?
- Analysis of gameplay requirements and AI architecture design
- Implementation of perception systems (FOV, hearing, LKP)
- Development of behavior trees or finite state machines
- Navigation setup (NavMesh, NavMeshLink, optimization)
- Integration of memory, alerting, and group behavior
- Debugging via Gizmos, profiling, and performance optimization
- Documentation and team training
- Post-deployment support
Estimated Timelines
| AI Complexity | Composition | Timeline |
|---|---|---|
| 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 |
We are a team with 10+ years of experience in game development, having implemented AI for 20+ projects on Unity and Unreal. We guarantee functionality and provide support after delivery. Contact us for a consultation — we will select the optimal solution for your project.





