If you’ve ever played a game like Fire Emblem,
Pokemon, or pretty much anything turn-based, there’s a great chance that you have
experienced the power of the Command Pattern. But! The command pattern is not limited to just
turn-based games! So today we’ll dive into the Command Pattern, learn what it is and how it
can help us make our game’s logic preservable, interchangeable and even undoable!
Welcome to Programming for Production, a series on iHeartGameDev where we break down
relatively complex software development topics so we understand how and when to use them
while developing our games! A quick but huge thank you to the channel patrons for selecting
this topic! And now let’s get started!
Controller remapping, undo and redo
systems, turn based battle systems, replay systems are just some of
the amazing features we can build using the Command Pattern! Today,
let’s learn the fundamentals.
In modern programming we define classes, and
classes have these internal functions called methods. Methods perform specific logic
that we also define. To use these methods, we call, or invoke, them in other parts of
the class or other parts of the codebase. Sometimes these methods require data called
parameters that are used in the method’s logic and sometimes they don’t. And… none of
this is likely news to you if you’re researching the command pattern.
But, today we want to take a look at a specific aspect of methods: execution.
The execution of a method’s logic.
When a program is running, it typically cycles
through the codebase scanning line after line of code. Inevitably, it will reach an invoked
method! When faced with an invoked method, the program essentially reads through
it: executing the defined logic. And… then the program moves on.
But here is a question: what if we did not want the logic to be executed immediately? What
if we wanted to somehow tell the program that we have this method and the data for its parameters
but we don’t want the logic to be executed yet! How can we delay logic from being executed
for an unknown amount of time?
Enter today’s powerful solution:
The Command Pattern.
As previously stated, when programs reach invoked methods: that method’s logic
is to be executed immediately. This is the fundamental idea behind the command
pattern: Encapsulating a method and any data that it may require inside a storable object so
that it can be executed at a later time.
What exactly is a “storable object” though?
In object-oriented programming and C#, the objects we are most familiar
with are instances of classes. Classes being the blueprints for an object,
essentially define what the object is. In other words: what it needs to be made, details about it
and what logic it can do: its methods. Instances of classes are when we use that blueprint while
the program is running to create the “object”. These instances can then be stored and use
their defined methods at any time.
And that is the key difference we get by using the
Command Pattern! We replace invoked methods that execute logic instantaneously… with commands!
Commands are classes that wrap the method and logic we want performed. And as we just
learned, we can create an instance of a class, allowing us to execute its methods
and logic at a later time.
So that is the idea behind the command pattern:
storing logic in an object so that it can be used at a later time while the program
is running. But its implementation in C#, Unity and our games are where the benefits
can really shine. Let’s map this out.
There are five components that we
want to understand with this pattern: the abstract command, the concrete
command, the command invoker, the command receiver, and the command client.
Without the command pattern, the client
triggers a method of a particular target: the receiver. Typically the logic
is actually defined by the receiver so it is something that the receiver knows how
to do: a class and a method of the class.
But with the command pattern
we add in a middle layer. Instead of invoking a method, the client will
create an instance of a concrete command… the invoker will store the instance… and then later, the invoker will execute
the command on or using the receiver. And the invoker has the guarantee that
every command can be executed because all commands inherit the ability to be
executed from the abstract class.
So to summarize, the client creates
a command intended for the receiver, but gives it to the invoker because the invoker
can execute the command at a later time.
This is a lot of “abstract” thinking”
so here is a… somewhat “meta” example of the command pattern to hopefully help
conceptualize each component: if I tell you, or “command you”, to like this video: I am the
client, the concrete command is “like this video”, the invoker is your brain, and the receiver
is your hand. I, the client, created the “like this video” concrete command
that was intended for the receiver: your hand. This is because your hand knows how
to move the cursor and press the like button. But! I gave the command to your brain
so that it stores the command instead. Your hand might be busy: holding a phone,
drinking coffee, playing guitar. By giving it to your brain, the command can
now be executed either immediately or sometime late. And that’s why the
invoker is so helpful. In this case, your brain, the invoker, can tell your hand, the
receiver” to move and press the like button.
Cool! Let’s dive into a simple project and
some code for a more concrete example.
In this project we have a lightbulb gameobject! The lightbulb has a script
appropriately titled, “lightbulb” and a method: “TurnOn”. “TurnOn” has the logic
necessary to, you guessed it, turn on the light! We also have a “UserInput” script which
is a Monobehavior class that is constantly listening for the user’s input. It
is currently hard-coded that pressing “spacebar” will invoke the light’s
“TurnOn” method in the scene!
Let’s think of the list of command pattern
components: this example currently has the client (the userInput file) and the
receiver (the lightbulb).
Let’s refactor this hard-coded logic to
use the command pattern instead!
What are we currently missing? The
abstract Command, the Concrete Command and the Command Invoker. How do we
go about implementing each?
All concrete commands should
have an Execute method. This guarantees that when the invoker is ready
to use the logic of its stored commands, all the invoker needs to do is call Execute on any of
them. But in programming how can we be sure that all Command classes have the Execute method?
This is where the abstract command comes in.
Two ideas are to create an abstract
class or create an interface. With an abstract class, we define
methods on a base class that others can derive from. We would write an abstract
Command class, define the Execute method and have our concrete Command classes derive
from this base class. However, this means that the Execute method would always have the
same logic, severely limiting its reusability. Alternatively, we can also make the Execute method
abstract as well. This means that any classes that derive from the base class can and must define the
Execute method’s functionality. This is better, but in C# we have the option to use interfaces,
and that’s the suggestion from this tutorial:
A C# interface defines a contract. This
contract is between the interface itself and whatever class is using it: stating that
the interface’s methods must be defined. So if the Command interface
exposes an Execute method and all Command classes implement the Command
interface: then the classes must define Execute. This sounds awfully like the abstract command
class with the abstract execute method. However, a class can derive
from multiple interfaces, which really increases the interchangeability
and reusability of Concrete Commands.
So, we’ll create a new CSharp script titled
ICommand: I being the prefix for interfaces. We’ll define a Command interface which includes
an Execute member. Worth noting that we can call this whatever makes sense within the context of
our games: IAction, IOrder, ICommand, etc…
Now that we’ve defined the abstract Command,
we’ll move on to the concrete Command. As we now know, all concrete commands are going
to derive from at least the ICommand interface. This guarantees that the Execute method is going
to be on all concrete commands. Plain and simple: If it doesn’t derive from this
interface, it’s not a command.
To name Commands, we will start with
the name of the action or method that the command is performing and add “Command”.
In our example project here, the command is going to turn on the light, so we can call this
TurnOnCommand and have it derive from ICommand! If we think of our meta example from before,
we would consider a “LikeVideoCommand”.
Now… what makes up a concrete
Command? Because of the interface, we can guarantee that the concrete
command will define an Execute method. It’s here where we write the logic from
the original implementation. However, sometimes this logic will need to reference a
particular class instance or take in parameters. So what we can do is store this
data when we create the instance using what’s known as a constructor.
A constructor is a special method always called when we first create the
instance of, or instantiate, a class.
In the case of our TurnOnCommand,
we’ll pass the reference to the lightbulb on instantiation and store
it. And then in the Execute method, we’ll have the lightbulb reference
invoke its “Turn On” method!
Ok! Let’s put this new concrete command to use! Back in the “UserInput” file, we’ll replace
the current logic that is hardcoded. Instead, we will instantiate a new TurnOnCommand
that can be stored in a variable of type ICommand. And below that, we’ll invoke the
TurnOnCommand’s Execute method.
In play mode, we can now see that our lightbulb
will still turn on! Which is great… But! We are still missing a crucial component of
the command pattern: the command invoker. Currently there isn’t much of a difference between
the original and current implementations. BUT by completing the command pattern
and adding in the Command invoker, this pattern enables us to
do quite a few things:
With the invoker we can create a list of all
commands that have been and are to be executed. By adding each command to the list, we create a
history. What makes this special is that: what can be added, can also be taken away! Meaning, we
can relatively easily undo previous commands.
The invoker also handles the actual execution of
the commands. So we can have multiple clients or triggers using the same invoker. To bring back
the meta example: if I were to tell you to like the video, and your mom tells you to take out
the trash, that’s two clients creating separate commands for the same invoker. And the invoker can
even decide how it wants to execute each command in the list. It can execute them all at once, it
can execute the commands that are most recently added, execute the oldest, the most important…
That’s the cool thing about the invoker, is that we can tailor its functionality
to the needs of our game’s systems.
This leads to a noteworthy point about the Command
Pattern: its actual implementation can vary. More specifically, while the interface and
concrete commands are typically structured how we’ve used them here, the logic within the
command invoker is what can take on many forms.
So while this lightbulb is a rather
simple example, in the future, we’ll take a look at alternative implementations
that conform to the game we’re actually making. Today’s video is more about understanding
the concepts behind the pattern.
For this first example of what an invoker
can be, let’s create a new class called “LightSwitch”. This lightswitch class
will be in charge of turning on our light. To do so, it will need to somehow access the
TurnOnCommand that is currently in the client, and call Execute on that Command. The most basic
way we can do this is to store a single Command within the invoker, called OnCommand. We would
set this in the constructor of the invoker. And in a local method, called
PowerOn, execute the OnCommand.
In our UserInput file, we can now create and
store the invoker, instantiating a LightSwitch. When we instantiate the LightSwitch,
it expects the TurnOnCommand. And now when we press spacebar, we’ll
use the LightSwitch’s PowerOn method! Testing this, we’ll see the
light once again turns on!
Awesome! At this point, we have successfully
decoupled the client and the receiver as much as we possibly can using the command pattern.
What this means is that the client knows as little as possible about the receiver, which
ultimately makes them both more independent.
Ok! We now realize that a lightbulb
and lightswitch should also be able to turn off. We can add the “turn off”
functionality to the light in multiple ways, but the way we will do it today is by
refactoring our lightbulb class and commands. Instead of a “TurnOn” method in the lightbulb
class, we’ll have a “TogglePower” method which will either turn the bulb on or off
depending on its current state.
We’ll then rename and modify the TurnOnCommand
appropriately to TogglePowerCommand and change the Execute definition to
call the lightbulb’s “TogglePower”. We’ll also rename the PowerOn method
of the lightswitch to TogglePower as well! In the UserInput class, we’ll rename
the TurnOnCommand to TogglePowerCommand, and change the LightSwitch’s “PowerOn”
method to “TogglePower” as well.
Testing again in play mode and now when we press
spacebar, the lightbulb will turn on and off!
Now this is one of the simplest examples of
the invoker, but we can make the invoker do more. This implementation is missing what most
invokers include: a stored list of commands. And without the list, we’re not maximizing
the Command Pattern’s usefulness.
To really showcase this, let’s extend our
example a bit. We’ve gone ahead and upgraded our lightbulb. It is now one of those fancy
Philips hue lights that can change color. This functionality is defined within two
new methods on the lightbulb class that will change the bulb’s color to either the
one that is passed in or a random color. And we also have a new command:
ChangeColorCommand. This command will also derive from ICommand and we’ll set the randomcolor
method of the lightbulb in Execute.
This increase in functionality requires
something more advanced than a lightswitch. So let’s remove the code related to this first
invoker in our UserInput file, and then set up our more advanced invoker. Let’s imagine that
Philip hue lightbulbs are controlled with an app. We’ll create a new invoker
class called: LightApp.
Where the LightSwitch stored a single command at
instantiation, the LightApp will instead store a list of Commands over time! For this tutorial,
we will use a “Stack” as the type of list but, as mentioned, there are multiple other
list types that the invoker can use. Of course, this list starts out empty, so how do we actually
add commands like the TogglePowerCommand? Instead of a TogglePower method like the
LightSwitch, we’ll rename that to something more generic: AddCommand. AddCommand
will expect a Command as a parameter, and inside of this method, it will
invoke Execute on the Command argument, followed by adding the command to the
list using the stack’s push method!
In the Client, or UserInput file,
we can add the LightApp invoker. We can then use the LightApp’s AddCommand
method when SpaceBar is pressed, passing in a new TogglePowerCommand. And we can do
the same for the ChangeColorCommand on a separate input listener: we’ll use the “C” key.
Now, pressing SpaceBar will turn on the lightbulb, add the command to the list and execute
it. Pressing “C” will change the color, add another command to the list, and
execute that command. And one more time, pressing space will turn off the light,
adding a third command and executing it.
Wonderful! We can now turn the light on, off and
change its color! But we might notice that this list doesn’t do much for us… yet! The last
thing we’ll cover in this tutorial is one of the many benefits that the Command Pattern
offers which is to undo previous commands. As we know, the Command interface currently
guarantees that all of the Commands will define an Execute method. We can use this same guarantee
to ensure that all Commands have an Undo method as well! So, updating the ICommand
interface, we’ll add in Undo.
Our two commands must now also define the
Undo method. But how does Undo work? When we use each command, we are essentially
telling the Light to do a specific thing, so if we wanted to “Undo”, we
would have it do the opposite! For the TogglePowerCommand, this would be rather
hard to tell the difference between triggering undo and just pressing space again because
both will result in the light turning off or on depending on its current state. But changing
the light color is a different story.
When creating an instance
of the ChangeColorCommand, we will store the current color in a
separate variable using the constructor. This means that when the command is
initially added to the list and is executed, changing the color of the lightbulb, that command
will remember and store the previous color! So to define the undo functionality
of the ChangeColorCommand, all we need is to set the lightbulb instance’s
color back to the stored previous color!
And for the TogglePowerCommand, the
undo will just reverse the current state using the lightbulbs toggle power.
However, we don’t have a place in our code
that calls a Command’s undo method. As we know, the invoker is the middleman that actually
calls the methods of the commands. The AddCommand calls Execute on each of the
Commands and adds it to the list. Let’s create an UndoCommand method that calls Undo on the
last element and then removes it from the list! We can easily remove the last
command using the Stack’s Pop method. All we need to do is add another input
handler for the Client, we’ll use the “Z” key, and invoke the LightApp’s UndoCommand method.
If we enter play mode, we can turn the light on, change the colors and press Z to undo
our previous commands! Fantastic!
The Command Pattern is pretty wild because it can
be defined in so many ways, but hopefully now you can see it can lead to results like the mechanics
we mentioned at the start. In future videos we’ll definitely expand more on this
pattern with advanced implementations to showcase what it can help accomplish.
Awesome! Thank you to all of the patrons for
choosing this topic. It was a blast getting to learn yet another complex behavioral pattern
and make such a fun project. If YOU would like to vote on the next tutorial that we’ll
cover and access this projects files, consider supporting the channel and join the Patreon! And
a special shoutout to one beets and zack etier for the extra support! If you’re interested in
joining an amazing community, we’d love to have