Back to Game Development

Module 5: Game AI Programming

Master Finite State Machines, Behavior Trees, NavMesh, and create intelligent enemies

πŸ€– What is Game AI?

Game AI isn't about creating sentient robots - it's about making NPCs (non-player characters) behave believably! Good AI challenges players, reacts to situations, and creates engaging gameplay. It's the difference between boring enemies and memorable encounters!

Simple Definition

Game AI is code that makes characters appear intelligent by making decisions based on game state. It's about creating the illusion of intelligence, not actual intelligence!

Good AI should:

β€’ Be challenging but fair

β€’ React to player actions

β€’ Have personality and variety

β€’ Be predictable enough to learn

β€’ Run efficiently (not lag the game)

Why Learn Game AI?

Engaging Gameplay

Smart enemies make games more fun and replayable

Problem Solving

Learn to break complex behaviors into simple rules

Reusable Patterns

FSM and Behavior Trees apply to many game types

Industry Standard

These techniques are used in AAA games

πŸ“š Learn More:

πŸ”„ Finite State Machines (FSM)

A Finite State Machine is like a flowchart for AI behavior. The AI is always in ONE state (Idle, Patrol, Chase, Attack) and transitions between states based on conditions. It's simple, efficient, and perfect for most game AI!

FSM Concept

Think of a guard AI: Normally Patrolling. Sees player β†’ Chase. Gets close β†’ Attack. Loses sight β†’ Search. Can't find player β†’Patrol again.

States: What the AI is doing right now

Transitions: Conditions that change states

Actions: What happens in each state

Implementing FSM in Unity

// EnemyAI.cs - Simple FSM implementation

using UnityEngine;

using UnityEngine.AI;

public class EnemyAI : MonoBehaviour

{

// States enum

enum State { Idle, Patrol, Chase, Attack, Search }

private State currentState = State.Idle;

// References

public Transform player;

public Transform[] patrolPoints;

private NavMeshAgent agent;

// Settings

public float detectionRange = 10f;

public float attackRange = 2f;

private int currentPatrolIndex = 0;

void Start()

{

agent = GetComponent<NavMeshAgent>();

currentState = State.Patrol;

}

void Update()

{

// State machine logic

switch (currentState)

{

case State.Idle:

IdleState();

break;

case State.Patrol:

PatrolState();

break;

case State.Chase:

ChaseState();

break;

case State.Attack:

AttackState();

break;

}

}

void IdleState()

{

// Do nothing, wait

if (CanSeePlayer())

currentState = State.Chase;

}

void PatrolState()

{

// Move to patrol points

if (agent.remainingDistance < 0.5f)

{

currentPatrolIndex = (currentPatrolIndex + 1) % patrolPoints.Length;

agent.SetDestination(patrolPoints[currentPatrolIndex].position);

}

// Check for player

if (CanSeePlayer())

currentState = State.Chase;

}

void ChaseState()

{

// Follow player

agent.SetDestination(player.position);

float distanceToPlayer = Vector3.Distance(transform.position, player.position);

if (distanceToPlayer < attackRange)

currentState = State.Attack;

else if (!CanSeePlayer())

currentState = State.Patrol;

}

void AttackState()

{

// Stop and attack

agent.SetDestination(transform.position);

transform.LookAt(player);

// Attack logic here

Debug.Log("Attacking player!");

float distanceToPlayer = Vector3.Distance(transform.position, player.position);

if (distanceToPlayer > attackRange)

currentState = State.Chase;

}

bool CanSeePlayer()

{

float distance = Vector3.Distance(transform.position, player.position);

if (distance < detectionRange)

{

Vector3 direction = (player.position - transform.position).normalized;

RaycastHit hit;

if (Physics.Raycast(transform.position, direction, out hit, detectionRange))

{

return hit.transform == player;

}

}

return false;

}

}

πŸ’‘ FSM Best Practices:

  • β€’ Keep states simple - one clear purpose per state
  • β€’ Use enums for state names (cleaner than strings)
  • β€’ Add Enter/Exit methods for state transitions
  • β€’ Visualize your FSM on paper before coding

🌳 Behavior Trees

Behavior Trees are more flexible than FSMs! They're hierarchical structures that let you combine simple behaviors into complex AI. Think of it like a decision tree - the AI evaluates conditions and executes actions based on priorities.

Behavior Tree Nodes

Composite Nodes

Sequence: Execute children in order, stop if one fails

Selector: Try children until one succeeds

Parallel: Execute multiple children at once

Leaf Nodes

Action: Do something (move, attack, etc.)

Condition: Check something (can see player?)

Decorator: Modify child behavior (repeat, invert)

Example Tree: Enemy AI that patrols, chases if sees player, attacks if close

Selector (try in order until one succeeds)

β”œβ”€ Sequence (attack if all conditions met)

β”‚ β”œβ”€ Condition: Can see player?

β”‚ β”œβ”€ Condition: In attack range?

β”‚ └─ Action: Attack

β”œβ”€ Sequence (chase if can see)

β”‚ β”œβ”€ Condition: Can see player?

β”‚ └─ Action: Chase player

└─ Action: Patrol

🎯 When to Use Behavior Trees:

  • β€’ Complex AI with many behaviors
  • β€’ Need to combine and reuse behaviors
  • β€’ Want visual editing (many tools available)
  • β€’ Team collaboration (designers can edit trees)

πŸ—ΊοΈ Navigation Meshes (NavMesh)

NavMesh is Unity's built-in pathfinding system. It creates a walkable surface mesh that AI can navigate. No need to code A* yourself - Unity handles it all!

Setting Up NavMesh

Step 1: Mark Static Objects

1. Select ground, floors, ramps (walkable surfaces)

2. In Inspector, check "Navigation Static"

3. Select obstacles (walls, buildings)

4. Mark as "Navigation Static" too

Step 2: Bake NavMesh

1. Window β†’ AI β†’ Navigation

2. Go to Bake tab

3. Adjust settings (Agent Radius, Height, Max Slope)

4. Click "Bake" button

5. Blue overlay shows walkable areas!

Step 3: Add NavMeshAgent

1. Select AI character

2. Add Component β†’ NavMesh Agent

3. Adjust Speed, Acceleration, Stopping Distance

4. Set destination in code!

// Advanced NavMesh usage

using UnityEngine.AI;

NavMeshAgent agent = GetComponent<NavMeshAgent>();

// Set destination

agent.SetDestination(targetPosition);

// Check if reached destination

if (!agent.pathPending && agent.remainingDistance < 0.5f)

{

Debug.Log("Arrived!");

}

// Stop moving

agent.isStopped = true;

// Resume moving

agent.isStopped = false;

// Check if path is valid

if (agent.hasPath)

{

// Path exists!

}

πŸ‘Ύ Enemy AI Patterns

Different enemy types need different behaviors! Let's explore common AI patterns used in games.

Patrol Enemy

Moves between waypoints, chases if player gets close. Classic guard behavior!

Turret Enemy

Stationary, rotates to face player, shoots when in range. Simple but effective!

Swarm Enemy

Moves in groups, surrounds player. Weak individually, dangerous in numbers!

Boss Enemy

Multiple phases, different attacks, telegraphed moves. Memorable encounters!

πŸŽ“ Complete AI Enemy Example

Combining FSM, NavMesh, and smart behaviors creates engaging enemies that challenge players while feeling fair and learnable!

πŸ“š Learning Resources

Game AI

Advanced Topics

🎯 What's Next?

You now understand game AI programming! In the next module, we'll dive into UI/UX and Audio - learning to create menus, HUDs, inventory systems, and integrate sound effects and music!