iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

Game AI Basics

Game AI for behaviour usually splits into perception (what the agent sees), decision (what to do about it), and action (how to do it). Finite state machines are the workhorse for low-complexity agents; behaviour trees scale better as you add reactivity. This example shows an FSM-driven guard NPC in plain JS for clarity.

A guard NPC with patrol / chase / search states

EXAMPLE
// Tiny FSM with on-enter, on-update, on-exit hooks
class FSM {
  constructor(states, initial) {
    this.states = states;
    this.current = initial;
    this.states[initial].onEnter?.(this);
  }
  transition(next) {
    if (next === this.current) return;
    this.states[this.current].onExit?.(this);
    this.current = next;
    this.states[next].onEnter?.(this);
  }
  update(dt, ctx) { this.states[this.current].onUpdate?.(this, dt, ctx); }
}

const guard = { pos: {x: 0, y: 0}, lastSeen: null, patrolIdx: 0 };
const waypoints = [{x:0,y:0},{x:10,y:0},{x:10,y:10},{x:0,y:10}];

function moveToward(p, target, dt, speed=2) {
  const dx = target.x - p.x, dy = target.y - p.y;
  const d = Math.hypot(dx, dy);
  if (d < 0.1) return true;
  p.x += dx / d * speed * dt;
  p.y += dy / d * speed * dt;
  return false;
}

const states = {
  patrol: {
    onUpdate(fsm, dt, ctx) {
      const wp = waypoints[guard.patrolIdx];
      if (moveToward(guard.pos, wp, dt)) {
        guard.patrolIdx = (guard.patrolIdx + 1) % waypoints.length;
      }
      if (ctx.canSeePlayer) fsm.transition('chase');
    },
  },
  chase: {
    onUpdate(fsm, dt, ctx) {
      if (ctx.canSeePlayer) {
        guard.lastSeen = { ...ctx.player };
        moveToward(guard.pos, ctx.player, dt, 4);
      } else {
        fsm.transition('search');
      }
    },
  },
  search: {
    timeLeft: 0,
    onEnter() { this.timeLeft = 5; },
    onUpdate(fsm, dt, ctx) {
      this.timeLeft -= dt;
      if (guard.lastSeen) moveToward(guard.pos, guard.lastSeen, dt, 2.5);
      if (ctx.canSeePlayer) fsm.transition('chase');
      else if (this.timeLeft <= 0) fsm.transition('patrol');
    },
  },
};

const fsm = new FSM(states, 'patrol');
// Game loop calls fsm.update(dt, { canSeePlayer, player }) each tick.

Why it matters

Per-state data (timers, counters) lives on the state object itself, not on the agent — that way the state owns its lifecycle and you do not have to remember to reset agent fields on transition. As the agent grows past five or six states, migrate to a behaviour tree before the transition graph becomes a hairball.

Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.

Example

Example
// FSM, Behaviour Tree, GOAP, Utility AI — pick by complexity.
// FSM is the right answer 80% of the time.
Try it Yourself »

Test yourself

Q1. The most common, robust game-AI structure is…
Q2. A* is mainly used for…
Q3. A common alternative to FSM for complex AI is…

Discussion

Loading…