THE GIST“A command is a reified method call” — a method call wrapped in an object.
PROBLEM
Input code calls jump() and fireGun() directly. No rebinding buttons, no AI driving the same actor, no taking a move back.
PATTERN
Turn the call into an object you can keep in a variable, hand to any actor, queue, log — and undo.
Rebindable input: each button holds a command.
Player and AI emit the same commands. Demo mode for free.
Undo / redo:undo() plus a history list.
class Command {
public:
virtual ~Command() {}
virtual void execute() = 0;
virtual void undo() = 0;
};
class MoveUnitCommand : public Command {
public:
MoveUnitCommand(Unit* unit, int x, int y)
: unit_(unit), x_(x), y_(y) {}
virtual void execute() {
// Remember where it was, so undo can go back.
xBefore_ = unit_->x();
yBefore_ = unit_->y();
unit_->moveTo(x_, y_);
}
virtual void undo() {
unit_->moveTo(xBefore_, yBefore_);
}
private:
Unit* unit_;
int x_, y_, xBefore_, yBefore_;
};
seen in every editor’s Ctrl-Zkey rebinding menusreplaysa JS closure is a command
LIVE DEMO
Commands, kept
click it, then arrows to move · Z undo · Y redo · or a controller: stick, LB / RB · same history whoever is driving
Flyweight
THE GISTSplit an object in two: what every instance shares (intrinsic) and what makes this one different (extrinsic). Keep one copy of the first.
PROBLEM
A forest of thousands of trees. Each has a mesh and bark and leaf textures — and they’re all the same ones.
PATTERN
Every Tree points at one shared TreeModel and keeps only its own position, height and tint.
On the GPU: instanced rendering. The model once, the instances as a list, one draw call.
Terrain tiles: a grid of pointers to three Terrain objects instead of an enum and a pile of switches.
class TreeModel {
private:
Mesh mesh_;
Texture bark_;
Texture leaves_;
};
class Tree {
private:
TreeModel* model_; // shared by every tree
Vector position_; // the rest is this tree's own
double height_;
double thickness_;
Color barkTint_;
Color leafTint_;
};
// The world is a grid of pointers to flyweights.
Terrain* tiles_[WIDTH][HEIGHT];
int cost = world.getTile(2, 3).getMovementCost();
seen in GPU instancingGodot: a Resource is shared by every node that uses itUnity: sharedMaterial
LIVE DEMO
A thousand instances
hover a tree — what’s its own, and what does it share?
Observer
THE GIST“Lets one piece of code announce that something interesting happened without actually caring who receives the notification.”
PROBLEM
An achievement for falling off a bridge. Do we really want unlockFallOffBridge() in the middle of the collision code?
PATTERN
The subject keeps a list of observers. Anyone can add or remove themselves; notify() walks the list.
KEEP IN MIND
It’s synchronous. A slow observer blocks the subject.
Lapsed listeners: forget to unregister and zombies keep getting called.
Who’s listening is only known at runtime. Use it between lumps of code, not inside one.
void Physics::updateEntity(Entity& entity) {
bool wasOnSurface = entity.isOnSurface();
entity.accelerate(GRAVITY);
entity.update();
if (wasOnSurface && !entity.isOnSurface()) {
notify(entity, EVENT_START_FALL);
}
}
class Subject {
public:
void addObserver(Observer* observer);
void removeObserver(Observer* observer);
protected:
void notify(const Entity& entity, Event event) {
for (int i = 0; i < numObservers_; i++) {
observers_[i]->onNotify(entity, event);
}
}
private:
Observer* observers_[MAX_OBSERVERS];
int numObservers_;
};
seen in Godot signalsC# eventsaddEventListenerthe Observer slide in the core loop
LIVE DEMO
The lapsed listener
open and close the stats screen a few times, then fall
Prototype
THE GISTAn object can spawn other objects like itself: clone() copies the class and the state.
PROBLEM
Ghost, Demon, Sorcerer… and then GhostSpawner, DemonSpawner, SorcererSpawner. A second, parallel class hierarchy.
PATTERN
One Spawner holds a prototype monster and stamps out clones. A fast-ghost spawner is just a fast ghost.
VERDICT
Nystrom never found the class pattern the best answer: spawn functions, templates or first-class types do it with less code. Where prototypes do shine is in data →
class Monster {
public:
virtual Monster* clone() = 0;
};
class Ghost : public Monster {
public:
Ghost(int health, int speed)
: health_(health), speed_(speed) {}
virtual Monster* clone() {
return new Ghost(health_, speed_);
}
private:
int health_;
int speed_;
};
class Spawner {
public:
Spawner(Monster* prototype) : prototype_(prototype) {}
Monster* spawnMonster() { return prototype_->clone(); }
private:
Monster* prototype_;
};
Spawner* ghostSpawner = new Spawner(new Ghost(15, 3));
seen in JS prototype chainsUnity: prefab variantsGodot: inherited scenes
LIVE DEMO
Prototypes for data
dim rows are not stored on that object — they’re looked up on its prototype
Singleton
THE GIST“Ensure a class has one instance, and provide a global point of access to it.” Two problems, one pattern.
THE ANOMALY
Every other chapter shows how to use a pattern. This one shows how not to.
WHY WE REGRET IT
It’s a global variable in a nice class: harder to reason about, invites coupling, unfriendly to threads.
It solves two problems when you have one. You wanted easy access to the log; now there can only ever be one log, and every call site says so.
Lazy initialisation takes control away: the audio system loads on the first sound, mid-fight.
class FileSystem {
public:
static FileSystem& instance() {
// Lazy initialize.
if (instance_ == NULL) {
instance_ = new FileSystem();
}
return *instance_;
}
private:
FileSystem() {}
static FileSystem* instance_;
};
// Fine, until the log file becomes a dumping
// ground and you want one log per domain:
Log::instance().write("Some event.");
// ...in every file that ever logged anything.
seen in Godot autoloadsGameManager.Instancethe core loop deck’s pattern card
MonsterManager, ParticleManager, SoundManager, ManagerManager… a “manager” that babysits another class usually just wants its methods moved into that class. OOP is letting objects take care of themselves.
What to do instead
keep every object’s scope as narrow as still gets the job done — and for “only one”, a static class or an assert in the constructor
State
THE GIST“Allow an object to alter its behavior when its internal state changes. The object will appear to change its class.”
PROBLEM
A platformer heroine with isJumping_ and isDucking_. Every fix opens a new bug: air jumps, standing up in mid-air, jumping out of a dive.
PATTERN
A finite state machine: a fixed set of states, one at a time, inputs, and transitions between them.
Simplest: an enum and a switch on it.
The State pattern: one class per state, the heroine delegates to the current one.
enter() and exit() actions; static or per-machine states.
class HeroineState {
public:
virtual void handleInput(Heroine& heroine, Input input) {}
virtual void update(Heroine& heroine) {}
};
class DuckingState : public HeroineState {
public:
DuckingState() : chargeTime_(0) {}
virtual void handleInput(Heroine& heroine, Input input) {
if (input == RELEASE_DOWN) {
// Change to standing state...
}
}
virtual void update(Heroine& heroine) {
chargeTime_++;
if (chargeTime_ > MAX_CHARGE) heroine.superBomb();
}
private:
int chargeTime_; // only means anything while ducking
};
// Heroine just delegates:
// state_->handleInput(*this, input);
seen in Unity’s AnimatorGodot’s AnimationTree state machinemenus, AI, network protocols
TWO WAYS
Flags, or a state machine
if (input == PRESS_B) {
if (!isJumping_ && !isDucking_) jump();
} else if (input == PRESS_DOWN) {
if (!isJumping_) duck();
else { isJumping_ = false; dive(); }
}
switch (state_) {
case STATE_STANDING: // B: jump, DOWN: duck
case STATE_JUMPING: // DOWN: dive
case STATE_DUCKING: // release DOWN: stand
case STATE_DIVING: // nothing - B is ignored
}
replaying jump, dive, jump… · click it, then Space is B and ↓ is down · or a controller: A and ↓
LIVE DEMO
States, and a stack of them
click it: Space jump · ↓ duck (hold to charge) / dive · F fire · controller: A, ↓, X · it plays itself until you do
Beyond a single machine
Concurrent
Armed and unarmed versions of every state is a combinatorial explosion. Two machines — state_ and equipment_ — each get every input.
Hierarchical
A substate that doesn’t handle an input passes it up to its superstate — just like an overridden method calling the base class.
Pushdown
An FSM has no memory of where it came from. A stack of states does: push Firing, pop it when done, and she’s back to whatever she was doing.
good fit: behaviour that depends on internal state, split into a few distinct options, driven by inputs over time — for rich AI, look at behaviour trees and planners
II
Sequencing Patterns
a game world lives at its own cadence — we have to invent its time
seen in OpenGL’s swapBuffers()Direct3D swap chainsphysics and AI that must appear simultaneous
TWO WAYS
One buffer, or two
slowed right down: the beam scans what the player sees, the pen is the game drawing the next frame
LIVE DEMO
Slapstick comedians
Harry is slapped once; every frame the stage updates each actor in array order
Game Loop
INTENTDecouple the progression of game time from user input and processor speed.
PROBLEM
An event loop waits for input. A game can’t: the monster keeps chomping on your hero while you think. And it must run at the same speed on every machine.
PATTERN
Process input without blocking, update, render — and track the passage of time.
As fast as it can: speed depends on the machine.
Variable time step: keeps up, but non-deterministic and unstable.
Fixed update, variable render: catch up in fixed steps, render when you can, interpolate the leftover.
double previous = getCurrentTime();
double lag = 0.0;
while (true) {
double current = getCurrentTime();
double elapsed = current - previous;
previous = current;
lag += elapsed;
processInput();
while (lag >= MS_PER_UPDATE) {
update();
lag -= MS_PER_UPDATE;
}
render(lag / MS_PER_UPDATE);
}
If you’re in a browser or an engine, the platform owns the loop and calls you back — the pattern is still there.
seen in requestAnimationFrameGodot: _process(delta) and _physics_processUnity: Update and FixedUpdate“Fix Your Timestep”
LIVE DEMO
Same game, slower machine
60 fps
Update Method
INTENTSimulate a collection of independent objects by telling each to process one frame of behavior at a time.
PROBLEM
A skeleton guard patrolling, two statues firing lightning: each one’s counters and flags end up as loose variables mushed together in the main loop.
PATTERN
The world keeps a collection of objects. Each has an update() that does one frame of its behaviour. Each frame, call them all.
KEEP IN MIND
Slicing behaviour into frames means storing where you left off — State helps.
Not truly concurrent: update order matters.
Careful adding or removing objects while you walk the list →
class Skeleton : public Entity {
public:
Skeleton() : patrollingLeft_(false) {}
virtual void update() {
if (patrollingLeft_) {
setX(x() - 1);
if (x() == 0) patrollingLeft_ = false;
} else {
setX(x() + 1);
if (x() == 100) patrollingLeft_ = true;
}
}
private:
bool patrollingLeft_; // was a local in the loop
};
void World::gameLoop() {
while (true) {
// Handle user input...
for (int i = 0; i < numEntities_; i++) {
entities_[i]->update();
}
// Physics and rendering...
}
}
seen in Unity: MonoBehaviour.Update()Godot: _process(delta)the core loop’s Pong
TWO WAYS
Removing while you iterate
for (int i = 0; i < numObjects_; i++) {
objects_[i]->update();
// a slain object is removed at once
}
for (int i = 0; i < numObjects_; i++) {
if (!objects_[i]->isDead()) objects_[i]->update();
}
// then sweep out the dead
watch the peasant
III
Behavioral Patterns
a role-playing game has more behaviour than a word processor has features
INTENTGive behavior the flexibility of data by encoding it as instructions for a virtual machine.
PROBLEM
Spells for a wizard fighting game. Designers want to tweak them without a recompile, you want to patch them after ship, modders want to write their own — without crashing anyone.
PATTERN
An instruction set, encoded as a sequence of bytes. A virtual machine runs them one at a time, passing values on a stack.
The Interpreter pattern’s tree of objects is flexible but slow. Bytecode is dense and linear, like machine code — but sandboxed.
It grows like vines: keep it on a short leash.
You’ll need an authoring tool — and you’ll miss your debugger.
class VM {
public:
void interpret(char bytecode[], int size) {
for (int i = 0; i < size; i++) {
char instruction = bytecode[i];
switch (instruction) {
case INST_LITERAL: {
// The value is the next byte in the stream.
int value = bytecode[++i];
push(value);
break;
}
case INST_SET_HEALTH: {
int amount = pop();
int wizard = pop();
setHealth(wizard, amount);
break;
}
// ... GET_HEALTH, ADD, DIVIDE
}
}
}
};
seen in LuaGDScriptUnreal BlueprintsWren, Nystrom’s own language
LIVE DEMO
A spell, one byte at a time
wizard 0
health 45
agility 7
wisdom 11
Subclass Sandbox
INTENTDefine behavior in a subclass using a set of operations provided by its base class.
PROBLEM
A hundred superpowers, each written by someone different, each poking at audio, particles, physics, the renderer’s private layers. Duplicate code everywhere, coupling everywhere.
PATTERN
The base class offers protected provided operations and one abstract sandbox method. Subclasses only use what they’re given.
Coupling moves into one place: the base class.
A shallow, wide hierarchy — a point of leverage.
Watch for the brittle base class; split helpers out (getSoundPlayer()) when it bloats.
class Superpower {
protected:
virtual void activate() = 0; // the sandbox method
void move(double x, double y, double z);
void playSound(SoundId sound, double volume);
void spawnParticles(ParticleType type, int count);
double getHeroZ();
};
class SkyLaunch : public Superpower {
protected:
virtual void activate() {
// Spring into the air.
playSound(SOUND_SPROING, 1.0f);
spawnParticles(PARTICLE_DUST, 10);
move(0, 0, 20);
}
};
// Every power: inherit, override activate(),
// build it out of the provided operations.
seen in Unity: a MonoBehaviour calling GetComponent, InstantiateGodot: a Node calling get_node, add_child
TWO WAYS
Where the coupling lives
count what has to change
LIVE DEMO
SkyLaunch::activate()
click it, then Space (or A on a controller) · ground: spring · low: double jump · high: dive
Type Object
INTENTAllow the flexible creation of new “classes” by creating a single class, each instance of which represents a different type of object.
PROBLEM
Dragon : Monster, Troll : Monster, hundreds more. A designer emails: troll health 48 → 52. Check out, edit, recompile, check in, reply. Repeat.
PATTERN
Two classes and no inheritance: Monster and Breed. Every monster points at its breed; breeds come from data.
A breed can construct its monsters (newMonster()) and control their memory.
Breeds can inherit from a parent breed — at lookup, or copied down at load.
Data is easy; behaviour per type is harder (pick from a fixed set, or Bytecode).
class Breed {
public:
Breed(int health, const char* attack)
: health_(health), attack_(attack) {}
Monster* newMonster() { return new Monster(*this); }
int getHealth() { return health_; }
const char* getAttack() { return attack_; }
private:
int health_; // Starting health.
const char* attack_;
};
class Monster {
public:
const char* getAttack() { return breed_.getAttack(); }
private:
int health_; // Current health.
Breed& breed_;
};
{
"Troll": { "health": 25, "attack": "The troll hits you!" },
"Troll Archer": { "parent": "Troll", "health": 0,
"attack": "The troll archer fires an arrow!" }
}
seen in Unity ScriptableObjectsGodot custom Resourcesevery item database
INTENTAllow a single entity to span multiple domains without coupling the domains to each other.
PROBLEM
Bjørn, a Danish baker in a platformer: input, physics, animation, rendering and sound in one 5,000-line class. Changing anything means knowing everything:
if (collidingWithFloor() && getRenderState() != INVISIBLE) playSound(HIT_FLOOR);
PATTERN
Slice the class along domain lines into components. The entity becomes a container that holds them, and the state they share.
Swap one component: DemoInputComponent and the computer plays.
Components talk through the container’s state, direct references, or messages.
class GameObject {
public:
int velocity;
int x, y;
GameObject(InputComponent* input,
PhysicsComponent* physics,
GraphicsComponent* graphics)
: input_(input), physics_(physics), graphics_(graphics) {}
void update(World& world, Graphics& graphics) {
input_->update(*this);
physics_->update(*this, world);
graphics_->update(*this, graphics);
}
private:
InputComponent* input_;
PhysicsComponent* physics_;
GraphicsComponent* graphics_;
};
GameObject* createBjorn() {
return new GameObject(new PlayerInputComponent(),
new BjornPhysicsComponent(),
new BjornGraphicsComponent());
}
seen in Unity: GameObject + componentsGodot: a scene built from child nodesECS engines
Inheritance can’t pick the parts
object
physics
graphics
Decoration
—
■
Zone
■
—
Prop
■
■
one GameObject class, two component classes — no duplication, no multiple inheritance, three classes instead of four
LIVE DEMO
Plug and play
click a socket to plug a component in or out · click the world, then ← → ↑ drive Bjørn · or a controller
Event Queue
INTENTDecouple when a message or event is sent from when it is processed.
PROBLEM
Audio::playSound() sprinkled everywhere. It blocks while the sound loads; two enemies hit in one frame play the same wail twice as loud; and it runs on whichever thread called it.
PATTERN
Sending enqueues a request and returns at once. The receiver pulls requests later, when it suits it — push on one end, pull on the other, a buffer in between.
A ring buffer: an array whose head and tail wrap around.
The receiver can merge, delay or drop requests.
Careful: the world changes before the event is handled, and loops of events don’t crash — they slosh.
void Audio::playSound(SoundId id, int volume) {
// Merge with a pending request for the same sound.
for (int i = head_; i != tail_;
i = (i + 1) % MAX_PENDING) {
if (pending_[i].id == id) {
pending_[i].volume = max(volume, pending_[i].volume);
return;
}
}
assert((tail_ + 1) % MAX_PENDING != head_);
pending_[tail_].id = id;
pending_[tail_].volume = volume;
tail_ = (tail_ + 1) % MAX_PENDING;
}
void Audio::update() {
if (head_ == tail_) return;
ResourceId resource = loadSound(pending_[head_].id);
int channel = findOpenChannel();
if (channel == -1) return;
startSound(resource, channel, pending_[head_].volume);
head_ = (head_ + 1) % MAX_PENDING;
}
seen in every OS event loopGodot: call_deferred()Go channelspub/sub
LIVE DEMO
A ring buffer of sounds
Service Locator
INTENTProvide a global point of access to a service without coupling users to the concrete class that implements it.
PROBLEM
Physics, AI and UI all play sounds. AudioSystem::instance() at every call site ties each one to the concrete class and to how to reach it.
PATTERN
A phone book. Callers look the service up by its interface; the locator hides who provides it and how it was found.
Null service: nothing registered? You get one that does nothing, not a crash.
Decorated service: wrap the real one in LoggedAudio to log every call.
Still global: use it sparingly. Pass the object in first.
INTENTAccelerate memory access by arranging data to take advantage of CPU caching.
PROBLEM
CPUs got fast; RAM didn’t. Fetching a byte from main memory can cost hundreds of cycles. An accountant who can do a box in a minute, with a warehouse that takes a day to bring one.
PATTERN
Every read fetches a whole cache line — the pallet the forklift brings anyway. Keep data contiguous, in the order you process it, and use the whole pallet.
Arrays of components, not pointers to them.
Active objects packed at the front.
Hot data apart from cold.
// Before: pointer, pointer, cache miss, cache miss.
for (int i = 0; i < numEntities; i++) {
entities[i]->ai()->update();
}
// After: one flat array per component type.
AIComponent* aiComponents =
new AIComponent[MAX_ENTITIES];
PhysicsComponent* physicsComponents =
new PhysicsComponent[MAX_ENTITIES];
while (!gameOver) {
for (int i = 0; i < numEntities; i++) {
aiComponents[i].update();
}
for (int i = 0; i < numEntities; i++) {
physicsComponents[i].update();
}
// ...
}
In Nystrom’s test: the same work, fifty times faster.
seen in Unity DOTSBevyany ECS
TWO WAYS
Chasing pointers, or streaming
each miss fetches a line of eight cells into the cache; a miss stalls, a hit is nearly free
Two more ways to fill the line
Packed data
Activating swaps a particle with the first inactive one. No flag to check — and no flag to store.
Hot / cold splitting
Data you touch every frame stays in the packed array; data you need once in a lifetime moves out behind a pointer.
Dirty Flag
INTENTAvoid unnecessary work by deferring it until the result is needed.
PROBLEM
A scene graph: a parrot on a pirate in a crow’s nest on a ship. Its world transform is the whole parent chain combined. In one busy frame all four move — recalculating eagerly does ten combines, the parrot four times.
PATTERN
Primary data (local transforms), derived data (world transforms), and one bit: set when the primary changes, checked and cleared when the derived is needed.
Worth it when primary data changes more often than derived data is used, and can’t be updated incrementally.
Set the flag on every change. Miss one and you use stale data.
void GraphNode::setTransform(Transform local) {
local_ = local;
dirty_ = true;
}
void GraphNode::render(Transform parentWorld, bool dirty) {
dirty |= dirty_;
if (dirty) {
world_ = local_.combine(parentWorld);
dirty_ = false;
}
if (mesh_) renderMesh(mesh_, world_);
for (int i = 0; i < numChildren_; i++) {
children_[i]->render(world_, dirty);
}
}
The dirty parameter carries a parent’s dirtiness down, so moving a node never has to walk its children.
seen in the “unsaved changes” dot in your editorengines’ cached global transformssleeping bodies in physics engines
LIVE DEMO
A parrot on a pirate on a ship
Object Pool
INTENTImprove performance and memory use by reusing objects from a fixed pool instead of allocating and freeing them individually.
PROBLEM
Every wave of the wand spawns hundreds of sparkles. Allocating that fast is slow — and fragments the heap: fourteen bytes free in two seven-byte holes still can’t fit twelve.
PATTERN
Allocate the whole array up front. Each object knows if it’s in use; create() takes a free one, death hands it back.
A free list threads through the dead objects’ own memory: constant-time create, no extra bytes.
When it’s full: size it so it never is, don’t create, kill the least noticeable, or grow.
class Particle {
// ...
private:
int framesLeft_;
union {
// State when it's in use.
struct { double x, y, xVel, yVel; } live;
// State when it's available.
Particle* next;
} state_;
};
void ParticlePool::create(double x, double y,
double xVel, double yVel,
int lifetime) {
// Make sure the pool isn't full.
assert(firstAvailable_ != NULL);
// Remove it from the available list.
Particle* newParticle = firstAvailable_;
firstAvailable_ = newParticle->getNext();
newParticle->init(x, y, xVel, yVel, lifetime);
}
seen in Unity: UnityEngine.Pool.ObjectPool<T>bullets in every shootersound channels
LIVE DEMO
Sparkles from a pool
click the sky to cast · cast fast enough and the pool runs dry
Spatial Partition
INTENTEfficiently locate objects by storing them in a data structure organized by their positions.
PROBLEM
Real-time strategy: hundreds of units, and each needs to know who it’s close enough to hit. Comparing every pair grows with the square of the army.
PATTERN
On a 1D battle line you’d sort by position. In 2D, lay graph paper over the field: each cell keeps the units inside it, and a unit only checks its own cell and its neighbours.
grid
a persistent bucket sort
BSP, k-d tree, BVH
binary search trees
quadtree, octree
tries
void Grid::handleCell(int x, int y) {
Unit* unit = cells_[x][y];
while (unit != NULL) {
// Handle other units in this cell.
handleUnit(unit, unit->next_);
// Also try half of the neighboring cells,
// so no pair is checked twice.
if (x > 0 && y > 0) handleUnit(unit, cells_[x - 1][y - 1]);
if (x > 0) handleUnit(unit, cells_[x - 1][y]);
if (y > 0) handleUnit(unit, cells_[x][y - 1]);
if (x > 0 && y < NUM_CELLS - 1) {
handleUnit(unit, cells_[x - 1][y + 1]);
}
unit = unit->next_;
}
}
seen in physics broadphaseBox2D’s dynamic treequadtrees in 2D games
LIVE DEMO
Graph paper on a battlefield
hover a unit to see which cells it checks
See also
hover a pattern — every link is one the book draws in its See Also sections
Get on with it, already
Abstraction and decoupling make evolving a program easier — but only spend them where you’re confident the flexibility is needed.
Design for performance throughout, but put off the low-level optimisations that lock assumptions in until as late as possible.
Move quickly to explore the design space, but don’t leave a mess you’ll have to live in.
If you’re going to throw code away, don’t waste time making it pretty. Rock stars trash hotel rooms because they check out tomorrow.
Most of all: if you want to make something fun, have fun making it.