Unity Tutorials Beta

Learn how to create games in Unity, a powerful and popular game engine.

Implementing The Command Pattern In Unity

How to achieve replay functionality, as well as undo and redo in Unity by using the command pattern. Use it to enhance strategy and similar games.

Version

  • Unity 2019.1, Unity

Have you ever wondered how games like Super Meat Boy and others achieve their replay functionality? One of the ways to do it is by executing the inputs exactly as the player issued them, which, in turn, means that the input needs to be stored somehow. The Command pattern can be used to do that and more.

The Command pattern is also useful if you want to create Undo and Redo functionalities in a strategy game.

In this tutorial, you will implement the Command pattern using C# and use it to traverse a bot character through a 3D maze. During this process, you’ll learn:

  • The fundmentals of the Command pattern.
  • How to implement the Command pattern
  • Queuing input commands and postponing their execution.
  • Undoing and redoing the issued commands before their execution.
Note: This tutorial assumes that you are already familiar with Unity and have intermediate C# knowledge. This tutorial uses Unity 2019.1 and C# 7.

Getting Started

To set things in motion, download the project materials using the link at the top or bottom of this tutorial. Unzip the file and open the Starter project inside Unity.

Go to RW/Scenes and open the Main scene. You will notice that the scene contains a bot inside a maze, and there is a terminal UI that displays instructions. The floor’s design is like as a grid, which will be useful visually when you make the bot move across the maze.

command pattern: game view

If you click Play, the instructions won’t seem to be working. That’s OK because you will add that functionality in this tutorial.

The most interesting part of this scene is the Bot GameObject. Select it from the Hierarchy by clicking on it.

Take a look in the Inspector, and you will see that it has a Bot component attached to it. You will use this component while issuing input commands.

the Bot component in the inspector

Understanding the Bot logic

Navigate to RW/Scripts and open the Bot script in your code editor. You don’t need to know about what is happening in the Bot script. But take note of the two methods named Move and Shoot. Again, you don’t need to worry about what happens inside these methods but you need to understand how to use them.

Notice that the Move method accepts an input parameter of type CardinalDirection. CardinalDirection is an enumeration. An enumerator of type CardinalDirection can be either Up, Down, Right or Left. Based on the chosen CardinalDirection, the bot will move by exactly one square across the grid in the corresponding direction.

The Shoot method makes the bot shoot a projectile that can destroy the yellow walls but is useless against other walls.

Finally, take a look at the ResetToLastCheckpoint method; to understand what it does, take a look at the maze. In the maze, there are points referred to as the checkpoints. To solve the maze, the bot should reach the green checkpoint.

When the bot crosses a new checkpoint, it becomes the last checkpoint for the bot. ResetToLastCheckpoint resets the bot’s location to the last checkpoint.

You can’t use these methods yet, but you will soon. First, you will learn about the Command design pattern.

Understanding the Command Design Pattern

The Command pattern is one of the 23 design patterns described in the book Design Patterns: Elements of Reusable Object-Oriented Software by the — Erich Gamma, Richard Helm, Ralph Johnson and John Vlissides — or the GoF (Gang of Four).

The authors state that the “Command pattern encapsulates a request as an object, thereby letting us parameterize other objects with different requests, queue or log requests, and support undoable operations.”

Woah there! What??

I know, this definition isn’t exactly “user friendly,” but let’s break it down.

Encapsulation refers to the fact that a method call can be encapsulated as an object.

The encapsulated method can act on multiple objects based on the input parameter. This is what parameterizing other objects means.

The resulting “command” can then be stored alongside other commands before their execution. This refers to the queuing of requests.

Command Queue

Finally, “undoable” here doesn’t mean something that is impossible to achieve, but rather it refers to the operations that can be reversed by an Undo functionality.

Interesting…

OK, but what does this mean in code?

Simply put, a Command class will have an Execute method which can accept an object (on which the command acts) called the Receiver as an input parameter. So, essentially, the Execute method is encapsulated by the Command class.

The multiple instances of a Command class can be passed around as regular objects, which means that they can be stored in a data structure, such as a queue, stack, etc.

Finally, to execute any command, its Execute method will need to be called. The class that triggers the execution is called the Invoker.

All Clear!

Right now, the project contains an empty class called BotCommand. In the next section you will tackle the requirements to implement the above, enabling the Bot to execute actions using the Command pattern. :]

Moving the Bot

Implementing the Command Pattern

In this section, you will implement the Command pattern. There are multiple ways to implement this pattern. This tutorial will teach you one of such ways.

First, go to RW/Scripts and open the BotCommand script in your editor. The BotCommand class should be empty but not for long.

Paste the following code inside the class:

    //1
    private readonly string commandName;

    //2
    public BotCommand(ExecuteCallback executeMethod, string name)
    {
        Execute = executeMethod;
        commandName = name;
    }

    //3
    public delegate void ExecuteCallback(Bot bot);

    //4
    public ExecuteCallback Execute { get; private set; }

    //5
    public override string ToString()
    {
        return commandName;
    }

So what is happening, here?

  1. The commandName variable is simply used to store a human, readable name of a command. It is not essential to this pattern, but you will need it later in the tutorial.
  2. The BotCommand constructor accepts a function and a string. This will help you to setup a Command object’s Execute method and its name.
  3. The ExecuteCallback delegate defines the type of the encapsulated method. The encapsulated method will return void and accept an object of type Bot (the Bot component) as an input parameter.
  4. The Execute property will reference the encapsulated method. You will use this to call the encapsulated method.
  5. The ToString method is overridden to return the commandName string. Useful for convenience and use in the UI for example.

Save your changes and — congratulations! You have successfully implemented the Command pattern.

All that’s left is to use it.

Creating the Commands

Open BotInputHandler from RW/Scripts.

Here, you will create five instances of BotCommand. These instances will respectively encapsulate the methods to move the Bot GameObject up, down, left and right, and also to make the bot shoot.

To do so, paste the following inside this class:

    //1
    private static readonly BotCommand MoveUp =
        new BotCommand(delegate (Bot bot) { bot.Move(CardinalDirection.Up); }, "moveUp");
    
    //2
    private static readonly BotCommand MoveDown =
        new BotCommand(delegate (Bot bot) { bot.Move(CardinalDirection.Down); }, "moveDown");

    //3
    private static readonly BotCommand MoveLeft =
        new BotCommand(delegate (Bot bot) { bot.Move(CardinalDirection.Left); }, "moveLeft");

    //4
    private static readonly BotCommand MoveRight =
        new BotCommand(delegate (Bot bot) { bot.Move(CardinalDirection.Right); }, "moveRight");

    //5
    private static readonly BotCommand Shoot =
        new BotCommand(delegate (Bot bot) { bot.Shoot(); }, "shoot");

In each of these instances, an anonymous method is passed to the constructor. This anonymous method will be encapsulated inside its corresponding command object. As you can see, the signature of each of the anonymous methods matches the requirements set by the ExecuteCallback delegate.

In addition, the second parameter to the constructor is a string representing the name given to represent the command. This name will be returned by the ToString method of the command instance. This will be used later for the UI.

In the first four instances, the anonymous methods call the Move method on the bot object. The input parameter varies, however.

For the MoveUp, MoveDown, MoveLeft and MoveRight commands, the parameter passed to Move is respectively CardinalDirection.Up, CardinalDirection.Down, CardinalDirection.Left and CardinalDirection.Right. These correspond to different directions of movement for the Bot GameObject as discussed previously in the Understanding the Command Design Pattern section.

Finally, in the fifth instance, the anonymous method calls the Shoot method on the bot object. This will make the bot shoot a projectile on the execution of this command.

Now that you have created the commands, they need to be accessed somehow when the user issues an input.

To do this, paste the following code inside the BotInputHandler, just below the the command instances:

    public static BotCommand HandleInput()
    {
        if (Input.GetKeyDown(KeyCode.W))
        {
            return MoveUp;
        }
        else if (Input.GetKeyDown(KeyCode.S))
        {
            return MoveDown;
        }
        else if (Input.GetKeyDown(KeyCode.D))
        {
            return MoveRight;
        }
        else if (Input.GetKeyDown(KeyCode.A))
        {
            return MoveLeft;
        }
        else if (Input.GetKeyDown(KeyCode.F))
        {
            return Shoot;
        }

        return null;
    }

The HandleInput method simply returns a single command instance based on the key pressed by the user. Save your changes before you continue.

Using the Commands

Alright, now it is time to use the commands you created. Go to RW/Scripts again and open SceneManager script in your editor. In this class, you will notice that there is a reference to a uiManager variable of type UIManager.

The UIManager class provides some helpful utility methods for the terminal UI that is being used in the scene. If a method from UIManager gets used, this tutorial will explain what it does, but for the purposes of this tutorial you don’t need to know its inner workings.

Furthermore, the bot variable references the bot component attached to the Bot GameObject.

Now, add the following code to the SceneManager class, replacing the existing code comment //1:

    //1
    private List<BotCommand> botCommands = new List<BotCommand>();
    private Coroutine executeRoutine;
    
    //2
    private void Update()
    {
        if (Input.GetKeyDown(KeyCode.Return))
        {
            ExecuteCommands();
        }
        else
        {
            CheckForBotCommands();
        }           
    }

    //3
    private void CheckForBotCommands()
    {
        var botCommand = BotInputHandler.HandleInput();
        if (botCommand != null && executeRoutine == null)
        {
            AddToCommands(botCommand);
        }
    }

    //4
    private void AddToCommands(BotCommand botCommand)
    {
        botCommands.Add(botCommand);
        //5
        uiManager.InsertNewText(botCommand.ToString());
    }

    //6
    private void ExecuteCommands()
    {
        if (executeRoutine != null)
        {
            return;
        }

        executeRoutine = StartCoroutine(ExecuteCommandsRoutine());
    }

    private IEnumerator ExecuteCommandsRoutine()
    {
        Debug.Log("Executing...");
        //7
        uiManager.ResetScrollToTop();

        //8
        for (int i = 0, count = botCommands.Count; i < count; i++)
        {
            var command = botCommands[i];
            command.Execute(bot);
            //9
            uiManager.RemoveFirstTextLine();
            yield return new WaitForSeconds(CommandPauseTime);
        }

        //10
        botCommands.Clear();
        
        bot.ResetToLastCheckpoint();

        executeRoutine = null;
    }

That's a lot of code! But don't worry; you are finally ready for the first proper run of the project in the Game view.
You will examine this code afterward. Save your changes before you continue.

Running the Game to Test the Command Pattern

Alright, it's time to build everything and press Play in the Unity editor.

You should be able to enter direction commands using the WASD keys. To enter the shoot command, use the F key. Finally, to execute, press the Return key.

Note: You cannot enter more commands until the execution process is over..

Notice how the lines are being added to the terminal UI. The commands are being represented by their names in the UI. This was possible due to the commandName variable.

Also, notice how the UI scrolls to the top before execution and how the lines are being removed upon execution.

A Closer Look at the Commands

Now, it's time to examine the code you added in the "Using the Commands" section:

  1. The botCommands list stores references to the BotCommand instances. Remember, as far as memory is concerned you only created five command instances, but there can be multiple references to the same command. Moreover, the executeCoroutine variable references the ExecuteCommandsRoutine which handles the command execution.
  2. Update checks if user has pressed the Return key, in which case it calls ExecuteCommands, otherwise CheckForBotCommands gets called.
  3. CheckForBotCommands uses the static method HandleInput from BotInputHandler to check if the user has issued an input, in which case a command is returned. The returned command gets passed to AddToCommands. However, if the commands are being executed i.e. if executeRoutine is not null, it will return without passing anything to AddToCommands. As such the user has to wait until the execution finishes.
  4. AddToCommands adds a new reference to the returned command instance, to botCommands.
  5. InsertNewText method of the UIManager class adds a new line of text to the terminal UI. The line is the string passed to it as the input parameter. In this case, you pass commandName to it.
  6. The method ExecuteCommands starts ExecuteCommandsRoutine.
  7. ResetScrollToTop from UIManager scrolls the terminal UI to the top. This is done just before the execution starts.
  8. ExecuteCommandsRoutine has a for loop, which iterates over the commands inside the botCommands list and executes them one-by-one by passing the bot object to the method returned by the Execute property. A pause of CommandPauseTime seconds is added after each execution.
  9. The method RemoveFirstTextLine from UIManager removes the very first line of text in the terminal UI, if it exists. As such, after a command gets executed its name gets removed from the UI.
  10. After the execution of all the commands, botCommands gets cleared and the bot gets reset to the last checkpoint it crossed using ResetToLastCheckpoint. Finally, executeRoutine is set to null and the user can continue issuing more inputs.

Implementing Undo and Redo Functionalities

Run the scene one more time and try to reach the green checkpoint.

You might notice that there is no way for you right now to undo a command that you entered, which means that if you made a mistake you cannot go back unless you execute all the commands. You can fix that by adding an Undo and consequently a Redo functionality.

Go back to SceneManager.cs and add the following variable declaration right after the List declaration for botCommands:

       private Stack<BotCommand> undoStack = new Stack<BotCommand>();

The undoStack variable is a Stack (from the Collections family) that will store the references to the commands that have been undone.

Now, you will add two methods UndoCommandEntry and RedoCommandEntry for the purposes of Undo and Redo respectively. In the SceneManager class, paste the following code after ExecuteCommandsRoutine:

    private void UndoCommandEntry()
    {
        //1
        if (executeRoutine != null || botCommands.Count == 0)
        {
            return;
        }

        undoStack.Push(botCommands[botCommands.Count - 1]);
        botCommands.RemoveAt(botCommands.Count - 1);
            
        //2
        uiManager.RemoveLastTextLine();
     }

    private void RedoCommandEntry()
    {
        //3
        if (undoStack.Count == 0)
        {
            return;
        }

        var botCommand = undoStack.Pop();
        AddToCommands(botCommand);
    }

Going through this code:

  1. If the commands are being executed or if the botCommands list is empty, the UndoCommandEntry method won't do anything. Otherwise, it will push the reference to the very last entered command, onto the undoStack. This also removes the command reference from the botCommands list.
  2. RemoveLastTextLine method from UIManager removes the last line of text from the terminal UI so that the UI is consistent with the contents of botCommands when an undo occurs.
  3. RedoCommandEntry does nothing if undoStack is empty. Otherwise, it pops off the last command on top of undoStack and adds it back to the botCommands list via AddToCommands.

Now, you will add keyboard inputs to use these methods. Inside the SceneManager class, replace the body of the Update method with the following:

    if (Input.GetKeyDown(KeyCode.Return))
    {
        ExecuteCommands();
    }
    else if (Input.GetKeyDown(KeyCode.U)) //1
    {
        UndoCommandEntry();
    }
    else if (Input.GetKeyDown(KeyCode.R)) //2
    {
        RedoCommandEntry();
    }
    else
    {
        CheckForBotCommands();
    }
  1. Pressing the key U calls the UndoCommandEntry method.
  2. Pressing the key R calls the RedoCommandEntry method.

Handling Edge Cases

Great — you are almost done! But before finishing, you should make sure of two things:

  1. If you enter a new command, the undoStack should get cleared.
  2. Before executing the commands, the undoStack should get cleared.

To do this, first you will add a new method to SceneManager. Paste the following method after CheckForBotCommands:

    private void AddNewCommand(BotCommand botCommand)
    {
        undoStack.Clear();
        AddToCommands(botCommand);
    }

This method clears the undoStack and then calls the AddToCommands method.

Now, replace the call to AddToCommands inside CheckForBotCommands with the following:

    AddNewCommand(botCommand);

Finally, paste the following line after the if statement inside the ExecuteCommands method, to clear the undoStack before execution:

    undoStack.Clear();

And you are done! For real this time!

Phew!

Save your work. Build and click Play in the editor. Type commands as before. Press U to undo the commands. Press R to redo the undoed commands.

Try to reach the green checkpoint.

Where to Go From Here?

You can download the project materials using the Download Materials button at the top or bottom of this tutorial.

To know more about the design patterns involved in game programming, I strongly recommend checking out Game Programming Patterns by Robert Nystrom.

To learn more about advanced C# techniques, check out the course C# Collections, Lambdas, and LINQ on our website.

Challenge

As a challenge, see if you can reach the green checkpoint at the end of the maze. I have provided the solution below in case you are stuck. It is just one of the many solutions you can arrive at.

[spoiler title="Maze Solution"]

  • moveUp × 2
  • moveRight × 3
  • moveUp × 2
  • moveLeft
  • shoot
  • moveLeft × 2
  • moveUp × 2
  • moveLeft × 2
  • moveDown × 5
  • moveLeft
  • shoot
  • moveLeft
  • moveUp × 3
  • shoot × 2
  • moveUp × 5
  • moveRight × 3

command pattern victory screen

[/spoiler]

That's it! Thanks for reading. I hope you enjoyed the tutorial, and if you have any questions or comments, please join the forum discussion below!

Special thanks to the artists Lee Barkovich, Jesús Lastra and sunburn for some of the assets used in the project.

Add a rating for this content

Contributors

Comments