Game Programming Patterns - A dive into the Command pattern
I decided to read the book "Game Programming Patterns" by Robert Nystrom because I wanted to improve my development skills, not just game development. I had heard so much about this book from experts in the field that they called it the bible for game development. I was eager to dive into the book and learn the secrets behind creating amazing and well-organized games. Since the book promises to reveal patterns that make games more engaging, I knew it would be the perfect resource to take my projects to the next level.
In "Game Programming Patterns," I immediately discovered the Command pattern. It was the first concept discussed, and I found it fascinating. The Command Pattern showed me how to organize my code to make it more flexible and manageable. By creating separate commands for each action, I was able to control and coordinate the different aspects of my projects more efficiently. It was like a conductor conducting an orchestra and bringing everything together in harmony. I was excited to implement this pattern and see how it would improve my game development process.
Robert Nystrom refers to the “gang of four" several times in this chapter. By this he means nothing more than the Gang of Four Design Patterns, a collection of 23 design patterns included in the book “Design Patterns: Elements of Reusable Object-Oriented Software”.
When approaching the command pattern, command actions are often compared to callbacks because of their decoupled architecture. Some even say that "Commands are an object-oriented replacement for callbacks".
In "Game Programming Patterns," the author presents two possible use cases for the Command pattern.
- Handling user input
- Creating undo/redo actions in the game
Based on the first use case, the importance of this pattern to the health of our game or software can be seen (yes, this applies to other solutions as well).
Let’s now work our way through some examples…
Imagine you have 4 global methods that are used to perform actions in the game (Attack()
, Jump()
, Block()
and Crouch()
). Somewhere in your game source code we will call one of these methods based on user input.
The method that handles this logic will be called once per frame by the game loop. A simple implementation of this method would be:
fn handle_user_input(&self, button: XboxButton) {match button {XboxButton::A => jump(),XboxButton::B => block(),XboxButton::X => attack(),XboxButton::Y => crouch(),}}
The problem with this implementation is that there is no way to assign user input. This is a certain problem because many users want to edit the game controls.
And this is where the Command pattern comes in, by extracting these methods to commands and making them “interchangeable".
One step we can take to improve this is to create a structure that stores the pointer to the associated command.
struct InputHandler {button_a : Box<dyn Command>,button_x : Box<dyn Command>,button_b : Box<dyn Command>,button_y : Box<dyn Command>,}
With this structure, we can change the behaviour of the button by changing only the pointer of the command that performs the action.
fn set_button_mapping(&mut self, button: XboxButton, command: Box<dyn Command>) {match button {XboxButton::A => self.button_a = command,XboxButton::B => self.button_b = command,XboxButton::X => self.button_x = command,XboxButton::Y => self.button_y = command,}}
After these brief examples, we can redesign the handle_user_input
method to support calling the new execute method of the commands struct. In C++, we can use inheritance as we know it, but since I’m using Rust to show this example, I'll use traits to emulate inheritance in Rust.
Disclaimer: These code samples were not written to be perfect; they are intended to show how the pattern could be applied.
trait Command {fn execute(&self);}struct CrouchCommand;impl CrouchCommand for Command {fn execute(&self) {// perform action to crouch}}struct AttackCommand;impl AttackCommand for Command {fn execute(&self) {// perform action to attack}}
If we assume that there is a method to change the mapping of the inputs in the InputHandler
structure and that the structure contains the implementation of the handle_user_input
method, it looks like this.
fn handle_user_input(&self, button: XboxButton) {match button {XboxButton::A => self.button_a.execute(),XboxButton::B => self.button_b.execute(),XboxButton::X => self.button_x.execute(),XboxButton::Y => self.button_y.execute(),}}
As mentioned earlier, this approach allows players to customize the input mapping according to their preferences or to dynamically change the mapping during the game, which provides more flexibility and control.
After analyzing the practical implementation and advantages of the command pattern in game development and general software development, it becomes clear that the command pattern is of utmost importance in both areas.
In game development, the Command pattern enables customizable input mappings, implementation of undo/redo functions, and dynamic changes to game actions. It improves code organization, maintainability, and reusability by enabling clear separation of duties. In general software development, the Command pattern promotes loose coupling, enables transactional operations, and facilitates the implementation of complex user interactions and workflows. It's a valuable tool for building modular, extensible, and adaptable software architectures.
Overall, the Command pattern allows developers to create systems that are easier to maintain, improve, and customize, making it an important pattern for both game development and general software development.