Three challenges in game programming

Each field of programming presents its own challenges, and game programming is no exception. In fact, I would say that a game is among the hardest things you can program in general. Why? I can think of three main reasons, which are closely related, as we will see.

1. Performance matters

It is often said that you should “write first, optimize later”. This is only true to a very limited degree for games. If you write your entire game without regards for performance, it might be nearly impossible to get it to run well. To create a game that gets decent frame rates on all target devices, you have to keep performance in mind from the start.

For example, on Android, the garbage collector has a tendency to freeze your entire program for hundreds of milliseconds while it goes and takes the trash out. (It’s a bit better on recent versions, I’m told.) This results in visible stutter and might ruin the experience for any kind of real-time game, so you want to avoid it at all costs. So you have to pre-allocate and reuse all of your temporary vectors, matrices and other constructs as much as possible, and doing that after the fact means you have to chase down all your allocations and change many lines of code.

Bad example:

public void render() {
  // Either getTransform makes a copy, or we have to do it here.
  Matrix3 t = new Matrix3(getParent().getTransform());
  t.multiply(this.transform);
  this.renderTransformed(t);
}

Good example:

private final Matrix3 t = new Matrix3();
public void render() {
  t.set(getParent().getTransform());
  t.multiply(this.transform);
  this.renderTransformed(t);
}

This goes to show that in games, code hygiene is not the primary concern. Dirty and fast is better than clean and slow. No sensible game engine strictly adheres to the Model/View/Controller model. Engines become increasingly data driven, which can have huge speed benefits, but at the cost of modularity and abstraction. This is a sensible tradeoff, because games are typically developed, shipped and then hardly maintained anymore, so it matters little if the final version is a ball of spaghetti code. Here’s a great article on the kind of stunts game developers can and will pull at the last minute, because the game needs to ship, pronto.

Performance concerns will also sometimes drive you towards programming languages that are more performant, but harder to use. It’s nice being able to write everything in C# or Java, but sometimes a judicious amount of C++, C or even assembly is required.

2. Abstraction is hard

Because performance is such a key feature of games, game programmers necessarily have to cut corners when it comes to nicely abstracted, well factored, well layered, and generally “clean” code.

For example, it would be nice if we could fully separate rendering from game logic. Suppose we’re building a platformer, then a powerup might look like this:

class Powerup extends GameObject {
  private Vector2 position;

  public void update(float delta) {
  }
}

Note how it doesn’t contain any rendering code. The renderer could do something like:

public void render(Game game) {
  for (GameObject obj : game.getObjects()) {
    if (obj instanceof Powerup) {
      drawSprite(powerupSprite, obj.position);
    } else if (obj instanceof Player) {
      // render player
    } else if ... // and so on
  }
}

(In real life you’d use a visitor pattern instead of a series of instanceof checks, but it doesn’t matter for the example.)

Now we realize that a stationary powerup is a bit boring, and also harder to detect visually. Always looking for ways to make our game more juicy, we decide to make it bob up and down, by simply adding a sine function to the y coordinate. This will affect display only; the actual hit box shouldn’t move.

But for that, we need to keep track of the angle we put into the sine, and where to put it? The renderer is stateless, so we can’t put it there. One approach is to keep a second tree of objects, which wrap the game objects, but are just about presentation logic:

class PowerupRenderer implements Renderer<Powerup> {
  private final Powerup powerup;
  private float sinAngle;

  public void update(float delta) {
    this.sinAngle += BOB_SPEED + delta;
  }
  
  public void render() {
    drawSprite(powerupSprite, powerup.getPosition());
  }
}

But this adds memory overhead, memory fragmentation and the need for another pass through all objects during the update. Moreover, keeping those objects in sync when game objects are created, reparented and destroyed is tricky.

We can put the angle on the game object, but it isn’t really related to game logic at all. Still, we end up throwing abstraction out the window and end up with a class that’s a mixture of game logic and presentation logic:

class Powerup extends GameObject {
  private Vector2 position;
  private float sinAngle;

  public void update(float delta) {
    this.sinAngle += BOB_SPEED + delta;
  }
}

Another place where abstraction might be harder to do is when working with APIs that you don’t control, like OpenGL. You can make a single draw call like I did in the example above, but unless your game only has a handful of sprites on the screen, it will be too slow. That means you need to batch your draw calls, which means they have to be made from a centralized place in the code. It’s certainly doable, even in a pretty readable way (e.g. libgdx’s Batch class) but you still end up catering to the API and not to your own code structure.

3. Games are hard to test

In a typical web application, it’s possible to get 90-99% unit test coverage without much effort. Requests come in, responses go out. Testing the user interface is harder, but with tools like Selenium it’s still possible to check that at least the DOM makes sense.

Games, however, are all about the user interface. A lot of the code will be involved in drawing graphics, and there is no sensible way to test this. Unlike a web browser, OpenGL doesn’t have a notion of what the scene looks like, so the best you can do is assert things like “the pixel at position 372, 836 should have the colour #df884b”. Not exactly a useful or maintainable test. It’s quite possible to take and store a screenshot and have the test compare to that, but most of the time you’ll be changing code with the very purpose of changing what’s on the screen, so this is not very useful. It’s also possible to stub draw calls and verify that they are invoked in the correct order with the correct parameters, but the test ends up closely reflecting the actual production code and acts more as a “change detector” than as a useful verification.

Additionally, many problems in games don’t actually result from code, but from other game assets: models, textures, maps. Suppose a texture is mapped upside down, rendering your hero’s face on her legs? Or the normals are inverted so everything facing the sun is black? No automated test is smart enough to detect such problems; it takes human effort, and a lot of it.

That doesn’t mean that games are entirely untestable by unit testing. Stuff like a matrix library, for example, is very testable, but you wouldn’t typically write one yourself. Utility functions and classes are often easy to test as well. So is the game logic of a typical puzzle game, but that of your average first-person shooter – since it typically depends on a third-party, nondeterministic physics engine to do the bulk of the work – is not.

There are those who advocate unit testing, but most people don’t seem to be unit testing their games very much. The truth may be in between: perhaps there’s more that game developers (including myself) can do to make their code more testable without compromising performance.

Conclusion

I’m not trying to say that game programming is the hardest thing in the world. Each field has its own share of challenges, sometimes much larger than the ones I mentioned here. Still, game development seems to have more than its fair share of them. That’s what keeps it interesting!