Screenshot from Impaler

Early Investments for Game Projects

Game development is an iterative process. Make change → evaluate change → repeat. As a developer, the faster you can implement a change and test it, the happier you will be. Even modestly scoped game projects will be compiled thousands of times and deployed hundreds of times. With this in mind, there are smart early investments that can improve the developer experience.


Breakdown of the compile and loading steps.

The game development iteration loop. Waiting is unfortunately a part of it.


Building my game, it was worth the effort to

  • reduce compile + load times
  • implement one-click Steam builds
  • create QA shortcuts for quick navigation to different parts of the game
  • add unit tests and assertions for math-heavy code

Stats from a C-based Game Project

The stats below show the effort that goes into a small indie games. It is a small game priced at $4.99 and likely 1% of the scale and complexity of a big title. You can see why having a painless testing workflow is so important - even at indie scale.

Task Count Description
Production builds 10 Production releases to Steam
Test builds ~200 Test builds deployed to Steam for QA
Asset additions ~1000 Total shader, sound, and image assets
Code commits ~3000 Code commits to GitHub
Debug sessions ~5000 Estimated number of local game launches

Based on this, a 20s load time would result in staring at a load screen for 27hrs across the whole project. Worse, it would make the iteration process painful.


Compile & Load Times

To playtest a compiled game, the process looks like: build → load → navigate to area of interest → evaluate. Everything before “evaluate” is a waste of time so it is important to shorten or eliminate these steps where possible. Reducing build + load times was the single best investment I made on this project.

Breakdown of the compile and loading steps.

Breakdown of an optimized debug build (4.16s on a 2020 Macbook Air)


Reducing compile times

  • perform incremental builds by recompiling only files that changed
  • compile without optimizations (don’t gcc -O3)
  • use parallel make jobs (make -j4)
  • eliminate extraneous steps in the build process (e.g. disable code signing for debug builds)
  • remove unused code & dependencies


Reducing load times

  • avoid re-rendering the loading screen with every update. (update in 10% increments instead)
  • disable audio when it’s not being tested
  • load low resolution proxy assets (especially for large background images)
  • atlas textures and sprites (load one large image vs 100 small ones)
  • stream or lazily load large assets, such as music


Prior to the improvements, it would take 12 seconds to test the same change. While 4 seconds is much better, it still adds friction to the process. I do miss having an editor that allows scene previews without having to compile. This is one area where commercial game engines really shine. However, I am willing to live with this for now.


One-click Steam Builds

Short load times are a huge benefit for the developer. However, most of the playtesting / QA is done by others. In the case of Impaler, test builds were made available on Steam so they were easily accessible by the publisher and QA team. Deploying a build to Steam is a time-consuming process without automation.

Steps to put a test build on Steam

  • configure
    • increment the game’s version number
    • ensure the game configuration is set to RELEASE
    • update Steam build metadata
  • build
    • perform a full compile with optimizations
    • bundle the executable, metadata, and assets together
  • deploy
    • upload the package to Steam
    • set the “default” build to the latest package
    • create a release tag on GitHub


Following the steps above can take several minutes when done manually. Worse, you have to repeat this for each publisher platform. Steam and GOG provide command line tools that make it possible to script the entire process. With a few dozen lines of bash, one can distribute builds with a single terminal command. build_and_deploy_all.sh has saved countless hours of manual effort. Arguably, this was the second best investment I made on the project. It would be straightforward to execute this on a remote build server too.


QA Shortcuts

QA shortcuts are tools that skip repetitive gameplay steps during testing. When evaluating a new change, it’s typical to play the game for a period of time to get to the point of interest. Imagine you want to see how the flying monster on level 3 behaves with a new attack. Instead of having to play through the entire level to encounter the monster, a better approach is to load a test map where the monster starts directly in front of the player. Like loading screens, time spent navigating to the point of interest should be minimized.

To make things easier, config-based shortcuts were added to

  • launch the game into a specific menu or level
  • spawn the player with a preset inventory
  • load a “zoo” scene that contains one of each game entity
  • override map layouts and lighting conditions
  • override unlock progress (e.g. start with all content available)
  • shorten the level duration to quickly play through the whole game
  • enable god mode, infinite money, etc.


Since the config file lived outside of the codebase, the QA team could use it as well. They greatly appreciated it.

Zoo scene containing all of the game's monsters

Zoo scene containing all of the game's monsters


Unit Tests & Assertions for Math

Unit tests and assertions can avoid significant headaches over the course of a project. They are particularly valuable for math-heavy sections of code that can produce invalid values such as NaN (not a number). An invalid math operation (like dividing by zero) can easily create a game breaking bug - e.g. setting the camera’s position to {NaN, NaN, NaN} and rendering a black screen. Math routines typically have clear inputs and outputs and are fairly painless to test.

Specific cases to test for

  • division by zero
  • square root of zero
  • normalizing the zero vector
  • unit vector inputs that are not of length = 1
  • All NaN values, in general


One approach I took was to create “safe” versions of common math operations. In local builds, these methods throw an error and provide an opportunity to fix the offending code path. In production, these methods return a reasonable (but incorrect) value instead. Almost anything is better than feeding NaN into your physics engine or renderer.


Recap

In the end, investing in developer quality-of-life tools is not just about saving time — it’s also about preserving the motivation to iterate. Whether you’re optimizing load times, streamlining QA, or scripting deployments, these efforts pay dividends in the long run. Take a moment to review your development process and identify areas where small investments could save hours, or even days, of effort down the line.