Pac-Man

A faithful Pac-Man arcade clone in Rust with SDL2 and Bevy ECS, compiled to WebAssembly for browser play.

Game Active Updated 13h ago

Pac-Man is a faithful recreation of the 1980 arcade game, written in Rust. SDL2 handles graphics, audio, and input; Bevy ECS runs the simulation as a standalone library with no game engine wrapped around it; and the whole thing compiles to WebAssembly through Emscripten to run in a browser with no plugins. The aim was accuracy without shortcuts: the ghosts behave the way the originals did, overflow bugs and all.

The ghosts

The four ghosts are the heart of the original, and the part easiest to get subtly wrong. Each has a documented personality, expressed entirely through how it picks a target tile:

  • Blinky targets Pac-Man's tile directly.

  • Pinky aims four tiles ahead of Pac-Man.

  • Inky takes the point two tiles ahead of Pac-Man, draws a vector from Blinky to it, and doubles that vector -- the only ghost whose target depends on another ghost.

  • Clyde chases like Blinky when far away, but breaks for his home corner once he is within eight tiles.

The detail worth dwelling on is the bug. The original arcade code had an overflow error: when Pac-Man faced up, the tiles-ahead offset for Pinky and Inky also shifted to the left. This clone reproduces it on purpose -- removing the bug would make the ghosts behave wrong relative to the arcade. The targeting, the scatter-versus-chase mode switching, and Blinky's Cruise Elroy speedup all follow the Pac-Man Dossier, the canonical reverse-engineering of the original.

ECS without the engine

The game wants the data-oriented structure of an entity-component-system, but none of the renderer, windowing, or asset machinery a full engine brings. So it uses bevy_ecs standalone -- the World and Schedule types, and nothing else from Bevy. SDL2 owns all I/O.

Every frame inserts a DeltaTime resource and runs a single schedule: Input, Update, Respond, Animation, Draw, Present, with the gameplay stages gated behind a pause state. SDL2's Canvas, TextureCreator, and EventPump are not thread-safe, so they live as NonSend resources -- the ECS statically guarantees they never cross a thread boundary. Systems communicate through events and observers rather than direct calls, which keeps collision, scoring, and audio decoupled.

The maze as a graph

The maze is stored as ASCII art in a constants file. At startup it is parsed into a graph of nodes and edges, and each edge carries a TraversalFlags bitfield saying who may cross it: Pac-Man, ghosts, or both. That single mechanism is how the ghost-house door lets ghosts out while keeping Pac-Man from wandering in, with no special cases. Ghost routing runs the pathfinding crate over that graph.

Sprites resolved at compile time

A build script reads the sprite atlas JSON at compile time and generates a phf perfect-hash map of every frame's coordinates. Sprite lookups are O(1) at runtime, and the binary never parses JSON or carries the atlas metadata as runtime data.

One codebase, desktop and browser

Desktop and web share a single codebase, split by a platform module behind cfg gates. On native targets the game owns its loop; on Emscripten the browser drives it through emscripten_set_main_loop, and a handful of functions (start_game, stop_game, restart_game) are exported as C FFI so the surrounding SvelteKit page can control it.

Attract mode

Left alone, the game plays itself. A stub AI steers Pac-Man toward the nearest remaining pellet, and the first real human keypress drops you straight into a live game. Input is tagged by source, so the AI's synthetic moves are never mistaken for a player's -- that is what lets the demo know the instant a human takes over.

Leaderboard and profiling

An online leaderboard runs on a separate Axum and Postgres server with OAuth2 login, fronted by the same SvelteKit site that serves the WASM build. Profiling is wired two ways: samply for sampling profiles, and Tracy for live instrumented capture, with one Tracy zone emitted per ECS system.

Where it stands

It is playable today, on the desktop and in the browser, with the full ghost AI, scoring, fruit, and sound. It remains a work in progress: there are no tagged desktop releases yet, though every CI build produces artifacts, and a few authenticity details on the roadmap are unfinished. The faithful-not-fast goal means rough edges get documented rather than papered over.

ยง09

Gallery

On this page