04 — A* for 2D Top-Down

Run the top-down demo. The enemy patrols room corners; step into its sight and it threads doorways to reach you. Select the Enemy to see its magenta path; select Grid Pathfinder to see the baked grid (red = blocked cells).

Why not NavMesh in 2D?

Unity's NavMesh is built for 3D geometry viewed from any angle; it doesn't bake from 2D colliders. Options for 2D, in the order you should consider them:

  1. This kit's grid A* — ~150 readable lines, zero dependencies, perfect for learning and for most student-scale games.
  2. A* Pathfinding Project (free tier) — the production standard: optimized grids, graph updates, local avoidance. Same concepts, industrial engineering. Graduate here when levels get big (2D guide).
  3. Community NavMesh-for-2D adapters (e.g. NavMeshPlus) — clever, but you're bending a 3D tool; know why before choosing it.

A* in plain language

You're in a maze, you know roughly which direction the exit is. You explore outward, always continuing from the frontier spot whose (distance walked so far) + (straight-line guess to the goal) is smallest. The guess — the heuristic — is what keeps you from flooding the whole maze like blind search would; the walked-so-far part is what keeps the route optimal. When you step on the goal, walk your breadcrumbs backward: that's the path.

That's the entire algorithm. In GridPathfinder2D.FindPath:

Two Unity-specific touches worth showing:

The grid bake — and the static-collider trick

Bake() walks the grid once at Start and marks a cell blocked if a collider overlaps it — but only colliders with no Rigidbody2D. Walls (static) block cells; the player and enemies (rigidbodies) don't punch moving holes in their own map. No layer setup, no tags — one line of physics insight:

if (c.attachedRigidbody == null && !c.isTrigger) walkable[x, y] = false;

The enemy — same brain, new legs

Open EnemyAI2D.cs next to EnemyAI3D.cs. The FSM is deliberately near-identical — Patrol/Chase/Attack, the same lose-sight timer, the same tints. What changed is only the movement layer: instead of agent.SetDestination, the enemy asks the pathfinder for a List<Vector2> and walks it node by node in FixedUpdate; instead of a vision cone it uses radius + a wall-blocking linecast.

One new idea: repath on a timer (every 0.5 s while chasing), because the player moves but recomputing every frame buys nothing visible and costs plenty. "How often should AI think?" is a real design dial — most shipped games tick AI far less often than every frame.

Exercises

  1. Lower repathInterval to 0.05 and raise it to 2.0 — feel the difference between twitchy and dopey.
  2. Draw the path with a LineRenderer so playtesters can see enemy intent (great for tuning).
  3. The open-list scan is O(n) on purpose (clarity first). Swap in a binary-heap priority queue and measure with the Profiler — a perfect data-structures crossover assignment.
  4. Add a second enemy sharing the same pathfinder. Then ten. When does it chug? (That's your cue for lesson item 2 above, and eventually the A* Pathfinding Project.)

Next: 05 - Platformer Enemies Beyond Back-and-Forth