Game AI skill for Codex
Enemies that read well and stay on budget. Codex starts from player-facing tells and tunable numbers, then picks the lightest structure that fits: FSM, hierarchical FSM, behaviour tree or utility scoring. It uses the engine's navmesh where one exists, writes admissible A* for grids, gives agents perception and memory instead of omniscience, staggers decisions to fit the frame budget and adds debug draws that show what each agent is thinking.
curl -fsSL codexskills.dev/install.sh \ | sh -s -- game-ai Installs to ~/.agents/skills/game-ai/ for every project on this machine.
curl -fsSL codexskills.dev/cli.mjs -o codex-skills.mjsnode codex-skills.mjs install game-ai The CLI also searches, updates and removes skills. Run it with no arguments to pick from a list. Needs Node 20.12+.
curl -fsSL codexskills.dev/install.sh \ | sh -s -- --project game-ai Installs to .agents/skills/game-ai/ in the current repository. Commit it and everyone who runs Codex there gets it.
curl -fsSLO codexskills.dev/downloads/game-ai.zipunzip -o game-ai.zip -d ~/.agents/skills/ The archive holds the skill folder with its generated agents/openai.yaml. Unzip into .agents/skills/ to scope it to a repo.
& ([scriptblock]::Create((irm codexskills.dev/install.ps1))) game-ai PowerShell 5.1 or 7. Installs to %USERPROFILE%\.agents\skills\. Add -Project for the current repo.
104-line POSIX script. No sudo, no telemetry. Read install.sh
Use it in Codex
Start a prompt with $game-ai, or pick it from /skills.
You can also just describe the task: Codex reads the description below and loads the playbook when your request matches.
What Codex reads before loading it
Builds NPC and enemy AI for games - finite state machines, hierarchical states, behavior trees with blackboards, utility AI scoring, A* pathfinding on grids and navmeshes with good heuristics, steering and avoidance, perception (sight cones, hearing, memory) and AI tick budgeting - using engine tools where they exist (Unity NavMesh, Godot NavigationAgent, Unreal Behavior Trees and EQS). Use when enemies need to patrol, chase, flee or take cover, pathfinding is slow or produces ugly paths, AI logic has become a tangle of booleans, or many agents tank the frame rate.
Use it when
- Designing enemy or NPC decision making
- Pathfinding is slow, produces ugly paths or agents get stuck
- Enemy logic has grown into tangled booleans
- Many agents push AI time over the frame budget
Use something else when
- You need the overall game flow state machine, not agent AI: use game-loop-architecture
- Agents are LLM-driven chat characters: use agent-tool-design
How to check the result
Debug draws show each agent's state, cone and path; scripted hide-and-seek scenarios behave as specified; path tests pass; AI time at max agents stays within its frame slice.
The mistake it prevents
Letting AI read the player's exact position every frame, which makes enemies omniscient and unfair instead of reacting to what they could perceive.
Done well means: AI is fun and readable to the player before it is smart, each agent’s decision structure matches its complexity (FSM for simple, behaviour tree or utility for rich), pathfinding uses the engine’s navigation when available, decisions run on a budget instead of every frame, and a debug view shows what every agent is thinking.
Before writing code
- Read
AGENTS.mdand check what the engine provides: Unity AI Navigation package (NavMeshAgent,NavMeshSurface), GodotNavigationRegion2D/3D+NavigationAgent2D/3D, Unreal Behavior Trees, Blackboards, EQS andAIController. Use them instead of hand-rolling. - Write the behaviour spec as player-facing tells: “Guard patrols, hears footsteps within 8 m, investigates for 5 s, chases on sight, gives up after losing sight for 4 s, telegraphs attacks for 0.6 s.” Designers tune these numbers; expose them as data.
Choose a decision structure
| Agent complexity | Structure |
|---|---|
| 2-6 clear modes (patrol, chase, attack, flee) | Finite state machine |
| Modes with sub-modes (combat: approach, strafe, reload) | Hierarchical FSM |
| Many conditional behaviours, reused across enemy types, designer-authored | Behaviour tree with blackboard |
| Many competing desires (hunger, threat, ammo, cover) choosing the best | Utility AI (score each action, pick highest) |
| Long plans with preconditions (crafting, colony sims) | GOAP or HTN planner; expensive, use sparingly |
Start with the simplest that fits. Most action-game enemies are an FSM plus good animation tells.
FSM with explicit transitions
type State = "patrol" | "investigate" | "chase" | "attack" | "return";
function think(a: Agent, w: WorldView, dt: number) {
a.stateTime += dt;
const sees = canSee(a, w.player);
if (sees) a.lastSeen = { pos: w.player.pos.clone(), time: w.time };
switch (a.state) {
case "patrol":
followPatrol(a);
if (sees) return enter(a, "chase");
if (a.heardNoise) return enter(a, "investigate");
break;
case "investigate":
moveTo(a, a.heardNoise!.pos);
if (sees) return enter(a, "chase");
if (a.stateTime > a.cfg.investigateSeconds) return enter(a, "return");
break;
case "chase":
moveTo(a, a.lastSeen!.pos);
if (sees && dist(a.pos, w.player.pos) < a.cfg.attackRange) return enter(a, "attack");
if (w.time - a.lastSeen!.time > a.cfg.loseSightSeconds) return enter(a, "investigate");
break;
case "attack":
faceTowards(a, w.player.pos);
if (a.stateTime > a.cfg.windupSeconds) performAttack(a);
if (a.stateTime > a.cfg.windupSeconds + a.cfg.recoverSeconds) return enter(a, "chase");
break;
case "return":
moveTo(a, a.patrolPoints[a.patrolIndex]);
if (sees) return enter(a, "chase");
if (arrived(a)) return enter(a, "patrol");
break;
}
}
function enter(a: Agent, s: State) { a.state = s; a.stateTime = 0; a.onEnter?.(s); }
Rules: transitions live in one place per state; entering a state resets its timers; add hysteresis (different enter and exit distances) so agents do not flicker between states.
Behaviour trees
- Composites: Sequence (all succeed in order), Selector (first that succeeds), Parallel. Decorators: Inverter, Cooldown, Timeout, Repeat. Leaves: Conditions and Actions returning Success, Failure or Running.
- Shared data goes on a blackboard (
targetActor,lastKnownPosition,coverPoint), not in node fields, so subtrees are reusable. - Re-evaluate higher-priority branches while a lower one is running (Unreal: decorator “Observer aborts: Lower Priority”); otherwise an agent finishes a long patrol leg while being shot.
- Keep trees shallow and readable; a 200-node tree is a design smell, split into subtrees.
Utility AI
const actions = [
{ name: "attack", score: (a) => a.hasTarget ? curve(a.targetDistance, 0, a.cfg.range) * a.ammo01 : 0 },
{ name: "reload", score: (a) => 1 - a.ammo01 },
{ name: "takeCover", score: (a) => a.threat01 * (1 - a.health01) },
{ name: "heal", score: (a) => a.hasMedkit ? Math.pow(1 - a.health01, 2) : 0 },
];
const best = actions.reduce((x, y) => (y.score(a) > x.score(a) ? y : x));
Normalise every input to 0-1, shape it with response curves, add a small bonus for the current action (inertia) to avoid dithering, and log scores in the debug view.
Pathfinding
| World | Approach |
|---|---|
| Engine with navigation | Bake a navmesh; use NavMeshAgent / NavigationAgent / MoveTo |
| Tile grid | A* on the grid, 8-directional with octile heuristic; smooth with line-of-sight string pulling |
| Large open grid, many agents to one target | Flow field (one Dijkstra from target, all agents follow gradient) |
| Huge maps | Hierarchical A* (clusters/portals) or precomputed region graph |
A* on a grid with a binary heap:
import heapq, math
def octile(a, b):
dx, dy = abs(a[0] - b[0]), abs(a[1] - b[1])
return (dx + dy) + (math.sqrt(2) - 2) * min(dx, dy)
def astar(start, goal, passable, cost=lambda a, b: 1.0):
open_heap = [(octile(start, goal), 0.0, start)]
came, g = {start: None}, {start: 0.0}
while open_heap:
_, gc, cur = heapq.heappop(open_heap)
if cur == goal:
path = []
while cur is not None:
path.append(cur); cur = came[cur]
return path[::-1]
if gc > g[cur]:
continue # stale heap entry
x, y = cur
for dx, dy in ((1,0),(-1,0),(0,1),(0,-1),(1,1),(1,-1),(-1,1),(-1,-1)):
nxt = (x + dx, y + dy)
if not passable(nxt):
continue
if dx and dy and not (passable((x + dx, y)) and passable((x, y + dy))):
continue # no corner cutting
step = (math.sqrt(2) if dx and dy else 1.0) * cost(cur, nxt)
ng = gc + step
if ng < g.get(nxt, math.inf):
g[nxt], came[nxt] = ng, cur
heapq.heappush(open_heap, (ng + octile(nxt, goal), ng, nxt))
return None
- The heuristic must never overestimate (admissible) for optimal paths; Manhattan on an 8-directional grid overestimates diagonals and produces odd paths.
- Weighted A* (
h * 1.2) is faster and slightly suboptimal; often a good trade for games. - Cap search nodes per request and return partial paths toward the goal when exceeded.
Steering and crowds
- Path following: seek the next waypoint, advance when within a radius, and slow down on arrival (arrive behaviour) to avoid orbiting the target.
- Avoidance: engine RVO (Unity
NavMeshAgentobstacle avoidance, Godotavoidance_enabled) or separation forces between neighbours from a spatial hash. - Do not path every agent every frame. Repath when the target moved more than a threshold or on a timer (0.25-1 s), staggered across agents.
Perception
- Sight: distance check, then cone (
dot(forward, dirToTarget) > cos(halfAngle)), then a raycast for occlusion, in that order (cheapest first). - Hearing: gameplay noise events with radius, not audio playback.
- Memory: last known position plus timestamp; agents search there instead of knowing where the player is. Omniscient AI feels unfair.
Budget and performance
- Tick decisions at 5-10 Hz and stagger agents (
agentIndex % 6 == frame % 6); movement and animation still update every frame. - Level of detail: off-screen or distant agents think less often or use simplified logic.
- Batch raycasts and pathfinding requests; engines offer async path queries.
Gotchas
- Perfect aim and reaction are not fun. Add reaction delay (200-400 ms), aim error that shrinks over time, and attack tokens so only 1-2 enemies attack at once.
- Agents stuck on corners: navmesh agent radius smaller than the collider, or navmesh not rebaked after level changes.
- AI reading the player’s exact position every frame bypasses perception; route through memory.
Verification
- Debug draw: state label above each agent, sight cones, current path, blackboard values or utility scores.
- Scripted scenarios: player hides behind a wall, agent investigates last known position and gives up after the configured time.
- Pathfinding unit tests on small grids: blocked goal returns null, no corner cutting, path cost equals expected optimum.
- Profile with max agent count: AI time under its slice of the frame budget (e.g. 2 ms).
Hand-off summary
**Guard AI:** FSM patrol/investigate/chase/attack/return; config in GuardConfig asset
**Perception:** 90 deg cone, 15 m sight, raycast LOS; hearing via noise events; memory 4 s
**Navigation:** Godot NavigationAgent3D, repath every 0.5 s staggered; avoidance on
**Fairness:** 300 ms reaction, 0.6 s attack windup, max 2 attackers via tokens
**Perf:** 40 guards, AI 1.1 ms/frame at 8 Hz decisions What the zip contains
SKILL.md plus the interface file below. Nothing runs on install.
agents/openai.yaml
Gives the Codex app a display name, a one-line description and a starter prompt for this skill.
interface:
display_name: "Game AI"
short_description: "Enemies that read well and stay on budget"
default_prompt: "$game-ai Make guards that patrol, investigate noises and chase the player on sight" About this skill
Written and maintained by the Codex Skills editors to the same editorial standard as every skill here: a selective description, a real workflow, known pitfalls and a way to verify the work. First published , last updated . MIT licensed. Something wrong or out of date? Tell us.