Enemy AI — the illusion of thought is mostly what the enemy does not know
An enemy that always knows where you are is not difficult, it is cheap. It turns to face you through a wall. It starts moving before you round the corner. Every death it hands you feels arbitrary, because you were never given the information you would have needed to avoid it. The fix is not smarter decisions — the decision layer, the state machine, is probably fine already. What is missing is everything feeding it: what this enemy is allowed to sense, what it still believes after the sensing stops, how long it takes to act on a belief, and what happens when four of its friends want the same thing at the same moment. This shelf builds that layer, one gate at a time, and every part ends with something you can walk in front of and watch.
What this shelf teaches#
| Part | You build | The idea |
|---|---|---|
| 01 | A patrolling enemy with three perception gates: range, vision arc, line of sight. | Ignorance is a feature you build — three independent gates make blind spots learnable. |
| 02 | A memory of last known position, time since seen, and a decaying confidence value. | Belief persists and decays after sensing stops — that gap reads as a mind. |
| 03 | A reaction delay, a telegraphed wind-up, an on-screen belief tell, two perception-only difficulty presets. | Difficulty belongs in the information channel, not the stat sheet. |
| 04 | A coordinator handing out a fixed number of attack tokens per room. | Five individually correct enemies can still produce an incorrect fight. |
In part 01, the enemy spots you only when all three gates agree and loses you the instant one stops being true — hide behind a crate and watch it keep walking. That independence is what makes each gate tunable. In part 02, breaking line of sight sends the enemy to your last position to search before it gives up and returns to patrol, and a thrown object writes to that same memory from across the room: sensing is instant and true, belief persists and can be wrong. Part 03's numbers matter beyond the tell — an enemy with more health is the same fight for longer, while one that reacts faster is a different fight. Part 04's tokens do the coordinating: holders commit and attack while everyone else takes an angular slot around you and applies pressure, and tokens release on death and expire on timeout.
Where reasonable people differ#
Four forks run through this shelf. None has a right answer, and picking wrong is recoverable — picking without noticing you had a choice is what hurts later.
RayCast2D node, or a direct space-state query. The node sits in the scene tree, shows up in the editor, and draws itself while you debug. The query needs no node at all and lets one enemy fire six rays in a single frame. They also disagree on conventions in ways that bite: the node's target_position is local, the query's from/to are global, and the query returns an empty Dictionary on a miss rather than a null collider. Deciding question: is this one ray on a fixed offset, or a variable number of rays per frame?
A smooth confidence value, or discrete awareness stages. A float that rises and decays gives you free hysteresis at the cone's edge and a ramp you can put straight on screen as a meter. Named stages — unaware, suspicious, alert — give you a small number of distinct poses and sounds the player learns by shape. Deciding question: does the player need to read a continuous meter, or to recognise a pose across a room?
A fixed reaction delay, or a randomized range. Fixed is learnable, and a player who has learned it can beat the enemy on rhythm — which may be exactly what you want, or exactly the exploit you are trying to close. A range removes the rhythm at the cost of making the enemy harder to read at all. Deciding question: is the player meant to master this specific enemy, or to stay uneasy around it?
A global coordinator, or one per room. An autoload is reachable from anywhere and survives scene changes, at the usual cost of global state — see /systems/event-bus/00-overview for the tracing problems that come with it. A node scoped to the arena keeps the pool local and lets each encounter set its own token count, but every enemy needs a reference to it. Deciding question: do token counts differ between encounters? If they do, the per-room node wins immediately.
Prerequisites#
- Godot 4.7, 2D, Godot Physics. Typed GDScript throughout.
- Comfort with
CharacterBody2D,_physics_process, and moving something around a room under its own power. - Collision layers and masks — not mastery, but you should know that a mask says what a body detects and a layer says what it is. Part 01 leans on that distinction hard.
- A decision layer of some kind. Every part here writes data that something else reads and acts on. If the enemy is still a pile of
ifstatements, build /systems/state-machine/02-enum-machine first — it is one file and takes an evening. - Signals, at the level of connecting one and knowing when it fires. Part 02 depends on knowing that
body_enteredis an edge event, not a continuous truth.
Part 01 stands alone if all you want is honest vision. Parts 02 through 04 each assume the one before it, because each one is built on top of a problem the previous part created.
Where this shows up in the projects#
Precision Platformer has no enemies yet, but its collision and physics work is the substrate this shelf sits on — if the raycast conventions in part 01 feel unfamiliar, the stages there cover the same space from the movement side.
Next: Part 01 — What the enemy is allowed to see. Start there even if your enemy already chases correctly — part 01 opens by building the version you probably already have, then walking behind a wall to show you what it costs.