When first reading about this pattern, I found it difficult to picture what the pattern was actually doing or what kinds of situations it would be useful for. I know a lot of people are confused by this one too- who has yet to use Unity's new input system in order to avoid the setup for it? So, I decided to write a blog post about it to really delve in and understand it! I hope my journey of understanding the command pattern can help you to understand it better, too, as it's quite a useful one for cross-platform and multiplayer games!
The Problems
There are a few problems that this pattern can help with that I'd like to mention, though it should be noted that this is not a complete list of situations where the command pattern is useful.
To start, one major factor in game development today is making your game compatible across platforms so that you can play it on a computer, a console such as the Nintendo Switch, or even a phone, and not have to create separate source code for each platform. First of all, if you're trying to make your game compatible cross-platform, how do you account for the different kinds of controllers and buttons for each system? If you make it so that the player jumps when the space
key is pressed on a keyboard, that won't translate to any button on a Nintendo Switch controller or the touch screen on a phone, and your game would only be playable on a device with a keyboard.
The command pattern offers a way to separate the action performed in the game from the button that causes the action to happen, allowing you to map it to whatever button makes the most sense for the target platform, and if there's more than one target you simply map that action to different buttons depending on what your user is playing on.
Another issue that comes up has to do with multiplayer games. When playing a live multiplayer game, what happens in the game needs to happen synchronously for every player in the game, though they may be on different devices and spread all around the world. To make sure the games are kept in sync without using the command pattern, you may have to send lots of detailed information when communicating data about a player's actions to the other players' devices. This would be very inefficient and could create a lag, as the more data you send the longer it will take for the other device to be able to download and interpret that data. For example, "the player does a jump animation and then does x-type of attack with it's respective animation and hits the enemy, causing y damage and the enemy to do it's being-hit animation".
Without the command pattern, you need to send such detailed information in order to make sure each instance of the game stays in sync. This can get quite complicated, and there's a much better way to keep the games in sync. With the command pattern, you can simply share the name or type of actions themselves, in the order they happen, and each game instance will have that action mapped to produce the same outcome; this way, without needing to send the details, the same things will happen in both games.
It's important to note that the command pattern is not the only thing involved in synchronizing game states for multiplayer gameplay, but those aren't the topic of this blog post.
The Command Pattern
I've already given some common problems and how the command pattern can solve them, but what's the actual definition of the pattern? If you look it up, you'll see definitions such as "a request encapsulated as an object" or "a reified method call". Maybe that's all you need to see to understand it, but my response was "huh?".
My best explanation for the command pattern is that it is a way of separating the logic for a command or request from the logic that actually executes commands or requests.
Let's take a look at an example of controlling a player GameObject's behavior through user input in Unity. We'll look at Unity's old Input Manager, and then go over the new system.
Old Input Manager
Previously in Unity, in order to have objects in your game respond to user input, you would do something similar to the following:
public class Player : MonoBehaviour
{
Rigidbody rb;
float jumpForce = 5f;
void Start()
{
rb = GetComponent<Rigidbody>();
}
void Update()
{
if (Input.GetKeyDown(KeyCode.Space))
{
Jump();
}
}
void Jump()
{
rb.AddForce(Vector3.up * jumpForce, ForceMode.Impulse);
}
}
This script is pretty straightforward. In the Update function, we listen for when the user presses the Space
key, and when that happens we call Jump()
, which adds an upward force to the player's rigidbody component which makes the player character jump. Very simple and easy to use, with no setup required. If you're doing a very simple game and know for 100% certainty you only want it to be played on a computer, this is a valid solution.
The issue arises when we want our game to be compatible cross platform. A PlayStation controller does not have a space key- in order to have this work on PlayStation, we would need to change our code to account for the PlayStation buttons instead. This can lead to having different versions of the source code, or a lot of extra code for the game where some of that code is ignored depending on which platform the user is playing the game on.
While this isn't so terrible, and is how Unity's Input Manager worked for a long time, it does have some downsides. For one, it adds complexity and makes it harder to manage user input across devices. As your game grows in scale, so too will the types of input you need mapped to actions, and you will have to write out all the logic for each type of controller. Also, what about games where a button can cause more than one type of response in the game, depending on the context? There is no built-in way with this type of input management to change the behavior depending on the context. Furthermore, there is no support for gestures, touch input, or more advanced features such as haptic feedback or gyroscopic controls that are available on modern devices.
Again, the Input Manager is not inherently a bad choice for your game- it truly depends on the game you're making and if you plan to grow it or make it cross-platform at any point. If not, and it's just a simple game intended to be played only on one device, the Input Manager works just fine.
Now, let's look at how Unity's new input system works.
New Input System Package
I'll briefly go over setting up the input system, but I'm not going to go over every step and won't be going over it in detail. If you need a more thorough walk through, this detailed guide goes over using both the old and new ways of handling user input, and Unity's official manual also has a quick start guide you can follow.
In order to use Unity's new input system, you have to download the input package from the package manager. You then need to create an Input Actions object through the create menu (it's at the bottom if you right click and go to create). You can name it whatever you like; I tend to call it Controls. You then need to create a Control Scheme. Each control scheme can represent a different platform or device that your game can be played on. Next you add an Action Map. In a game I'm currently working on, I have an Action Map for any actions associated with the player, another for the main camera controls, and one for the game manager GameObject. You can also organize it by "Gameplay" or "UI", or anything that makes the most sense for your game.
Next, you can add an Action to your action map. We'll go over the same Jump action example from the previous section. Name your new action "Jump", and then add a key binding to it. To do this, expand the arrow next to your action to see "" below. Click on that, and you will see the menu on the right side change, and it will have a Path option that is set to blank. Click that, and either start typing the name of the key or button, or you can have it listen for a key/button press and it will capture the next one you use. We'll use the Space
key for our Jump Action, so navigate to that however you prefer. You'll also see a couple sections below the Path option; interactions are where you would control whether the Action is fired only when the button is pressed for a certain duration, or if it is tapped multiple times, and processors can help you control how this key press in interpreted; we won't be using them here though.
Next we write out the logic for what happens when the key or button that's bound to our Action is pressed. There are a few ways to connect your Action to a method within a script. One way is to attach a PlayerInput component to your GameObject that should be listening for input. Let's look at the code for this setup:
public class Player
{
InputActionAsset playerInput;
Rigidbody rb;
float jumpForce = 5f;
void Start()
{
rb = GetComponent<Rigidbody>();
playerInput = GetComponent<PlayerInput>().actions;
playerInput.Enable();
}
public void OnJump()
{
rb.AddForce(Vector3.up * jumpForce, ForceMode.Impulse);
}
}
Looks pretty similar! The difference is that we must reference our PlayerInput
component, enable it, and then follow Unity's naming convention by adding the word "On" before the name of our Action. This will automatically connect our Jump Action to the OnJump method in the script.
Looking at the difference between Unity's old and new input systems, you can see how the new system is different in that it encapsulates an Action as an object. This object is bound to a key or button press, and this is all separate from the logic for what happens when the action is performed. This decoupling of the command issuer from the command executor is what allows for the benefits the command pattern brings in cross-platform and multiplayer games.
Top comments (0)