What I wish I had known about gameplay programming 20 years ago


As you may guess from this post’s title, I am a moderately old guy who recently turned 35 (or was it 36? ouch!). As it happens, I’ve now worked over a decade in the video games industry as (among other programmer roles) a gameplay programmer for some of Sweden's largest game studios. I am not going to appeal to authority here and let this article stand on its own merits (to the extent it can be objectively validated, as it is fairly subjective), so let me explain what I want to say. Prepare for a wall of text - as a coder I assume you’re adapt at the lost skill of reading!

I started programming at a fairly early age; at age eleven I was dabbling in QuickBasic; by age 15 I had progressed to attempting to write games in C++ w/ Direct Draw 3. Maybe not as impressive as the people that were coding in assembler at age nine, but bear with me. Back in those days, you had to write the engine yourself. There was no Unity, and Unreal was a mostly proprietary engine. At best you could mod Half-Life.

So to further elaborate on how the above is any relevant: I never finished games as a teenager, despite programming a lot on games. In part, because circumstances then were much different than today, as already pointed out. However, in other part, because my ideal view of programming back then was flawed. I had decided that I liked programming, and that it was my ultimate goal in life to write interesting code, rather making an interesting game.

At this point, I did already mention C++, the most well known gameplay programming language, and so I might also attempt to dive a bit into the language debate towards latter sections of this article. Everything I say represents my subjective viewpoint, however I am not the kind of rabidly political language fan I was in my youth. Languages are just tools to solve problems, and it’s akin debating to whether a standard hammer or a sledgehammer is better for setting up a tent.

Today, I know that programming is about solving business problems with the help of code. It’s not about the code itself or thereby related intellectual self-aggrandization. Gameplay programming then is about implementing a fun game is the ‘best’ way, where we have to recognize that there is no code that objectively is best or can be best simultaneously in every single aspect. Characteristics such as performance, maintainability, development time, and more need to be weighted and considered.

What characteristics does one ought to value for gameplay code then? The answer to that question, as always, is “it depends”, and more specifically on your team size. If you have one hundred programmers on your own and supporting teams, then to be sure, writing perfect code (to reasonable limits) is viable and desirable. If you one the other hand are programming alone or in a small team (a position the vast majority of developers on this site are in) the rules of play are entirely different. The rest of this article assumes that you are an indie gameplay programmer with few resources trying to complete a game, not an engine nor just exercising your programming skills, so let’s start discussing coding in that context.

Above all, and what is going to be the key point covered in today’s article (maybe I will make a series for other aspects), you need to program fast. Why? Games necessarily have a lot of code, no way around that. Embrace change: Your team’s game designer (even if that’s you) needs to be able to change gameplay that doesn’t work, and that will require rewriting existing code. Don’t get married to your code so that you would refuse to change it. Trying to predict what requirements on the code might change ahead of time is futile and counter-productive, since instead it increases coding time and bloat. Another way of putting it is that gameplay code has a lower human read to write ratio than code of most other programming disciplines.

With that mind, most time spent coding is actually spent staring at the screen and figuring things out, rather than entering text with your keyboard. Can we speed that up? Let’s start with the code itself. There we can do wonders by picking a good modern IDE. For almost everyone working on games that is going to be Visual Studio with Visual Assist installed. Visual Assist makes navigating C++ code via keyboard shortcuts (think, near instant find definition & references) ridiculously much faster than in standard Visual Studio, and also supports e.g. C#.

Along the previously established tangent, fast iteration is incredibly important. What I mean by that is that when you make a change in code (or a data file) until you see the results in the game. Gameplay programming is very visual, and is somewhat different from other programming disciplines in that you’re rarely trying to satisfy and optimize objective metrics, but subjective “is it fun” metrics. You gradually keep testing and tuning until you reach ‘fun’.

Again focusing on coding itself: fast iteration needs fast builds. C++ leaves something to be desired in that regard, with historically massive build times necessary to deal with its complex parse structure, though you can find solutions that have been around a while like Incredibuild, or more experimental and upcoming ones like Unreal’s experimental live coding. C# has generally good out of the box build times even if it too can be slow for non-toy projects. Modularization (into dynamic libraries/assemblies) can obviously help with that, but is not necessarily viable for gameplay code as shall be explained later.

Breakpoint debugging, learn it! For many years in my youth I did not know about this or thought it was fringe usage. However, it is usually a faster and better alternative to logging when it comes to figuring out bugs. Conditional breakpoints and stepping around by overriding the instruction pointer are key things to try. Moreover, simple code that does one thing per line is usually more breakpoint friendly, so it encourages straightforward code that is easy to modify.

Back to fast iteration, the game engine itself plays a major role for iteration speed. Anecdotally, I am using Unity for my current project, and it is not uncommon (due to something known as domain reloading in C# to ensure a full global state reset) that it takes 20 s to start the game when you press the play button. Consider that you might easily be starting the game more than a hundred times in a workday and you will see the problem. From what hear Unreal is moving in the right direction here with faster startup times, which is a major selling point to the indie developer who needs to maximize their time efficiency.

Now would be a good time to touch on a probably familiar subject since there are likely Unity developers in the audience: Singletons and global state, but also how gameplay code is structured. A fact of life is, these make unit testing impossible. An equally important fact of life is, games don’t necessarily benefit all that much from unit testing, especially when it can be supplanted by basic integration and system tests. A thrice important fact is that any given piece of gameplay code may need to read data from, or perform an action in (subject to interface restrictions due to threading, command queues, and what else) any other piece of gameplay code. You do not want code architecture to dictate gameplay possibilities. You always need a mechanism to facilitate gameplay code having full access to itself. Either you need to pass around a pointer/reference to the full gamestate everywhere, or you accept that anyone can call singletons/the global environment. Unity by default already exposes a lot of singletons (or similarly) static interfaces. A hybrid approach is also possible, where you do a best effort to keep a World/Universe/Gamestate class of which you pass around a reference, but that if needed is also accessible by a global or singleton. No matter what choice you make, you will have to live with it forever in that project. Make sure you’ve considered all impacts.

Global state and threading don’t necessarily mix all that well. But so another fact of life is, a lot of gameplay code isn’t possible to easily parallelize (look up Amdahl’s law). Many game simulations (and indeed designer specified game rulesets) rely on operations happening in a certain order, and massively complicating the code to track intricate task dependency chains to unlock additional parallelism simply isn’t worth it. Long live the for parallel each loop which is almost always good enough in terms of letting you put a bunch of work on many cores! Complicated multithreaded programming can suddenly turn very easy with ParallelForEach. The main thread paradigm running the gameplay loop isn’t going away anytime soon for small teams, despite smart ways of utilizing additional cores in various engine and even gameplay subsystems co-existing.

Wrapping up the topic of singletons and global state, let me just say that organizing your Unity project according to singletons certainly can save time and be a very pleasant informal architecture for a small team project, being a good compassion to Unity’s overall loosely and leisurely written MonoBehaviour events, if you can live with the downsides. If will not work very well if you’re on the other hand trying to eliminate domain reloads in Unity, decided that you need unit testing for more than some leaf level utility classes (hardly gameplay code), or if your team is large and maximal separation of concerns is necessary to minimize accidents.

Let me sidestep briefly here into scripting languages, since we already got into the serious meat of the discussion on where gameplay code lives. Did you ever feel like you needed to add a layer of Lua, Python, or whatever to “make the game moddable” and/or “enable scripters to work independently”? For the love of all things, do not make this classic mistake - it can and is at serious risk to kill your project. Gameplay code is inseparable from your primary code environment. Like I mentioned, any piece of gameplay code might need to communicate to another other piece (aside from whatever rare strong structural modularization is possible). Adding another scripting layer means you will now have a build an interface rather than gameplay code, this takes potentially huge time (not just up front), makes things much harder to debug, and necessarily much slower even performance wise. One of those “cool” ideas that sound good until you ever tried it in a serious project. It is fine to say work primarily in C# (or even a language like Lua if you hate yourself) and call down into a C++ engine, but the key is then to minimize a common interface that does not need to propagate any gameplay logic.

I did promise to debate language choices a bit, so I am going to again bring up the language that I have used very extensively in my career besides C++: Namely C#. It has, for mostly good reasons, become a very popular language for gameplay programming thanks to Unity. Build times are OK, it has very helpful static typing, and it has most modern features you could wish for and is not yet (but maybe getting closer to with additional language releases) being over-bloated by features that do similar things. What’s not to like for gameplay programming?

It is very important to understand the one dark side of C# that can kill your project: It is a garbage collected language. Unity’s garbage collector (GC) in particular is horrendous. If you’re not familiar with the symptoms, consider that the GC will stall your application at random points if necessary in order to collect garbage from memory. The player will therefore experience random frame lags, which injects a very bad feel into playing the game.

What this entails for your project is twofold: In part you will have to spend a lot of extra development time trying to reduce memory allocation impact through one of two strategies: Either to have zero transient allocations that survive beyond generation 0-1 during runtime (good luck with that), or just generally reduce the time collections take by buffering objects to reduce garbage pressure, plus designing code in a particular way that makes it easy for the GC to analyze liveness of. The second important point is that you might figure this out too late, and you can get into a situation where there’s practically nothing that can be done about it with reducing project scope.

Sure, Unity in particular gives you additional weapons to fight GC problems with now, such as native containers that allocate memory on the native heap and circumvents garbage collection (while also enabling you to work with Unity’s new Burst compiler). Using such features however come with increases in development time, and as the connecting thread in this article has stated, reducing programming time is probably going to be your most major concern.

C# is not the only language with a GC. In general, pay attention when selecting languages, a large proportion of today’s most popular languages utilize GC. That’s a major contributing factor to why C++ is still such a popular gameplay programming language, although you can thrash the heap with random allocations and delete/frees it’s usually possible to go back and solve that without rewriting the entire project codebase.

People like me used to not pick C++ back in version ‘98 because it usually took longer time to code things with it (than e.g. C#). That is becoming less true for proficient C++ developers (smartly) utilizing modern language futures to prevent many classical time drains, coupled with good IDE support to instantly navigate around their code base. Given that using C++ can actually save significant time on not having to deal with GC issues, and may even save your project, it is unsurprising it stays in such high use in the video game industry. I am still coding in C# right now evidently, but it’s not a straightforward choice.

It is time to wrap up for today. The key takeaways: Don’t be afraid of writing specific code for now rather than for the future. Don’t be afraid to look for the best tools for the job. Don’t be afraid to challenge ideas that sound cool on paper but carry major implications. I know, all of this is easier said than done.

Thanks for reading this far! Let me know in the comment section if I should explain something in more detail, but aside from that Google is your friend. This already became a long article despite touching just a fraction of what I thought I should have written about.

---

If you enjoyed this post, please help me test my multiplayer game in pre-alpha with the working title Rocket Pimp here at itch.io if you’re on a desktop computer with Chrome, Firefox, or Edge. The game is in a very crude stage, but I need relevant feedback and data to make upcoming design decisions. Moreover, I am going to need to hire freelancers to finish certain aspects of the game - if you would really really like to work on this game, do let me know (join the Discord linked in the game).

Leave a comment

Log in with itch.io to leave a comment.