Design Patterns: State Machine Pattern for Game Developers
From traditional Software development, we have multiple design patterns at our disposal. However, they must go hand in hand with clean code and QA methods to be used effectively, otherwise they can turn into anti-patterns, hindering you instead.
In the following I want to introduce some patterns in detail. I have used these with success in my games.
In this article we will take a closer look at the State Machine.
What is a State Machine?
The behavior of a state machine depends, as its name suggest, on its internal state. Each state is represents a certain kind of behavior of an object. The object may behave differently depending on its internal state.
State machines have a set of states and a set of transitions. A transition can lead from one state to the other. Workflow diagrams and similar can be considered a type of state machine and in fact can be used as visualization for a code implementation (although, this is not always beneficial).
Because they are originally rooted in computer science theory i.e. mathematics, there is a well-defined graphical annotation and a few theorems around them: two states are considered to be equal if they have the same transitions towards the same states, there are also algorithms to simplify complex state machines with many states. This is out of scope for this article, but I wanted to mention it if you are curious enough to look it up.
Examples of state machines I’ve used for game development:
- timers
- switching between one or more screens
- keeping track of checkpoints on a racetrack (although the states are dynamic here)
- a custom music player: on play, music is faded in, being played, then faded out
- target management systems, where individual targets can be unknown, known or registered. I designed two of these systems, one as stealth mechanic and the other as discovery mechanic
- keeping track of requests in multi threaded systems (requested, waiting, received)
- firing modes for projectile weapons (e.g. full auto, 3-round burst)
- AI-controlled enemies or pawns
A concrete example for game screens transitions implemented as state machine annotation looks like this:
How to Implement
Single-class State Machines can be implemented in two ways:
- using enums and switch/case structures
- very complex state machines that span across multiple classes can be implemented as Observer Pattern
- you do not have to use either methods, as any object invoking an internal state change on another object or on itself already uses a state machine pattern. This can be “hidden” in plain sight (although I’d avoid that personally because hiding anything in code is a bad idea. But that shouldn’t stop you from trying out and see, I think)
When to use
- ideal for simple use cases with 2-4 simple internal states. If you need more, consider using other patterns
- if you use many booleans to distinguish between states and need those states to implement logic
When not to use
- This pattern is the one that I feel like I overused during my career as software developer. It can quickly lead to ugly code that is a nightmare to maintain
- Defining complex behaviors
Tipps
- Rule of thumb: if you can pack a state machine into a single class, use the switch/case implementation!
- If you have a game with multiple screens that you can go back and forth, that already in itself is a state machine. This state machine works across multiple classes and can get very messy if you are not careful. State machines can be implemented via the Observer Pattern instead if this is the case
- Because of how easy they are to implement, think twice before using one. Chances are you don’t always need to distinguish between states in this particular way
- Keep your state machines as small as possible, no matter how you chose to implement them
- If each state has too much logic, you probably should reconsider your design choices
- The code executed in each state should not exceed ~150 lines of code (even if you outsource to a method). If it’s longer, see the following point:
- Creating different classes for each behavior e.g. via the Strategy or Observer Pattern can be more beneficial
Hope this article helps on your gamedev journey!
Do you have any feedback or questions? Feel free to let me know in the comments!
I appreciate the opportunity to help or improve.