Files
codegirl.games-hugo/content/posts/game-programming-patterns-review.md
T
2026-07-04 02:06:24 -07:00

82 lines
5.2 KiB
Markdown

---
title: "Game Programming Patterns, first impressions"
description: "A book review of Robert Nystrom's Game Programming Patterns and how I use it in my tower defense prototype."
date: 2026-06-28
type: books
book: "Game Programming Patterns"
tags: ["patterns", "books"]
---
*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](https://github.com/Codegirl-Games/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.