Primary reference: Nystrom, R. (2014). Game Programming Patterns. Available at gameprogrammingpatterns.com. Translations from Gang of Four (Gamma et al., 1994) into game-specific contexts.

01

Component

Nystrom, Ch. 14 - Component

The problem this solves: a "god class" Player that does everything directly becomes unmaintainable fast. Instead, Player behaviour is split into independent components: SpriteComponent, HealthComponent, StatComponent (x3 for stamina, hunger, water), Inventory, HUDComponent, CursorComponent. None of these inherit from Player.

The practical benefit: during development, the HUD, inventory, and stat bars were all modified independently without touching the Player class. Adding hunger and water was just adding two more StatComponent instances. Sprite component it used in multiple places such as Player, enemies, world items, without any inheritance or shared base class.

02

Flyweight

Nystrom, Ch. 3 - Flyweight

Two applications. First: WorldObjectTemplateManager stores one std::vector<CollisionShape> per world object type. Each placed tree or stump holds a non-owning pointer to that shared list plus a sf::Vector2f offset. A 128x128 map with several thousand objects uses the same memory as a single instance's shape data.

Second: ItemTypeRegistry stores one ItemTypeData block per item type. Every Food item in the world shares the same atlas rect, sprite size, and stat restore amount. The world item just holds the type enum and position.

This helped performance as I was having memory issues. The game was crashing when I tried to place a few hundred world objects with their own shape data, and the item pool was also causing issues when I tried to spawn more than a handful of items. After applying Flyweight, I could place more of world objects and spawn dozens of items without any memory problems.

03

Spatial Partition

Nystrom, Ch. 20 - Spatial Partition

The problem: assigning every tile to its nearest Voronoi site by checking all sites is O(n²). On an 80x80 map with 20 sites, that's 128,000 distance calculations per generation.

The fix: a hash-cell spatial grid divides the world into cells. Each tile queries only the 3x3 cell neighbourhood around its position, O(k) where k is the small number of sites in nearby cells. This took generation time from 84ms to 11ms at the 80x80 test size. That's the 7.7x speedup in the benchmarks, and it's directly attributable to this pattern.

04

Observer (Pull Variant)

Nystrom, Ch. 4 — Observer

The HUD needs to display stat values without being tightly coupled to the stat components. The standard push-based Observer (stat broadcasts a change, HUD subscribes) adds callbacks and event queuing overhead that isn't needed here.

Instead: HUDComponent holds const& references to HealthComponent and three StatComponent instances. At render time it calls getHealthRatio() and getRatio(). The HUD pulls current values when it needs them. Stat logic knows nothing about rendering.

05

State / Pushdown Automaton

Nystrom, Ch. 7 - State (Pushdown variant)

GameStateManager uses a std::stack<GameState>. pushState() saves the current state on the stack and transitions to a new one. popState() restores the previous state. changeState() clears the stack.

The specific behaviour this enables: Settings can be opened from both the main menu and the pause menu, and popping returns correctly to whichever one opened it. A simple state variable can't do that, you'd need to track the prior state separately anyway.

06

Command

Nystrom, Ch. 2 - Command

InputController maps InputAction enums to physical keys. Every piece of game logic queries InputAction::Sprint, not sf::Keyboard::Key::LShift. Rebinding a key in the settings menu propagates immediately to all query sites without touching any game logic code.

wasJustPressed() and wasJustReleased() compare the current frame's state array against a snapshot of the previous frame's state copied via memcpy. Both are O(1) array index lookups.

07

Update Method

Nystrom, Ch. 10 - Update Method

IUpdatable provides a single update(sf::Time) contract. The fixed-timestep game loop calls update on every entity at 60Hz. Each entity manages its own state, the loop never inspects type or casts.

Component updates cascade naturally: Player::update() calls m_health.update(dt), m_hunger.update(dt) etc. The drain rates (hunger: 0.5 units/s, water: 0.8 units/s) are set on each StatComponent and applied automatically in their own update, Player doesn't need to know the rate.