game ai programming

Game AI programming — A* pathfinding, state machines, and behavior trees

📘 gamedev-academy · Part 9 of 15 · Module 3 · Advanced
A free, step-by-step course on buying, selling & trading domains. View the full series →
Game AI programming — A* pathfinding, state machines, and behavior trees
Key takeaways
  • Finite state machines handle 90% of game AI needs — patrol, chase, attack, flee with clean transition rules.
  • A* pathfinding combines actual cost with a heuristic estimate to find optimal paths on any grid or graph.
  • Behavior trees are modular decision trees: Selector tries alternatives, Sequence requires all children to succeed.

Understanding game ai programming is the foundation of this guide. Below we break it down step by step so you can act with confidence.

Game AI makes your NPCs feel alive. A well-designed AI system creates the illusion of intelligence using simple, composable rules.

This lesson covers the three pillars of game AI: finite state machines for decision-making, A* for pathfinding, and behavior trees for complex behaviors.

Game Ai Programming: Finite state machines

An FSM gives each entity a current state and rules for transitioning between states. It is the simplest and most effective AI pattern:

🔬 Demo: Enemy FSM — patrol, chase, attack
class EnemyFSM {
  constructor(entity) {
    this.entity = entity;
    this.state = 'patrol';
    this.patrolIndex = 0;
    this.patrolPoints = [
      { x: 100, y: 200 }, { x: 400, y: 200 },
      { x: 400, y: 400 }, { x: 100, y: 400 }
    ];
  }

  update(dt, player) {
    const dist = this.distTo(player);

    switch (this.state) {
      case 'patrol':
        this.moveToward(this.patrolPoints[this.patrolIndex], 80, dt);
        if (this.reachedPoint(this.patrolPoints[this.patrolIndex]))
          this.patrolIndex = (this.patrolIndex + 1) % this.patrolPoints.length;
        if (dist < 150) this.state = 'chase';
        break;

      case 'chase':
        this.moveToward(player, 140, dt);
        if (dist < 30) this.state = 'attack';
        if (dist > 250) this.state = 'patrol';
        break;

      case 'attack':
        player.health -= 20 * dt;
        if (dist > 40) this.state = 'chase';
        break;
    }
  }

  moveToward(target, speed, dt) {
    const dx = target.x - this.entity.x;
    const dy = target.y - this.entity.y;
    const len = Math.sqrt(dx * dx + dy * dy);
    if (len > 1) {
      this.entity.x += (dx / len) * speed * dt;
      this.entity.y += (dy / len) * speed * dt;
    }
  }

  reachedPoint(p) { return this.distTo(p) < 5; }
  distTo(p) {
    const dx = p.x - this.entity.x, dy = p.y - this.entity.y;
    return Math.sqrt(dx * dx + dy * dy);
  }
}

A* pathfinding

A* finds the shortest path on a grid by combining actual cost with a heuristic estimate of remaining distance:

🔬 Demo: A* pathfinding on a tile grid
function aStar(grid, start, goal) {
  const open = [start];
  const cameFrom = new Map();
  const gScore = new Map();
  const fScore = new Map();
  const key = (n) => `${n.x},${n.y}`;

  gScore.set(key(start), 0);
  fScore.set(key(start), heuristic(start, goal));

  while (open.length > 0) {
    // Node with lowest fScore
    open.sort((a, b) => (fScore.get(key(a)) || Infinity) - (fScore.get(key(b)) || Infinity));
    const current = open.shift();

    if (current.x === goal.x && current.y === goal.y)
      return reconstructPath(cameFrom, current);

    for (const neighbor of getNeighbors(grid, current)) {
      const tentG = (gScore.get(key(current)) || Infinity) + 1;
      if (tentG < (gScore.get(key(neighbor)) || Infinity)) {
        cameFrom.set(key(neighbor), current);
        gScore.set(key(neighbor), tentG);
        fScore.set(key(neighbor), tentG + heuristic(neighbor, goal));
        if (!open.some(n => n.x === neighbor.x && n.y === neighbor.y))
          open.push(neighbor);
      }
    }
  }
  return []; // no path
}

function heuristic(a, b) {
  return Math.abs(a.x - b.x) + Math.abs(a.y - b.y); // Manhattan
}

function getNeighbors(grid, node) {
  const dirs = [{x:0,y:-1},{x:0,y:1},{x:-1,y:0},{x:1,y:0}];
  return dirs
    .map(d => ({ x: node.x + d.x, y: node.y + d.y }))
    .filter(n => grid[n.y]?.[n.x] === 0); // 0 = walkable
}

function reconstructPath(cameFrom, current) {
  const path = [current];
  const key = (n) => `${n.x},${n.y}`;
  while (cameFrom.has(key(current))) {
    current = cameFrom.get(key(current));
    path.unshift(current);
  }
  return path;
}

Behavior trees

Behavior trees are a modular, reusable alternative to FSMs for complex AI. Each node returns SUCCESS, FAILURE, or RUNNING:

🔬 Demo: Behavior tree for an NPC guard
const SUCCESS = 'success', FAILURE = 'failure', RUNNING = 'running';

class Selector {
  constructor(...children) { this.children = children; }
  tick(ctx) {
    for (const c of this.children) {
      const r = c.tick(ctx);
      if (r !== FAILURE) return r;
    }
    return FAILURE;
  }
}

class Sequence {
  constructor(...children) { this.children = children; }
  tick(ctx) {
    for (const c of this.children) {
      const r = c.tick(ctx);
      if (r !== SUCCESS) return r;
    }
    return SUCCESS;
  }
}

class Condition {
  constructor(fn) { this.fn = fn; }
  tick(ctx) { return this.fn(ctx) ? SUCCESS : FAILURE; }
}

class Action {
  constructor(fn) { this.fn = fn; }
  tick(ctx) { return this.fn(ctx); }
}

// Guard AI: attack if enemy close, else patrol
const guardAI = new Selector(
  new Sequence(
    new Condition(ctx => ctx.distToPlayer < 50),
    new Action(ctx => { ctx.enemy.attack(); return SUCCESS; })
  ),
  new Sequence(
    new Condition(ctx => ctx.distToPlayer < 200),
    new Action(ctx => { ctx.enemy.chasePlayer(); return RUNNING; })
  ),
  new Action(ctx => { ctx.enemy.patrol(); return RUNNING; })
);

When evaluating game ai programming, start with the fundamentals: what problem it solves, who it serves, and how it compares to the alternatives buyers consider first.

Start with the fundamentals. Define who you are serving, the single outcome you want, and the budget you are comfortable committing before you compare any options.

A practical rule is to favour clarity over cleverness. The choice that is easiest to understand, spell, remember and act on tends to win over the long run.

Context changes the answer. What is optimal for a fast-moving startup is rarely optimal for an established brand, so weigh your own stage, market and ambitions.

Avoid the common mistakes: rushing the decision, copying competitors blindly, and ignoring the total cost of ownership beyond the very first year of use.

Think in terms of trade-offs rather than a single perfect answer. Every option trades memorability, cost, reach and flexibility in a slightly different mix.

Document your reasoning. Writing down why you chose one path makes it far easier to revisit the decision later and to explain it to partners and investors.

Test with real people. A quick check with a handful of your actual customers will surface problems that no amount of internal debate ever reveals.

Plan for growth. The option that fits today should still make sense when your audience, catalogue or team is several times larger than it is right now.

Finally, revisit the decision periodically. Markets, tools and best practices move quickly, and a choice that was ideal last year may deserve a fresh look today.

Explore Crypto Trading

Cryptocurrency trading is one of the fastest-growing financial markets — open 24/7, globally accessible, and full of opportunity for those who learn the fundamentals first.

  • Trade spot, futures & copy-trading on one platform
  • Start with as little as $10 — practice risk-free on the testnet
  • Built-in TradingView charts, grid bots & DCA tools

Free Crypto Trading Academy → · Domain Trading Academy →

Frequently asked questions

What is the difference between FSM and behavior trees?

FSMs are simpler and work well for enemies with a few states. Behavior trees scale better for complex AI with many conditional behaviors — they are modular and reusable, while FSMs become spaghetti with many states.

Is A* expensive for many enemies?

A* on a small grid (100x100) takes under 1ms. For many enemies, recalculate paths every few frames instead of every frame, and use hierarchical pathfinding for large worlds.

Free Courses — Learn New Skills

No sign-up · No fees · Learn at your own pace

Start Trading

Trade crypto and premium domains — digital assets with real opportunities.

Find your name on Atom

DominantBrand curates the best premium, brandable names from Atom.com — the marketplace with a free AI appraisal, a USPTO trademark check, and secure escrow. Every listing even ships with a designed logo.

Browse Domains