Minimal Pac-Man-lite (Unity / C#)

The step-7 project in overview-arcade-classics-as-learning-projects and the capstone of the route. It is the first project with real AI, and it brings together the skills the canon reference (see source-classic-games-canon) lists for Pac-Man: tilemaps, simple pathfinding, finite state machines, and pickups.

Original project inspired by a recognisable pattern — not a clone.

Files

ScriptResponsibilityKey wiki concept
MazeGrid.csTile maze: walls, pellets, cell↔world, BFS distanceunity-tilemap, pathfinding-algorithms, csharp-collections
GridMover.csBase: glides actors cell-to-cell, asks subclass which waycsharp-inheritance
PacPlayer.csBuffered grid input; eats pellets on arrivalunity-input
Ghost.csScatter / Chase / Frightened FSM choosing a BFS targetai-state-machine-pattern
PacManGame.csPhase rhythm, power-pellet mode, score, lives, win/loseunity-gamemanager-pattern

How the three step-7 ideas show up

  • Tilemap. MazeGrid is a grid of walls and pellets with CellToWorld / IsWalkable. It is generated as a connected “pillar” maze so it is always solvable; the README below shows how to swap in a hand-authored layout or a real unity-tilemap.
  • Pathfinding. MazeGrid.Distance is a breadth-first search. A ghost looks at its walkable neighbours and steps toward the one with the best BFS distance to its target — simple, but genuine pathfinding. For bigger maps this is where A* on pathfinding-algorithms takes over.
  • Finite state machine. Ghost has three states. Each picks a different target (Chase → the player, Scatter → a home corner, Frightened → away from the player), while the movement code stays identical. That separation of “what do I want?” from “how do I move?” is the heart of ai-state-machine-pattern.

GridMover is shared by the player and the ghosts via csharp-inheritance: the gliding-between-cells mechanism is written once, and only the per-cell decision differs (read input vs run the FSM).

Scene setup (about 12 minutes)

  1. New 2D scene, Main Camera set to Orthographic (size ~4).
  2. Tile prefabs — four square sprites, each dragged into the Project window as a prefab (delete the scene copies). No scripts or colliders needed:
    • Wall (solid colour), Pellet (small dot), PowerPellet (bigger dot).
  3. Player prefab: a sprite with the PacPlayer script. Make it a prefab.
  4. Ghost prefab: a sprite with the Ghost script. Make it a prefab.
  5. Maze: empty GameObject Maze at the origin, add MazeGrid, and assign the Wall / Pellet / Power Pellet prefabs. Width and height should be odd (defaults 15 × 11).
  6. Game: empty GameObject Game, add PacManGame, assign the Maze, the Player Prefab, and the Ghost Prefab.
  7. Press Play. WASD or arrows steer; eat all pellets to clear the level. A power pellet turns the ghosts blue (Frightened) so you can send them home for bonus points; otherwise a ghost costs a life. Score, lives, and pellet count print to the Console.

The player and ghosts are spawned at the markers MazeGrid chooses, so you do not place them in the scene yourself.

Why this is the capstone

Every earlier step fed into this one: grid movement (Snake), timed phases (Space Invaders), state ownership (the game controllers), and now pathfinding and a state machine driving an opponent that reacts to you. Tuning scatterTime, chaseTime, frightenedTime, and the ghost speed changes the whole feel of the hunt — see game-feel and smooth-learning-curves.

Extension ladder

  1. On-screen score and lives — replace Debug.Log with UI/TMP text.
  2. Four ghost personalities — give each ghost a different Chase target (ahead of the player, a mirror of another ghost, etc.) as the original does.
  3. A pathfinding* — swap the BFS for A* from pathfinding-algorithms and compare on a larger maze.
  4. Author the maze — feed MazeGrid a string[] layout (or a real unity-tilemap) instead of generating pillars, and parse #/./o/P/G.
  5. Frightened polish — slow the ghosts and flash them near the end of the timer; classic game-feel cues.

Source files

5 items under this folder.