// part 1 of 3

The Big Picture

What are the moving pieces of a game, and how do they talk to each other? One sentence first, then we unpack every word of it.

Here is the entire architecture in one breath: a React component that, once mounted, hands a chunk of the screen over to a hand-written game loop, which ticks about 60 times a second and moves plain SVG elements around the DOM by hand — React is only used for the menus and dashboards sitting on top.

Two terms in that sentence deserve an immediate definition. The DOM is the live tree of elements a browser builds from markup — and it's not just text: every node is a real object in memory you can mutate (element.setAttribute(...)), and the browser repaints to match. Every zombie, tower and arrow in this game is one such node. The HUD (heads-up display) is the info layered over the game world — gold, lives, the wave counter, build buttons. It shows the game's state but isn't part of the simulated world, the way a monitoring dashboard shows your cluster but isn't the cluster.

Why the game is split in two regimes

React's whole model is declarative: you describe what the UI should look like given some state, and React computes the minimal DOM changes to get there. That is perfect for a HUD that changes a few times per second at most. It is a bad fit for animating 50+ entities at 60fps — re-running a render pass and diffing a virtual DOM every 16 milliseconds is real, measurable overhead spent asking a question ("what changed?") the game already knows the answer to.

So the code splits itself deliberately:

RegimeOwnsHow it updates
Declarative — Reactmenu, HUD numbers, build buttons, upgrade popup, end screennormal React: state changes → component re-renders
Imperative — hand-rolledthe playfield: zombies, towers, projectiles, effectsa requestAnimationFrame loop directly sets transform attributes on real SVG nodes; React mounted them once and got out of the way

What happens in a single frame

Everything the game does funnels through one callback that the browser fires before every repaint. One tick looks like this:

1 · requestAnimationFrame
the browser calls frame(now)
2 · step(g, dt)
simulate: move zombies, towers aim & fire, resolve hits, spawn & despawn
3 · render(g)
apply results to the DOM: one transform write per entity
4 · pushHud()
copy only changed values into React state
5 · React re-renders
just the HUD overlay, and only if step 4 changed something

Steps 2–4 are plain functions in ZombieTowerDefense.js called back to back. Steps 1 and 5 are other people's machinery — the browser's timer API kicks the frame off, and React's renderer reacts at the end. Getting clear on that boundary is most of understanding the architecture.

Two details are easy to miss and important to steal for your own projects:

The world state is a plain mutable object. Every zombie, tower and projectile lives in g — a normal JS object held in a React ref, not React state. That is why it can be mutated 60 times a second without triggering 60 re-renders: nothing about it passes through React's state machinery.

pushHud() diffs before it publishes. It compares the new HUD snapshot field-by-field against the last one and only calls setState when something a human would notice changed — gold went up, lives dropped, a wave started. React is told when to care instead of polling. It's the same courtesy you'd extend to any downstream consumer: don't publish an event when nothing happened.

ZombieTowerDefense.js — the shape of the loop
const frame = (now) => {
  raf = requestAnimationFrame(frame)          // book the next tick first
  const rawDt = Math.min(0.05, (now - prev) / 1000)
  prev = now
  const dt = g.status === 'playing' && !g.paused ? rawDt * g.speed : 0
  step(g, dt, rawDt)                          // simulate + render
  // ...pushHud() etc.
}

The pieces and how they connect

Part 0 introduced the files; here is the relationship that matters. ZombieTowerDefense.js is the engine — logic plus renderer. Everything else is data it reads: td-config.js for the numbers (tower damage, zombie HP, wave budgets, gold), the sprite files for the art, td-sfx.js for synthesized sound, the CSS for HUD styling and ambient animation. The config file's own header says it plainly: "Balance/map edits happen here, nowhere else."

That separation is a general game-dev principle worth keeping: numbers a designer would tweak (cost, damage, HP) stay completely apart from the code that enforces the rules — the same reason you keep config out of application code. When part 3 asks "what survives a switch to another engine?", the answer is: exactly the parts that respected this boundary.

Vocabulary — game-dev terms, minus the gatekeeping

TermWhat it means here
Game loopCode that re-runs ~60 times a second for as long as the page is open. Each run nudges the world a tiny bit and redraws it — a flipbook, just faster.
Scene graphThe tree of on-screen elements that exist right now, split into layers (ground effects, entities, air) so things draw in the right order. Contains only what is currently alive.
SpriteThe reusable artwork for one kind of thing. There is one Shambler sprite, but a dozen Shambler zombies can be walking around, all wearing it.
EntityOne specific, currently-alive instance — its own position, its own HP. Kill it and the next wave’s Shambler is a different entity in the same sprite.
Anchor pointThe one pixel inside a sprite that counts as its true position — a zombie’s feet, a tower’s base. The star of part 2.
Painter’s algorithmIn a ¾-from-above view, "closer to the bottom of the screen" must draw on top. The game re-sorts entities by screen Y every frame so a zombie walks behind a tower, then in front of it.
HUDThe dashboard over the world: gold, lives, wave counter, buttons. The "+8" coin that pops out of a dying zombie is not HUD — it lives in the game world, spawned and animated by the engine.

Next: how a tower goes from artwork to a positioned, health-bar-wearing thing on screen — and the single most important idea in 2D rendering.