Book Review: Game Programming Patterns
I don’t write book reviews on this blog, but this book was so good I’m making an exception! If you are even remotely interested in the programming side of game development, stop reading this right now and read Game Programming Patterns by Robert Nystrom instead. Or stick around if you need more information.
Your first reaction might be to wonder if software design patterns are still relevant in 2021? This talk from 2017 on the surface seems to suggest not, but if you watch it, you’ll see that it ends on a hopeful note: that perhaps we should be giving patterns a second chance instead of rolling our eyes and ignoring them.
My pet project for the past year has been to find an alternative game engine to Unity to use for making turn-based strategy games. I decided on MonoGame with my library code on top for making turn-based strategy (TBS) and Roguelike games, so I’m taking a very code-heavy approach compared to Unity. I was also sceptical at first, but to my great surprise, I have found the design patterns in this book to have great utility after all!
The book consists of 5 sections:
- Design Patterns Revisited
- Sequencing Patterns
- Behavioral Patterns
- Decoupling Patterns
- Optimization Patterns
Design Patterns Revisited
The first section revisits six design patterns from the Gang of Four’s original Design Patterns (1994) book, focusing on those relevant to game programming.
Command
The first pattern covered in the book was a surprise to me. In nearly 15 years of dabbling in game development, it has never occurred to me to use this pattern. The fault here is perhaps in the classic example of undo-redo in user interfaces - something rarely needed in games. This book supplied a new example: using commands to implement reconfigurable user inputs.
For example, you can write your code for input handling to allow the left arrow or A or a left press on the DPad to produce a new DirectionalMoveCommand(-1, 0)
.
Even cooler than that: you can replace the input handling code with AI to have your AI controlling characters the same way the player would.
Flyweight
The second pattern also surprised me. I had discarded it as a meaningless relic from days when we had much less RAM at our disposal long ago, but again the examples showed the errors in my thinking. The examples show how our modern game engines effectively use this pattern to reduce the memory cost of rendering many instances of the same 3D model.
It also helped me to better structure my code for loading map data from TileD, once I understood that the tileset and tilemap are an instance of this pattern. Each tile type is loaded once - all tiles of that type reference this definition.
Observer
This pattern was one that I found uses for long ago, so this chapter did not hold many surprises. For the most part, the author debunks some of the performance concerns game programmers might have, highlights the real problems you may run into and takes some looks at more modern ways of implementing the pattern.
Prototype
This pattern was an interesting one! The Prototype pattern is perhaps the only Creational pattern in the Gang of Four book that I’d never really found useful. This book shows that the problem it solves is a bit of a contrived one and that using the pattern might not save us that much code at all. Then just at the point where you might consign it to the dustbin, he throws a beautiful idea at you: use prototypes to reduce the duplicated data in your game’s data models.
Since I haven’t started mass-producing content like many different sub-types of monsters, I haven’t used this - yet.
Singleton
This pattern was mainly an explanation of how Singleton is a harmful anti-pattern to be avoided. Service Locator and Subclass Sandbox are the suggested alternatives. I discovered that MonoGame has ServiceLocator built into its base class, so there’s a good chance I will end up using this instead of dependency injection. In some cases. The main reason is that the game states have so many dependencies that the constructor injection has become messy. This pattern will let me reserve dependency injection for the main dependencies of a state.
State
I’ve been using something resembling state machines to switch between game modes for years, so this chapter wasn’t news to me. Nevertheless, it has a few extra techniques to share including, Hierarchical State Machines and Pushdown Automata, so it was worth the read.
Sequencing Patterns
The sequencing patterns will be very familiar to you if you’ve ever done any game programming. This section formalizes the game loop and the patterns around it that form the heart of any game engine.
Double Buffer
I guess this is technically one of the first game programming patterns I learned - nearly two decades ago, from what was probably a partial reading of Michael Abrash’s Zen of Graphics Programming. This chapter adds the inspired idea of using double buffering in contexts other than the graphical one everyone knows.
Game Loop
If you are new to game programming, this should probably be the place to start reading. If you’ve been making games for a while, this is probably all old hat to you. There’s some coverage of subtle bugs you might run into due to how the game loop forces you to structure your game logic into frame-sized chunks. Being aware of these subtle pitfalls should be very helpful to new game programmers.
Update Method
This chapter covers the simple idea that underpins the programming model of most game engines. Having many objects that need to update themselves, you inevitably give them an update method that simulates one frame of behaviour and then call these once every iteration of the game loop.
Behavioral Patterns
These were all new patterns or twists on familiar ideas.
Bytecode
The idea of having a scripting language inside your game for quicker iteration on gameplay logic is not new - I think Civilization IV (2005) is probably one of the most widely known early examples of this technique.
The Bytecode pattern takes this one step further by compiling your scripted game logic into bytecode at build time. Your engine can then execute the more efficient, densely packed bytecode in a virtual machine at run time. I consider this mainly a performance improvement over using an interpreted scripting language in your engine.
Subclass Sandbox
This inversion of the template method pattern has you provide a set of primitive operations in an abstract base class and then implement a wide variety of behaviour in subclasses using the protected methods provided by the superclass. This pattern provides an easy way to share functionality between many subclasses. This pattern pairs well with Bytecode to limit what your scripts are allowed to do.
Type Object
This pattern combats the deep and wide inheritance hierarchies that often arise when modelling many similar but different entities in a game. The example used is of monsters for a roleplaying game. It uses composition to have each Monster class reference a Breed class, which acts as the type. Essentially the type system is moved from being classes to being objects at runtime. The types turn into data rather than code.
Decoupling Patterns
The decoupling patterns didn’t contain any surprises. Except for Component, these are patterns from enterprise software engineering applied to game programming.
Component
If you’ve used Unity, you’ll be very familiar with this pattern. Essentially it is the idea of composition over inheritance. More complex game entities are composed of many reusable smaller components.
Event Queue
Event queues are a staple of enterprise software engineering, but I never realized that they are also lurking in the update loops of our game engines. They are typically used to handle input in game engines. The explanation of how to implement this efficiently with a ring buffer was great for implementing my own.
Having a second event queue to trigger non-essential secondary effects in reaction to commands in my game has turned out to be very effective. The main game logic is much simpler: emit an event at the point where the side effect should’ve occurred. Then later in your game’s update loop, process those events, leading to all the side effects like updating animations, playing sound effects, spawning particles, etc.
Service Locator
This pattern provides an alternative to Singleton for accessing components needed in many places without passing them into every class that needs them. Some good examples of functionality that is useful to share with a locator is a random number generator, logger or audio manager. These are widely used but rarely the most important dependencies, so using a service locator instead of dependency injection can make sense.
Optimization Patterns
The optimization patterns were interesting, but if you’ve been following Unity’s efforts at switching to an Entity-Component-System architecture (ECS) it probably won’t hold any great surprises for you…
Data Locality
This pattern explains how to organize data to optimize CPU performance by avoiding cache misses. In a nutshell: densely packed arrays of data are much more efficient than using the object-oriented approach. Essentially an explanation of why Entity-Component-System (ECS) architectures perform so well, but I suspect written before ECS became a buzzword, so it doesn’t mention that architecture at all.
Dirty Flag
A simple idea: defer expensive calculations until they are needed to avoid doing unnecessary work. It’s so simple it hardly deserves being called a pattern, but it’s nicely explained here.
Object Pool
This pattern is another one that’s probably old-hat to most game developers (maybe also to web developers that have used connection pools). If you have some objects that are expensive to allocate or that you go through very quickly so that it would cause hundreds or thousands of allocations per frame, then it’s time for an Object Pool. Instead of creating new objects, you pre-allocate a pool of them. When you need one of these objects, you get it from the object pool. Once finished using it, instead of deallocating it, you return it to the pool for reuse.
This pattern can be helpful to improve performance as well as to reduce memory fragmentation.
Spatial Partition
This chapter will be old news to game developers that have tried implementing collision detection from scratch. The treatment here was a bit light: just a quick introduction to the concept of spatial data structures. Then a bunch of links to specific algorithms that you can use to educate yourself. Although I consider this an optimization pattern that you won’t always need, when you do need it, you REALLY need it. It’s a crucial introduction for people that are new to game programming.
Conclusion
In my talk advocating reading this book, I compare it to the original “Gang of Four” Design Patterns book based on how many patterns I have used myself. The number comes to approximately 50% of Design Patterns used over a 15-year career, versus around 80% of Game Programming Patterns. However, the patterns in Design Patterns apply to many programming domains (including game programming), while those in Game Programming Patterns are laser-focused on the game programming domain.
If you are specifically interested in game programming, then I would say Game Programming Patterns is a must-read. The book is about 100 pages shorter than Design Patterns, and the writing style makes it a breeze to read! So stop reading my crappy review and read Game Programming Patterns by Robert Nystrom instead!