i built a full enemy ai system in unity 6 using only ai tools and tracked every hour it took me
I wanted to know whether I could build a complete enemy AI system in Unity 6, including state machine, patrol, detection, and combat behavior, using only AI tools for the scripting. No documentation tabs open, no Stack Overflow, no YouTube tutorials. Just me, Cursor, and Unity Muse. I tracked every hour. Here is how it went, what the AI got wrong, and what surprised me about how fast some parts went.
Marcus Webb
June 24, 2026
The experiment: build a complete enemy AI with Idle, Patrol, Chase, Attack, and Dead states, NavMesh movement, line of sight detection, and field of view cone detection, using only AI tools for any code I needed to write or look up. Tools allowed: Cursor Pro, Unity Muse Chat. Tools not allowed: Google, Stack Overflow, Unity documentation pages, YouTube. Time tracked with Toggl. Total time to working system: 4 hours 22 minutes. My estimate for doing the same thing my old way: 9 to 12 hours.
Why I Did the Experiment This Way
I wanted to see what happened when I removed every escape valve I normally use when the AI gives me something wrong or incomplete. In my normal workflow I use AI tools but I also keep documentation tabs open and occasionally verify things on Stack Overflow. I wanted to know whether the AI tools were actually sufficient on their own or whether I was using them as a starting point and doing the real work through research. The answer turned out to be somewhere in the middle.
The System I Built and the Code That Came Out
// EnemyStateMachine.cs
// Generated with Cursor Pro using project context
// Built without documentation or Stack Overflow
// Unity 6 LTS with NavMesh
using UnityEngine;
using UnityEngine.AI;
public class EnemyStateMachine : MonoBehaviour
{
public enum EnemyState { Idle, Patrol, Chase, Attack, Dead }
public EnemyState currentState = EnemyState.Idle;
[Header("Detection")]
public float sightRange = 12f;
public float attackRange = 2.2f;
public float fieldOfViewAngle = 110f;
public LayerMask playerLayer;
public LayerMask obstructionLayer;
[Header("Patrol")]
public Transform[] patrolPoints;
private int currentPatrolIndex;
[Header("References")]
private NavMeshAgent agent;
private Animator animator;
private Transform player;
private float lastAttackTime;
public float attackCooldown = 1.5f;
// Health is handled by a separate EnemyHealth script
// This script listens to EnemyHealth.OnDeath event
void Awake()
{
agent = GetComponent<NavMeshAgent>();
animator = GetComponent<Animator>();
player = GameObject.FindWithTag("Player").transform;
}
void OnEnable()
{
// Subscribe to death event from sibling component
GetComponent<EnemyHealth>().OnDeath += HandleDeath;
}
void OnDisable()
{
GetComponent<EnemyHealth>().OnDeath -= HandleDeath;
}
void Update()
{
if (currentState == EnemyState.Dead) return;
bool canSeePlayer = CanSeePlayer();
float distToPlayer = Vector3.Distance(transform.position, player.position);
switch (currentState)
{
case EnemyState.Idle:
animator.SetBool("isMoving", false);
if (canSeePlayer) TransitionTo(EnemyState.Chase);
else if (patrolPoints.Length > 0) TransitionTo(EnemyState.Patrol);
break;
case EnemyState.Patrol:
HandlePatrol();
if (canSeePlayer) TransitionTo(EnemyState.Chase);
break;
case EnemyState.Chase:
agent.SetDestination(player.position);
animator.SetBool("isMoving", true);
if (distToPlayer <= attackRange) TransitionTo(EnemyState.Attack);
if (!canSeePlayer && distToPlayer > sightRange * 1.4f)
TransitionTo(patrolPoints.Length > 0
? EnemyState.Patrol : EnemyState.Idle);
break;
case EnemyState.Attack:
agent.ResetPath();
transform.LookAt(player.position);
if (Time.time - lastAttackTime >= attackCooldown)
{
animator.SetTrigger("attack");
lastAttackTime = Time.time;
}
if (distToPlayer > attackRange) TransitionTo(EnemyState.Chase);
break;
}
}
private void HandlePatrol()
{
if (patrolPoints.Length == 0) return;
animator.SetBool("isMoving", true);
agent.SetDestination(patrolPoints[currentPatrolIndex].position);
if (agent.remainingDistance < 0.5f && !agent.pathPending)
{
currentPatrolIndex = (currentPatrolIndex + 1) % patrolPoints.Length;
}
}
private bool CanSeePlayer()
{
Vector3 dirToPlayer = (player.position - transform.position).normalized;
float angleToPlayer = Vector3.Angle(transform.forward, dirToPlayer);
if (angleToPlayer > fieldOfViewAngle / 2f) return false;
float distToPlayer = Vector3.Distance(transform.position, player.position);
if (distToPlayer > sightRange) return false;
// Raycast to check for obstructions between enemy and player
return !Physics.Raycast(transform.position + Vector3.up * 0.8f,
dirToPlayer, distToPlayer, obstructionLayer);
}
private void TransitionTo(EnemyState newState)
{
currentState = newState;
}
private void HandleDeath()
{
currentState = EnemyState.Dead;
agent.enabled = false;
animator.SetTrigger("death");
GetComponent<Collider>().enabled = false;
}
void OnDrawGizmos()
{
Gizmos.color = Color.yellow;
Gizmos.DrawWireSphere(transform.position, sightRange);
Gizmos.color = Color.red;
Gizmos.DrawWireSphere(transform.position, attackRange);
}
}What the AI Got Wrong and How I Fixed It Without Documentation
- First Cursor draft used FindObjectOfType for the player reference instead of FindWithTag. Not wrong exactly but FindObjectOfType is deprecated in Unity 6. I asked Muse Chat whether FindObjectOfType was the current recommendation in Unity 6. It told me to use FindAnyObjectByType or FindWithTag. Switched to FindWithTag. That took four minutes without touching a documentation page.
- The field of view calculation was initially wrong. The first Cursor version used Vector3.SignedAngle instead of Vector3.Angle for the FOV check. The result was an enemy that could see behind itself in a narrow cone. I described the behavior to Cursor and it identified the issue and corrected the calculation. Five minutes.
- NavMesh agent stopping at wrong distance during patrol. The agent.remainingDistance check returned zero while the path was still being calculated. Added the !agent.pathPending guard and it stopped the premature transition. I described the symptom to Muse Chat and it gave me the fix immediately. This was a Unity specific edge case that Muse Chat knew and Cursor did not initially suggest.
- The death handling caused a NullReferenceException when the game ended because the event was not being unsubscribed. Added the OnDisable unsubscription pattern. I had seen this pattern before but forgot to include it. Cursor added it when I described the error.
The Hour by Hour Breakdown
- Hour 1: State machine structure and basic Idle, Patrol, Chase states. Cursor generated the skeleton from a plain English description. I spent the first 20 minutes describing what I wanted and the next 40 testing and adjusting the generated code.
- Hour 2: Adding Attack state and the damage connection to the player. This took longer than expected because the attack logic depended on my existing PlayerHealth script which Cursor knew about through project context. Two iterations to get the damage call right.
- Hour 3: Field of view detection. This was the most complex part. The first Cursor implementation was functionally wrong. Two rounds of describing the problem and getting corrections before the FOV cone behaved correctly in all directions.
- Hour 4 and 22 minutes: Death handling, patrol point transitions, edge case fixes, and the Gizmos visualization. The Gizmos code was generated in one shot. The edge cases took the most iterations.
What I Learned About Working With AI on Game Systems
- Describe behavior not code. When something is wrong the most effective prompt is a description of what is happening versus what should be happening. Not fix the NavMesh. Tell me exactly what the enemy is doing wrong. The AI can work backward from behavior descriptions better than from vague fix requests.
- Test small before building big. I tested each state in isolation before wiring them together. The AI generates plausible code that occasionally has edge case bugs. Catching those in a two state system is much faster than untangling them in a five state system.
- Muse Chat and Cursor cover different failure modes. When the AI code had a Unity specific bug, Muse Chat found the fix faster. When the bug was a logic error in the state machine, Cursor found it faster because it could trace through the project context. Using both without documentation was genuinely viable for this system.
- Four hours and twenty two minutes is still four hours. The experiment proved AI tools significantly accelerate this kind of work. It did not prove AI tools make it trivial. The system still required real thinking, real testing, and real iteration. Just less of all three than doing it the old way.
Final Thoughts
The experiment answered my question. AI tools alone are sufficient to build a production quality Unity enemy AI system without external documentation. The total time was 4 hours 22 minutes. My honest estimate for doing the same thing the old way with full documentation access is 9 to 12 hours, based on how long similar systems have taken me previously. That gap is the actual value of the AI workflow. Not magic. Not instant. Just meaningfully, measurably faster.