Before writing a single line of code, a programmer benefits from a clear picture of what they are building. That becomes more true as the project grows. More systems means more dependencies, more events, more places for things to break if the architecture was not thought through first.

A Technical Design Document is how that picture gets built. It produces three things: a development order, a class structure, and an event catalogue. Those outputs tell you what to build, how to structure it, and how the pieces talk to each other before any of them exist. This article covers the process of building one.

It is primarily a programmer’s tool. But I like this version specifically because it can extend to artists and composers as well. More on that at the end.

Start With Systems

The first step is a full system inventory. List every major system the game needs. Do not sort or prioritize yet. Do not filter. Just get everything on the page.

A system is any part of the game with a single, clearly bounded job. It owns something, it does something with what it owns, and nothing else. The player controller owns movement state and translates input into velocity. The save system owns the data written to disk and handles every read and write operation against it. The audio manager owns playback and exposes a simple interface so the rest of the game can trigger sounds.

Describe a system’s job in one sentence without mentioning another system by name. If you can do that cleanly, the boundaries are probably right. If the sentence requires naming what the system depends on, or what hands off to it, or what it feeds into, those are signals that either the scope is too broad or you are actually looking at two systems that have not been separated yet.

Apply that test as you build the list. It surfaces problems and missing systems before writing a line of code.

Core vs. Optional

Once the list exists, sort it into two groups: systems required for the smallest complete loop, and everything else.

The smallest complete loop is the minimum set of things that makes the game technically playable. For a platformer: a player that can move, something to interact with or avoid, and a way to end and restart. The systems that make those three things work are core. Everything else is polish, expansion, or quality of life.

Core systems get built first. Optional systems wait.

This sorting step also surfaces assumptions. Systems that feel essential often are not when you measure them against the loop. A save system feels important. But a game can run without one. A progression system feels necessary. But the core loop does not require unlocking anything. Measured against the smallest playable version, both are optional. They get built, just not first.

Classes Only When You Need More Detail

Not every system needs class-level planning. A system like haptic feedback is simple enough to figure out while building it. A system like a state-machine-driven boss fight is not. Use judgment.

In my experience, two categories tend to warrant the extra depth.

Player-facing systems come first. Anything the player experiences on every frame: movement, input handling, controls. These systems get touched constantly during development and are the first thing a playtester will notice if something is off. Planning the class structure here means fewer expensive rewrites later when the rest of the game is built on top of them.

Complex systems come second. These are systems that read as one bullet point on the system list but expand significantly once you start mapping them out. A boss with multiple phases needs a state machine for phase transitions, another for individual move sets, hitbox management, animation triggers, and audio cues. Each of those is its own class with its own responsibility. The bullet point says “boss system.” The class breakdown reveals it is closer to five systems sharing a parent. Catching that during planning changes your time estimates and your architecture before either one is locked in.

Simple systems get a sentence. Complex ones get a breakdown. That distinction is the whole rule.

One Class, One Job

For systems that warrant class-level detail, document the same three things for each class: what it owns, what it does with what it owns, and what it exposes or fires when something changes.

A well-scoped class has a job specific enough to state in one sentence. “Reads hardware input and exposes a movement direction for other systems to consume.” “Tracks health state and fires an event when that state changes.” Working out that level of specificity during planning forces decisions about what the class owns, where its boundary is, and how the rest of the game interacts with it. All three of those are easier to get right on paper than mid-implementation.

It also makes reuse straightforward. A class with one clean job that communicates through exposed values or events can move to the next project without modification. The job it does is the same regardless of the game around it.

Event-Driven Communication

Systems should not hold direct references to each other. When a system needs to trigger something in another system, it fires an event. Other systems listen for that event and respond. The sender does not need to know who is listening. The listener does not need to know who sent it.

That separation matters because direct references create coupling. If the player controller holds a direct reference to the UI manager to update a health bar, those two systems are now dependent on each other. The player controller cannot exist without the UI manager. The UI manager cannot change its structure without potentially breaking the player controller. Multiply that across a full codebase and you end up with systems that cannot be moved, tested, or reused without pulling everything connected to them along.

An event-driven architecture keeps those boundaries clean. The player controller fires a health changed event and carries the new value as a payload. The UI manager listens for that event and updates the health bar. Neither system knows the other exists. Either one can be replaced or removed without touching the other.

For each event worth planning, document three things: what fires it, what consumes it, and what data it carries as a payload. Mapping this during the document phase is where coupling problems surface on paper instead of in code. A dependency that looks manageable in isolation gets expensive fast once three other systems are built around it.

Decoupled systems are also portable. A system that communicates only through events can move to the next project without modification.

Define Your Data

Most systems in the game produce, consume, or share data. Defining what that data is, where it lives, and which systems are allowed to read or write it is a planning step that is easy to skip and consistently expensive when skipped.

Save data is the clearest example of why. A SaveManager, a ProgressionManager, and a level select UI can all be built independently, but they all need to agree on the shape of the data they share. Define the save schema during planning and each system has a contract to build against: what fields exist, what gets written on level complete versus on death, what is stored per slot versus globally. That contract keeps three separately built systems consistent with each other without requiring coordination during development.

The same principle applies to any data that crosses system boundaries. When multiple systems can read or write the same value, ownership needs to be explicit. One system owns the data. Other systems request changes through that owner rather than writing directly. That boundary prevents a class of bugs where two systems modify the same value independently and produce results neither one expected.

Write the Open Questions Down

Some things will go unresolved during planning. That is expected. Write them into the document as explicit open questions and keep moving.

A question written down has a place in the process. It can be returned to, handed off, or answered later when more of the game is built and the answer is clearer. Some questions resolve themselves once adjacent systems are in place. Others stay open until you are actually building the thing. Either way, the document tracks them.

Flag each open question as blocking or deferrable. A blocking question has to be answered before you can reasonably build the system it affects. A deferrable question can wait until you reach it in development. That distinction shapes your development order directly: blocking questions get resolved first, deferrable ones get scheduled around.

The 75-80% Rule

The target for a finished TDD is roughly 75-80% coverage. Every major system identified, its classes mapped where warranted, its events planned, its data defined. That is enough to start building.

The goal is to have enough of the picture in place that you are not making structural decisions mid-development. Systems that are not planned tend to get bolted on. They borrow from systems they should not touch, they create coupling that was not accounted for, and they are harder to untangle later. Getting the major pieces down before building is what prevents that.

The remaining 20-25% will resolve as you go. Start building once it is close enough.

The Benefits of a TDD

As a programmer, this process produces a development order. The systems are listed, sorted by dependency, and broken down far enough to know what to build first and what has to wait. Starting development with that picture in place means structural decisions get made during planning, when the full system list is visible, rather than mid-build when they are harder to get right.

The document is also useful beyond the programmer. The systems section defines what the game needs to function. From that, an asset list for an artist is straightforward to derive: this system needs these sprites, this environment needs a tilemap at this resolution. The same goes for a composer. The planning work is already done. Translating it into role-specific deliverables is a much smaller step.

Learning to Think in Systems

Planning at this level of abstraction is a skill that develops over time. I have a background in professional software development and it still took deliberate, intentional practice before thinking in systems felt natural.

The best way to build that skill is on a small scale with low stakes, before applying it to a real project. I was intentional about how I practiced it. If you want to read about what I do, it is covered in full here: [How I Practice Game Design Without Stopping Development].


0 Comments

Leave a Reply

Avatar placeholder

Your email address will not be published. Required fields are marked *