82 lines
5.2 KiB
Markdown
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.
|