Braid has been sticking to my aesthetic palate in the years since I’ve played it, and has probably impacted my aesthetics as an independent developer more than any other game has. It has a beautifully complex mix of influences, somewhere between Vonnegut’s Slaughterhouse-Five, Super Mario, and apparently Italo Calvino. It creates immersion through an excellent marriage of mechanics and meaning. The game felt more like a literary experience to me, which is inspiring to me because my creative background lies mostly in non-game media.
I’m also interested in art as an interface to its creator, which independent games developed by small teams especially allow. The purity of independent development is one of its strongest advantages to me, though I rarely see it used for such a purpose. Braid was developed by Blow with the aid of one artist, David Hellman, and gives the strong impression of a personal investment. As a game developer, I also wonder how he did it, so this recent talk he gave caught my attention.
In an attempt to absorb his advice and hopefully distill it for others in a more quickly-digestible format (though I strongly advise watching the video), here’s my summary and loose interpretation of the talk:
- Independent game development is a monumental task for one individual. You must be brutally effective. Pragmatism is essential. Cautiously approach the programmer-inherent impulse to be clever.
- As programmers, we have a natural impulse to optimize. This usually works against us by complicating code and draining our time while providing only marginal benefit, except in the very few cases where performance does have to be highly tuned (e.g., world physics). Re-evaluate your understanding of what truly needs to be optimized, and weigh it against the ensuing code complication and opportunity cost of implementation.
- Data structures and optimization are intimately entwined. A theoretically perfect data structure may be the wrong choice if a naive one is more usable. Blow claims to use arrays of records for nearly everything in his games. These may require linear scanning just to access a single asset, for example, but the performance hit is negligible and the result is maintainable and usable code that can easily be optimized later.
- Implementation time is a parameter that can be optimized. Chasing a white whale of design is unproductive. Consider this when choosing complicated data structures and algorithms. “They require a large lifetime expenditure for a marginal benefit that possibly doesn’t matter; they have a much shorter lifespan than simpler algorithms. More complication equals more assumptions about the input state and the output state.”
- Again, despite the natural progammer’s impulse, writing generalized systems usually results in unneeded functionality. Specialized systems have built-in documentation because the purpose of every function is more clearly telegraphed. Deletes are harder to perform in generalized systems because they are more tightly coupled. Generalized systems are another hindrance to productivity.
- Connections between modules are usually above linear complexity because modules are shared between many other modules. Code addition should be a last resort; code deletion should be the ideal. Enforce minimal connections.
- Early experience teaches us that long functions should be decomposed into individual function calls. These make the aggregate function more readable as the helper functions are self-documenting. However, this decreases maintainability: without the context of the calling function, we wonder about the purpose of the helper functions and the conditions under which they must be called. Though the coder who creates the aggregate function knows exactly what to use the helper function for, a new programmer will not know the insidiously implicit pre- and post-conditions that apply to it. Function-synchronized commenting overhead is imposed, resulting in bloat. In general, creating helper functions implies that they are meant to be generally usable, though this may not be the case. A better approach is to use blocks in place of helper functions. This allows local scoping and clearly segments the different procedures contained within the function. It also enforces one-time, specialized use, and allows for adjacent comments that are much easier to maintain. Don’t try to hide the fact that a function is long.
- What kind of programmer is worth hiring? One who gets things done quickly, robustly, and simply; who finishes things in excellent and working condition rather than just to spec, and has a wide set of knowledge tools but knows when to limit their use.
- Anti-patterns are harder to detect than immediate benefits.
- Knowledge is the ability to entertain an idea; doing something requires a deep-level understanding of it. Acquiring the latter requires vigilance because recognition is often mistaken for understanding.