Organizing Game Code

Abstract

Problem: How should a game programmer structure and sequence the development of game code from the very beginning of a project?

Approach: Tim Cain shares his personal methodology refined over decades of professional game development (Fallout, Pillars of Eternity, The Outer Worlds), walking through each stage of code development in order.

Findings: Game code should be built in a deliberate sequence — design fundamentals first, then movement, terrain, props, structures, instancing, exploration, and finally the explosion of parallel systems (combat, UI, dialogue, AI). Every class should implement five standard functions: init, reset, exit, load, and save. Methods should fit on one screen.

Key insight: Don't write a single line of code until fundamental design questions are answered, then build systems in a layered order where each layer naturally enables the next.

Source: https://www.youtube.com/watch?v=mmt9mB7HAwU

Don't Start Coding Until Design Is Ready

Tim emphasizes that no code should be written until enough design decisions are made. Whether you're the designer or someone else is, fundamental questions must be answered first:

  • How big are your maps?
  • How will players traverse them?
  • Will buildings and dungeons be instanced or seamless?
  • Will the game support flying or swimming?

These don't need a full design document — a designer can often answer them immediately. But having them written down as design pillars before any code is written prevents costly rework later.

Build a Unit Test Level

Since around 2004, Tim has started every project by building a simple "unit level" — a test environment that anyone on the team (artist, programmer, designer) can visit to see the game's fundamental constraints:

  • A ledge showing the maximum height a player can jump to while standing
  • A higher ledge showing the maximum height for a jump-and-pull-up
  • The smallest vent a player can crouch through
  • The lowest bar a player can crouch under
  • The farthest distance a creature can stand and still be hit by a melee weapon
  • Examples of ladders, stairs, and the steepest walkable angle

Why This Matters

This level serves two purposes. First, it lets everyone see the game's physical limits — so an artist building a canyon knows exactly how steep the walls can be before players can walk up them. Second, it surfaces problems early. Sometimes you see the melee range and realize it's too short, or the jump distance doesn't feel right. These values should be locked in as soon as possible because changing them later forces cascading changes across all existing content.

The Coding Sequence

Stage 1: Basic Movement

The very first thing Tim always codes is a character moving around the world — walking, running, traversing terrain. This is where you decide movement speed, camera viewpoint height, and field of view. He references the early Fallout demo with a knight walking among trees and flowers as a perfect example: just two other people were on the team, and the goal was to nail down isometric perspective, camera distance, walk speed, and object scale.

Stage 2: Complex Terrain

Next comes mountains, canyons, water, and pits. You walk around and evaluate: Can you see a pit as you approach it? Does jumping over a gap feel right? If not, go back to the unit test level and adjust the jump distance.

Stage 3: Props

Props are environmental objects — trees, shrubs, ground clutter like grass and flowers. They are not intended for players or NPCs to walk on. Props can have collision volumes (tree trunks block you) or not (you walk through grass). They can have attachment points (where birds land on a tree). Some detect the player and animate (grass bending as you walk through).

Stage 4: Structures

Structures are objects intended to be walked on by both players and NPCs. They have both collision and pathing. This includes buildings (especially non-instanced ones where you can enter, climb to roofs, explore second floors), ruined buildings, large boulders, outcroppings, ships, and spaceships. Dungeons are also structures — the difference is they're typically instanced. By this point you should already know from your designer whether instancing is supported and how big pathing meshes need to be.

Stage 5: Instancing and Maps

Handle entering and exiting different maps — dungeon transitions, zone transitions through mountain passes, or moving between terrain areas. Decide whether maps stream seamlessly or require loading screens, and whether fast travel is supported.

Stage 6: Exploration (The Explosion)

This is where development "goes boom." Basic exploration means the player can move around the world, jump on things, activate switches that control doors, gates, and drawbridges. This introduces stateful objects. Then items appear in the world, which requires inventory and containers (chests). Containers lead to creatures (who also have inventory and can be looted). Creatures lead to combat. Combat leads to vendors. Each system naturally pulls in the next — combat, merchants, dialogue, bartering, AI — all developing in parallel.

Parallel Systems

At this stage, UI development begins and grows alongside everything else. It typically starts with a simple start/quit menu, then settings menus. Tim notes that early development settings often include debug options (like making combat easier or turning it off) that won't ship — many end up hidden behind developer consoles, which is why so many shipped games have console activation.

Load/save systems also emerge here. Design decisions must be made: checkpoint saves? Save anywhere? Save anywhere except in combat? These decisions shape both the code architecture and the UI.

The Five Sacred Functions

Tim had five functions that appeared in every class (or module) across every game he worked on:

  • init — Called when the game application first starts. Sets up the class. In C++ this would be called from the constructor; in C (where Tim started) it was an explicit function call.
  • reset — Called whenever the player starts a new game within the application. Clears quest logs, resets XP, attributes, and skills. Tim preferred this over destroying and reconstructing objects because the destroy/construct cycle fragments memory — a huge concern for most of his career.
  • exit — Called when the game application shuts down. Releases memory, closes file handles. In C++ this maps to the destructor.
  • load — Receives a file handle and reads whatever state the class needs. The class owner writes their own load logic since only they know what needs persisting.
  • save — Receives a file handle and writes whatever state the class needs, in a format that load can read back. For fixed data: just write the values sequentially. For variable-length data (arrays): write the count first, then the elements.

Why Distributed Load/Save Works

The programmer responsible for the load/save system can't possibly know how to serialize every system in the game — dungeons, creatures, merchant inventories all have different needs. By giving each class its own load/save methods, the system author who built each feature handles their own serialization. This also made it easy to later swap the underlying storage from flat files to databases — the classes just saw something that looked and acted like a file handle.

Keep Methods Small

Tim's final recommendation: keep methods small enough to fit on one screen. If a method grows too large, ask why and break it into smaller pieces. He admits to violating this himself — Pillars of Eternity had a switch statement with hundreds of case entries, and the first thing he told Brian McIntosh was to refactor it for Deadfire.

References