5.2 KiB
title, description, date, type, book, tags
| title | description | date | type | book | tags | ||
|---|---|---|---|---|---|---|---|
| Game Programming Patterns, first impressions | A book review of Robert Nystrom's Game Programming Patterns and how I use it in my tower defense prototype. | 2026-06-28 | books | Game Programming Patterns |
|
Game Programming Patterns by Robert Nystrom is the reference I'm using as I build. The patterns aren't abstract; I'm already applying several of them in my Odin tower defense prototype.
Here is where the book shows up in real code so far.
Patterns in the tower defense game
Object Pool
Enemies and projectiles are never allocated per spawn. Both use fixed arrays ([MAX_ENEMIES]Enemy, [MAX_PROJECTILES]Projectile) with acquire_enemy and acquire_projectile scanning for inactive slots. A spawned unit resets its fields and sets active = true; on death or impact, active = false returns the slot to the pool.
This was the first pattern I reached for, in week one. Every wave can spawn dozens of enemies and towers can fire many projectiles per second. Avoiding allocate/free in the hot path keeps the update loop predictable.
Command
Player input goes through a command layer, not straight into game logic. gather_commands and poll_controls_command read keyboard and mouse state and return a Command value: Place_Tower, Start_Wave, Upgrade_Tower, or None. execute_command dispatches to the right handler.
UI buttons, hotkeys, and mouse clicks all produce the same command type. Adding a new input source does not mean rewriting placement or wave logic.
Event Queue
Gold changes do not happen inside combat code directly. When an enemy dies or a wave is cleared, systems call push_event with Enemy_Killed or Wave_Survived. At the end of update_world, process_events drains the queue and applies gold rewards.
Combat systems announce what happened; the economy system decides what it costs. That separation kept the update loop readable as towers, waves, and enemy types piled on.
State
The game runs in explicit phases: Build, Combat, Game_Over, and Victory. Phase controls what input is accepted (you cannot place towers during combat), when waves can start, and when the simulation stops updating.
This is a straightforward state machine, not a deep AI behavior tree, but the same idea: one variable drives which rules apply this frame.
Update Method
Each system owns an update function called once per frame from update_world: update_wave, update_enemies, update_projectiles, update_towers, update_phase. No single giant function walks every entity type inline.
The book's "one game loop, many systems" structure maps cleanly onto separate .odin files as the project grew.
Game Loop
app.odin runs the classic loop: read input, call update_world, render the map, entities, overlay, and controls. Update and draw stay separate; simulation code never calls draw functions.
Data Locality
Enemies and projectiles live in contiguous fixed arrays rather than scattered heap allocations. I iterate the full pool each frame but skip inactive slots. Not as aggressive as a struct-of-arrays layout, but the fixed-buffer approach gives similar benefits: no pointer chasing, no allocator pressure during combat.
Type Object (archetype tables)
Tower and enemy stats live in shared lookup tables (TOWER_ARCHETYPES, ENEMY_DEF), not duplicated on every instance. A placed tower stores its kind, position, cooldown, and upgrade level; range, damage, cost, and footprint come from the table row.
Adding a cannon or a tank enemy is mostly a new table entry plus a behavior branch, not a new class hierarchy.
Subclass Sandbox
Tower types differ by switch t.kind in update_towers: archers spawn homing projectiles, cannons fire ballistic shots with splash, ice towers apply slow on hit. I skipped inheritance trees in favor of enum variants and explicit branches. Nystrom argues this is the right call when you only have a handful of types and the differences are behavioral, not structural.
Spatial partition (grid)
The map is a 2D tile grid (Blocked, Path, Build). Tower placement queries can_build_at and footprint overlap against grid cells, not against every entity on the map. A full spatial hash would be overkill at this scale; the grid already gives O(1) tile lookups for build rules.
What I have not used yet
Patterns I expect to need later but have not implemented in this prototype:
- Pathfinding for dynamic routes when tower placement blocks the path
- Behavior trees or a deeper State machine per enemy for complex AI
- Observer beyond the simple event queue (e.g. UI reacting to stat changes)
- Component or Entity-Component-System if entity types multiply significantly
Why I keep the book nearby
Nystrom names the patterns, explains the tradeoffs, and shows when not to use them. That matches how I work: reach for Object Pool and Command early because the problem is obvious; hold off on ECS until the entity count justifies the complexity.
Expect follow-up posts that go deeper on individual chapters as I apply more patterns to the tower defense game and the colony sim.