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:
- This kit's grid A* — ~150 readable lines, zero dependencies, perfect for learning and for most student-scale games.
- 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).
- 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:
g— cost from start to each visited cell ("walked so far")Heuristic()— the octile guess (diagonal-aware straight-line distance; it never overestimates, which is what guarantees optimal paths)open— the frontier, scanned for the lowestg + heach roundcameFrom— the breadcrumbs, walked backward byReconstruct()
Two Unity-specific touches worth showing:
- No corner cutting: a diagonal step is only allowed if both adjacent straight cells are open — otherwise enemies visibly clip wall corners.
NearestWalkable(): when the player stands close to a wall, their cell may read blocked; aiming for the nearest open cell instead of failing makes chase feel robust.
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
- Lower
repathIntervalto 0.05 and raise it to 2.0 — feel the difference between twitchy and dopey. - Draw the path with a
LineRendererso playtesters can see enemy intent (great for tuning). - 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.
- 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.)