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. By Najmm Shora.

Leave a rating/review
Download materials
Save for later
Share
You are currently viewing page 2 of 3 of this article. Click here to view the first page.

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.
Najmm Shora

Contributors

Najmm Shora

Author

Aleksandra Kizevska

Illustrator

Sean Duffy

Final Pass Editor

Over 300 content creators. Join our team.