Implementing Command Design Pattern in Unity

Let’s get back to the design patterns for Unity!
Today let’s introduce Command Design Pattern! This design pattern is famous for its encapsulation of requests, which can be useful for many applications and often is used for handling user input, but it’s not the only use case.

Command Design Pattern

The idea behind this design pattern is to move requests to the objects which could be collected and be executed in the queue. You can think about them like Actions or Events but represented as objects.

Implementation

To implement it, we need 2 elements.
Command – class which stores the logic of the request.
CommandInvoker – another class which will execute collected commands when triggered or asked to do so.

Let’s start with the Command class.

using UnityEngine;

/// <summary>
/// Abstract class for commands.
/// </summary>
public abstract class Command
{
    /// <summary>
    /// Method called to execute command.
    /// </summary>
    public abstract void Execute();
}

Because this is an abstract class, we don’t need to implement logic within method yet.

Next part will be the implementation of the Invoker class.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

/// <summary>
/// Command invoker. Collects command into buffer to execute them at once.
/// </summary>
public class CommandInvoker : MonoBehaviour
{
    // Collected commands.
    private Queue<Command> commandBuffer = new Queue<Command>();

    /// <summary>
    /// Method used to add new command to the buffer.
    /// </summary>
    /// <param name="command">New command.</param>
    public void AddCommand(Command command)
    {
        commandBuffer.Enqueue(command);
    }

    /// <summary>
    /// Method used to execute all commands from the buffer.
    /// </summary>
    public void ExecuteBuffer()
    {
        foreach (var c in commandBuffer)
        {
            c.Execute();
        }

        commandBuffer.Clear();
    }
}

You can see that this class is not that big, and its only purpose is to collect and execute Commands at the right time. ?

Example

So, as this is out of the way, now it’s time to create an example of how to use it! We are going to build a little grid-based game where the player will have to define the path for the character to arrive at home.

To start, we need UI to collect and display commands from the player, and a grid to place our character and goal.

Because we will have a few different views, I’m going to use the state machine to make our code little cleaner. You can read about it more in my other post about it.

UI layout for collecting commands.
using UnityEngine;

/// <summary>
/// Prepare level state. Generates map and place player and destination on the map.
/// </summary>
public class PrepareLevelState : BaseState
{
    public override void PrepareState()
    {
        base.PrepareState();

        // Map generating
        owner.MapGenerator.GenerateMap();

        // Positioning player in the center of the map.
        owner.Player.CurrentPosition = new Vector2Int(owner.MapGenerator.MapSize.x / 2, owner.MapGenerator.MapSize.y / 2);

        // Placing destination on the map. Guard for placing the ​destination at the same position as the player.
        do
        {
            owner.Destination.CurrentPosition = new Vector2Int(Random.Range(0, owner.MapGenerator.MapSize.x), Random.Range(0, owner.MapGenerator.MapSize.y));
        } while (owner.Player.CurrentPosition == owner.Destination.CurrentPosition);

        // Continuing to the collecting commands.
        owner.ChangeState(new CommandCollectState());
    }
}
using UnityEngine;

/// <summary>
/// Game state in which we are collecting input commands from the UI.
/// </summary>
public class CommandCollectState : BaseState
{
    public override void PrepareState()
    {
        base.PrepareState();

        // Attaching events to the UI buttons.
        owner.UI.CollectCommandsView.OnExecuteClicked += OnExecuteClicked;

        owner.UI.CollectCommandsView.OnUpClicked += OnUpClicked;
        owner.UI.CollectCommandsView.OnDownClicked += OnDownClicked;
        owner.UI.CollectCommandsView.OnLeftClicked += OnLeftClicked;
        owner.UI.CollectCommandsView.OnRightClicked += OnRightClicked;

        owner.UI.CollectCommandsView.ShowView();
    }

    public override void DestroyState()
    {
        owner.UI.CollectCommandsView.HideView();

        // Detaching events from the UI buttons.
        owner.UI.CollectCommandsView.OnExecuteClicked -= OnExecuteClicked;

        owner.UI.CollectCommandsView.OnUpClicked -= OnUpClicked;
        owner.UI.CollectCommandsView.OnDownClicked -= OnDownClicked;
        owner.UI.CollectCommandsView.OnLeftClicked -= OnLeftClicked;
        owner.UI.CollectCommandsView.OnRightClicked -= OnRightClicked;

        base.DestroyState();
    }

    /// <summary>
    /// Method attached to the Execution button.
    /// Starts changes game state to the Execution State.
    /// </summary>
    private void OnExecuteClicked()
    {
        owner.ChangeState(new ExecutionState());
    }

    /// <summary>
    /// Method attached to the Up button.
    /// Adds move command to the buffer and updates command list in the UI.
    /// </summary>
    private void OnUpClicked()
    {
        owner.CommandInvoker.AddCommand(new MoveCommand() { direction = Vector2Int.up, player = owner.Player });
        owner.UI.CollectCommandsView.AddCommand("UP");
    }

    /// <summary>
    /// Method attached to the Down button.
    /// Adds move command to the buffer and updates command list in the UI.
    /// </summary>
    private void OnDownClicked()
    {
        owner.CommandInvoker.AddCommand(new MoveCommand() { direction = Vector2Int.down, player = owner.Player });
        owner.UI.CollectCommandsView.AddCommand("DOWN");
    }

    /// <summary>
    /// Method attached to the Left button.
    /// Adds move command to the buffer and updates command list in the UI.
    /// </summary>
    private void OnLeftClicked()
    {
        owner.CommandInvoker.AddCommand(new MoveCommand() { direction = Vector2Int.left, player = owner.Player });
        owner.UI.CollectCommandsView.AddCommand("LEFT");
    }

    /// <summary>
    /// Method attached to the Right button.
    /// Adds move command to the buffer and updates command list in the UI.
    /// </summary>
    private void OnRightClicked()
    {
        owner.CommandInvoker.AddCommand(new MoveCommand() { direction = Vector2Int.right, player = owner.Player });
        owner.UI.CollectCommandsView.AddCommand("RIGHT");
    }

}
using UnityEngine;
using UnityEngine.SceneManagement;

/// <summary>
/// Game Over state, which just display summary window.
/// </summary>
public class GameOverState : BaseState
{
    public bool won = false;

    public override void PrepareState()
    {
        base.PrepareState();

        owner.UI.ExecutionSummaryView.OnReplayClicked += OnReplayClicked;
        owner.UI.ExecutionSummaryView.ShowMessage(won);

        owner.UI.ExecutionSummaryView.ShowView();
    }

    public override void DestroyState()
    {
        owner.UI.ExecutionSummaryView.HideView();

        owner.UI.ExecutionSummaryView.OnReplayClicked -= OnReplayClicked;

        base.DestroyState();
    }

    /// <summary>
    /// Method attached to the Replay button.
    /// </summary>
    private void OnReplayClicked()
    {
        // Reload current scene.
        SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex);
    }
}

Next, I’m going to generate a map of a given size from the code. After creating a grid, we will place the character and the goal.

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

/// <summary>
/// This class generates and handles map.
/// </summary>
public class MapGenerator : MonoBehaviour
{
    [Header("Map")]
    // Reference to map tile.
    [SerializeField]
    private GameObject mapTilePrefab;

    // Map size.
    [SerializeField]
    private Vector2Int mapSize = new Vector2Int(5, 5);
    public Vector2Int MapSize => mapSize;

    // Distance between tiles.
    [SerializeField]
    private float tileOffset = 3.0f;

    // List of generated tiles.
    private List<GameObject> mapTiles = new List<GameObject>();

    /// <summary>
    /// Method called to generate map.
    /// </summary>
    public void GenerateMap()
    {
        for (int i = 0; i < mapSize.y; i++)
        {
            for (int j = 0; j < mapSize.x; j++)
            {
                var inst = Instantiate(mapTilePrefab, transform);
                inst.transform.localPosition = GetTilePosition(j, i);
                inst.transform.localRotation = Quaternion.identity;

                mapTiles.Add(inst);
            }
        }
    }

    /// <summary>
    /// Method which return position on tile base on X, Y value.
    /// </summary>
    /// <returns>The tile position.</returns>
    /// <param name="x">The x coordinate.</param>
    /// <param name="y">The y coordinate.</param>
    public Vector3 GetTilePosition(int x, int y)
    {
        return new Vector3(x - (mapSize.x / 2.0f), 0, y - (mapSize.y / 2.0f)) * tileOffset;
    }

    /// <summary>
    /// Method that returns map tile.
    /// </summary>
    /// <returns>The tile.</returns>
    /// <param name="x">The x coordinate.</param>
    /// <param name="y">The y coordinate.</param>
    public GameObject GetTile(int x, int y)
    {
        x = Mathf.Max(0, Mathf.Min(mapSize.x - 1, x));
        y = Mathf.Max(0, Mathf.Min(mapSize.y - 1, y));

        return mapTiles[y * mapSize.x + x];
    }

    /// <summary>
    /// Method which clamp Vector2Int value to the map size.
    /// </summary>
    /// <returns>The tiles.</returns>
    /// <param name="index">Index.</param>
    public Vector2Int ClampTiles(Vector2Int index)
    {
        index.x = Mathf.Max(0, Mathf.Min(mapSize.x - 1, index.x));
        index.y = Mathf.Max(0, Mathf.Min(mapSize.y - 1, index.y));

        return index;
    }
}

Great! With that done, it’s time to implement MoveCommand! This command, as the name suggests, will move our character on the grid.

using UnityEngine;

/// <summary>
/// Command that moves player in provided direction.
/// </summary>
public class MoveCommand : Command
{
    // Reference to the player movement.
    public PlayerMovement player;

    // Direction value.
    public Vector2Int direction;

    public override void Execute()
    {
        player.MovePlayer(direction);
    }
}

Because I want to see the execution of each command, I’m going to add a slight delay to our Invoker.

using UnityEngine;

/// <summary>
/// Command Invoker which has a delay in command execution.
/// </summary>
public class DelayedCommandInvoker : CommandInvoker
{
    [SerializeField]
    private float commandsPerSecond = 1;

    private float lastExecution;
    private bool executeCommands = false;

    /// <summary>
    /// Method used to execute all commands from the buffer.
    /// </summary>
    public override void ExecuteBuffer()
    {
        executeCommands = true;
    }

    /// <summary>
    /// Unity method called each frame.
    /// </summary>
    private void Update()
    {
        // Determing if can execute commands.
        if (executeCommands && commandBuffer.Count > 0)
        {
            // If can check if there enough time passed by to execute next command.
            var diff = Time.time - lastExecution;
            if (diff / commandsPerSecond >= 1)
            {
                // Executing next command.
                lastExecution = Time.time;
                var c = commandBuffer.Dequeue();
                c.Execute();

                // If there is no commands left, stop trying to execute them.
                executeCommands = commandBuffer.Count != 0;
            }
        }
    }
}

There is just one more thing left before we will test our game! We need to check if the player arrived at his destination!

using UnityEngine;

/// <summary>
/// Execution state in which all commands are executed.
/// </summary>
public class ExecutionState : BaseState
{
    public override void PrepareState()
    {
        base.PrepareState();

        owner.CommandInvoker.ExecuteBuffer();
    }

    public override void UpdateState()
    {
        base.UpdateState();

        // State work as long as there are commands in the buffer
        if (!owner.CommandInvoker.HasCommandsInBuffer())
        {
            var gameOver = new GameOverState();
            // check if player is at the same position as destination.
            gameOver.won = owner.Player.CurrentPosition == owner.Destination.CurrentPosition;

            owner.ChangeState(gameOver);
        }
    }
}

Now we have all the things that we needed. Now it’s time to play!

Command Design Pattern in action!

Congratulations!

You successfully implemented the Command Design Pattern in Unity!

There are so many possibilities to implement it, but I hope that with this example, you understand how to do it yourself!

The whole project is available in my public repository. ?

And as always, I hope to see you next time!

5 1 vote
Article Rating
Subscribe
Notify of
guest
0 Comments
Inline Feedbacks
View all comments
0
Would love your thoughts, please comment.x
()
x