Implementing a Command Design Pattern in Unity

Share the Post

In this tutorial, we will learn about Command Design Pattern and then implement a Command Design Pattern in Unity to solve the movement of a game object.

Read also: What Are C# Delegates And How To Use Them

Read Also: Solving 8 puzzle problem using A* star search

Download the 8 Puzzle Unlimited App from Google Play.


Introducing the Command Design Pattern

Requests, Orders and Commands: We are all familiar with them in real life; one person sends a request (or an order or a command) to another person to perform (or do not perform) some tasks that are assigned to them.  In software development and programming, this thing works in a similar way: one component’s request is transmitted to another to execute specific tasks in the Command pattern.

Definition: A command pattern is a behavioural design pattern in which a request is transformed into an object that encapsulates (contains) all information needed to perform an action or trigger an event at a later time. This transformation into objects lets you parameterize methods with different requests, delay a request and/or queue the request’s execution.

Good and robust software design should be based on the principle of separation of concerns. This can usually be achieved by breaking up the application into multiple layers (or software components). A commonly recurring example is dividing an application into a graphical user interface that handles only the graphics part and another into logic handler for the business logic part.

The GUI layer is responsible for rendering a beautiful picture on the screen and capturing any inputs, where the logic handler could be to perform actual calculations for a specific task. The GUI layer thus delegates the work to the underlying layer of business logic.

Read Implementing Player Controls With Finite State Machine Using C# in Unity

Structure of a Command Design Pattern

The structure of the command design pattern is represented below by a UML class diagram. The various classes are explained in detail below.

Class diagram for Command Design Pattern

To implement a command pattern we will need the abstract Command, concrete Commands, an Invoker, a Client and the Receiver classes. 

Command

The Command is usually designed as an interface with just one or two methods for executing and undoing the command operation. All concrete commands classes must be derived from this Interface and should implement the actual Execute and if necessary then the Undo implementation.

public interface ICommand      
{ 
    void Execute();
    void ExecuteUndo();       
}

Invoker

The Invoker (also known as the Sender) class is responsible for initiating requests. This is the class that triggers the necessary command. This class must have a variable that stores the reference to a command object or a container of commands object. The invoker triggers a command instead of sending the request directly to the receiver. Note that the invoker isn’t responsible for creating the command object. Usually, it gets a pre-created command from the client via the constructor.

Client

The Client creates and configures concrete command objects. The client must pass all of the request parameters, including a receiver instance, into the command’s constructor. After that, the resulting command may be associated with one or multiple invokers. A client can be any class that creates different command objects.

Receiver (Optional)

The Receiver class is the class that receives the command and contains mostly all of the business logic. Almost any object may act as a receiver. Most commands only handle the details of how a request is passed to the receiver, while the receiver itself does the actual work.

Concrete Commands

Concrete Commands are derived from the Command interface and they implement various kinds of requests. A concrete command isn’t supposed to perform the work on its own, but rather to pass the call to one of the business logic objects or receivers (as described above). However, for the sake of simplifying the code, these classes can be merged.

Parameters required to execute a method on a receiving object can be declared as fields in the concrete command. You can make command objects immutable by only allowing the initialization of these fields via the constructor.

See also: https://refactoring.guru/

Implementing a Command Design Pattern in Unity

We will implement a Command Design pattern in Unity to solve the movement of a game object by applying different types of movements. Each of these movements will be implemented as a command. We will also implement an undo function to be able to undo the operations in reverse order.

So let’s start!

Create a new 3D Unity Project

We will start by creating a 3D Unity project. Name it as CommandDesignPattern.

Create the Ground

For our tutorial, we will create a simple Plane object which will form our ground. Right-click on the Hierarchy window and create a new Plane game object. Rename it to Ground and resize it to 20 units in x and 20 units in the z-axis. You can apply a colour or texture to the ground to make it look more visually appealing.

Create the Player

We will now create a Player game object. For our tutorial, we will use a Capsule to represent the player game object. So go ahead, right-click on the Hierarchy window and create a new Capsule game object. Rename it to Player.

Create the GameManager.cs Script

Select the Ground game object and add a new script component. Name the script GameManager.cs.

We shall now implement the movement of the Player object.

For this, we add a public GameObject variable called _player.

public GameObject mPlayer;

Now drag and drop the Player game object from the Hierarchy to the Player field in the inspector window.

Implement Movement for Player

We will use the Up, Down, Left and Right arrow keys for our movement of the Player.

For a start, we will implement this movement the usual way we are used to do. Let’s go ahead and implement in the Update method.  For simplicity, we will implement the discrete movement of 1 unit for each keystroke in the correct directions.

void Update()
{
    Vector3 dir = Vector3.zero;

    if (Input.GetKeyDown(KeyCode.UpArrow))
        dir.z = 1.0f;
    else if (Input.GetKeyDown(KeyCode.DownArrow))
        dir.z = -1.0f;
    else if (Input.GetKeyDown(KeyCode.LeftArrow))
        dir.x = -1.0f;
    else if (Input.GetKeyDown(KeyCode.RightArrow))
        dir.x = 1.0f;

    if (dir != Vector3.zero)
    {
        _player.transform.position += dir;
    }
}

Click the Play button and try out the game. Press the Up, Down, Left and Right arrow keys to see the movement of the Player.

Implement Right Click to Moveto Location

We will now implement a right-click on the Ground for the Player to move to the clicked location. How do we do that?

First of all, we will need the position of the clicked point on the Ground.

public Vector3? GetClickPosition()
{
    if(Input.GetMouseButtonDown(1))
    {
        RaycastHit hitInfo;
        Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
        if(Physics.Raycast(ray, out hitInfo))
        {
            //Debug.Log("Tag = " + hitInfo.collider.gameObject.tag);
            return hitInfo.point;
        }
    }
    return null;
}

What is the return type Vector3?

Use of the ? operator for return types in C#, for example.

public int? myProperty { get; set; }

It means that the value type in question is a nullable type

Nullable types are instances of the System.Nullable struct. A nullable type can represent the correct range of values for its underlying value type, plus an additional null value. For example, a Nullable<Int32>, pronounced “Nullable of Int32,” can be assigned any value from -2147483648 to 2147483647, or it can be assigned the null value. A Nullable<bool> can be assigned the values true, false, or null. The ability to assign null to numeric and Boolean types is especially useful when you are dealing with databases and other data types that contain elements that may not be assigned a value. For example, a Boolean field in a database can store the values true or false, or it may be undefined.

Now that we have the clicked position we will need to implement the MoveTo function. Our MoveTo function should move the player smoothly. We will implement this as a Coroutine with a linear interpolation of the displacement vector.

public IEnumerator MoveToInSeconds(GameObject objectToMove, Vector3 end, float seconds)
{
    float elapsedTime = 0;
    Vector3 startingPos = objectToMove.transform.position;
    end.y = startingPos.y;
    while (elapsedTime < seconds)
    {
        objectToMove.transform.position = Vector3.Lerp(startingPos, end, (elapsedTime / seconds));
        elapsedTime += Time.deltaTime;
        yield return null;
    }
    objectToMove.transform.position = end;
}

Now, all we need is to call the coroutine whenever the right mouse button is clicked.

Amend the Update method by adding the following lines of code.

****
    var clickPoint = GetClickPosition();
    if (clickPoint != null)
    {
        IEnumerator moveto = MoveToInSeconds(_player, clickPoint.Value, 0.5f);
        StartCoroutine(moveto);
    }
****

Click the Play button and try out the game. Press the Up, Down, Left, Right arrow keys and right-click on the ground to see the movement of the Player.

Implementing Undo Operation

How do we implement the Undo operation? Where is Undo of movement necessary? Ponder for 5 minutes.

Implement the Command Design Pattern in Unity

We are now going to implement the Undo method for each of the movement operations that we can do using both the keypresses as well as by right-clicking.

The easiest way to implement the Undo operation is to use the Command Design Pattern by implementing a Command Design Pattern in Unity.

For our command pattern, we will convert all the movement types to commands. Let’s start by creating the Command Interface.

Command Interface

public interface ICommand
{
    void Execute();
    void ExecuteUndo();
}

Our Command interface has two methods. The first one is the normal Execute method and the second one is the ExecuteUndo method that does the Undo operation. For each concrete commands, we will need to implement these two methods (besides other methods if necessary).

Now let’s convert our basic movement into a concrete command.

CommandMove

public class CommandMove : ICommand
{
    public CommandMove(GameObject obj, Vector3 direction)
    {
        mGameObject = obj;
        mDirection = direction;
    }

    public void Execute()
    {
        mGameObject.transform.position += mDirection;
    }

    public void ExecuteUndo()
    {
        mGameObject.transform.position -= mDirection;
    }

    GameObject mGameObject;
    Vector3 mDirection;
}

CommandMoveTo

public class CommandMoveTo : ICommand
{
    public CommandMoveTo(GameManager manager, Vector3 startPos, Vector3 destPos)
    {
        mGameManager = manager;
        mDestination = destPos;
        mStartPosition = startPos;
    }

    public void Execute()
    {
        mGameManager.MoveTo(mDestination);
    }

    public void ExecuteUndo()
    {
        mGameManager.MoveTo(mStartPosition);
    }

    GameManager mGameManager;
    Vector3 mDestination;
    Vector3 mStartPosition;
}

See how the ExecuteUndo method is implemented. It is actually just doing the reverse of what the Execute method is doing.

Invoker Class

We will now implement the Invoker class. Remember that the Invoker class is the class that is responsible for keeping the commands. Also, remember that for Undo to work we will need to implement a Last In First Out (LIFO) kind of data structure.

What is LIFO? How can we implement a LIFO? Introducing the Stack data structure.

C# includes a special type of collection which stores elements in LIFO style (Last In First Out). This collection includes a generic and non-generic Stack. It provides a Push() method to add value to the top (last), Pop() method to remove the top (or last) value, and Peek() method to retrieve the top value.

We will now implement the Invoker class which will hold a Stack of commands.

public class Invoker
{
    public Invoker()
    {
        mCommands = new Stack<ICommand>();
    }

    public void Execute(ICommand command)
    {
        if (command != null)
        {
            mCommands.Push(command);
            mCommands.Peek().Execute();
        }
    }

    public void Undo()
    {
        if(mCommands.Count > 0)
        {
            mCommands.Peek().ExecuteUndo();
            mCommands.Pop();
        }
    }

    Stack<ICommand> mCommands;
}

Notice how the Execute and Undo methods are implemented by the Invoker of the Commands. When calling the Execute method, the Invoker is pushing the command into the stack by calling Push method of the Stack and then executing the command’s Execute method. The command at the top is retrieved by the Peek method of the Stack. Similarly, while calling

Undo the Invoker is calling the ExecuteUndo method of the command by retrieving the top command from the Stack (using the Peek method). After that, the Invoker is releasing the top command by calling the Pop method of the Stack.

We are all set to make use of our Invoker and the commands. For this, we will first add a new variable to our GameManager class for the Invoker object.

private Invoker mInvoker;

Then we will instantiate the mInvoker object in Start method of GameManager script.

mInvoker = new Invoker();

Undo

We will now call the Undo by pressing the U key. Add the following code in the Update method before the closing bracket.

    // Undo 
    if (Input.GetKeyDown(KeyCode.U))
    {
        mInvoker.Undo();
    }

Using Commands

We will now modify the Update method to use the new Command pattern.

void Update()
{
    Vector3 dir = Vector3.zero;

    if (Input.GetKeyDown(KeyCode.UpArrow))
        dir.z = 1.0f;
    else if (Input.GetKeyDown(KeyCode.DownArrow))
        dir.z = -1.0f;
    else if (Input.GetKeyDown(KeyCode.LeftArrow))
        dir.x = -1.0f;
    else if (Input.GetKeyDown(KeyCode.RightArrow))
        dir.x = 1.0f;

    if (dir != Vector3.zero)
    {
        //Using command pattern implementation.
        ICommand move = new CommandMove(mPlayer, dir);
        mInvoker.Execute(move);
    }

    var clickPoint = GetClickPosition();

    //Using command pattern right click moveto.
    if (clickPoint != null)
    {
        CommandMoveTo moveto = new CommandMoveTo(
            this, 
            mPlayer.transform.position, 
            clickPoint.Value);
        mInvoker.Execute(moveto);
    }
    // Undo 
    if (Input.GetKeyDown(KeyCode.U))
    {
        mInvoker.Undo();
    }
}

Click the Play button and try out the game. Press the Up, Down, Left, Right arrow keys to see the movement of the Player and the “u” key to Undo in reverse order. 

Conclusion

The Command design pattern is one of the twenty-three well-known GoF design patterns that describe how to solve recurring design problems to design flexible and reusable object-oriented software, that is, objects that are easier to implement, change, test, reuse and maintain.

Complete Script for Unity

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

public class GameManager : MonoBehaviour
{
    public interface ICommand
    {
        void Execute();
        void ExecuteUndo();
    }

    public class CommandMove : ICommand
    {
        public CommandMove(GameObject obj, Vector3 direction)
        {
            mGameObject = obj;
            mDirection = direction;
        }

        public void Execute()
        {
            mGameObject.transform.position += mDirection;
        }

        public void ExecuteUndo()
        {
            mGameObject.transform.position -= mDirection;
        }

        GameObject mGameObject;
        Vector3 mDirection;
    }

    public class Invoker
    {
        public Invoker()
        {
            mCommands = new Stack<ICommand>();
        }

        public void Execute(ICommand command)
        {
            if (command != null)
            {
                mCommands.Push(command);
                mCommands.Peek().Execute();
            }
        }

        public void Undo()
        {
            if (mCommands.Count > 0)
            {
                mCommands.Peek().ExecuteUndo();
                mCommands.Pop();
            }
        }

        Stack<ICommand> mCommands;
    }
    public GameObject mPlayer;
    private Invoker mInvoker;

    public class CommandMoveTo : ICommand
    {
        public CommandMoveTo(GameManager manager, Vector3 startPos, Vector3 destPos)
        {
            mGameManager = manager;
            mDestination = destPos;
            mStartPosition = startPos;
        }

        public void Execute()
        {
            mGameManager.MoveTo(mDestination);
        }

        public void ExecuteUndo()
        {
            mGameManager.MoveTo(mStartPosition);
        }

        GameManager mGameManager;
        Vector3 mDestination;
        Vector3 mStartPosition;
    }

    public IEnumerator MoveToInSeconds(GameObject objectToMove, Vector3 end, float seconds)
    {
        float elapsedTime = 0;
        Vector3 startingPos = objectToMove.transform.position;
        end.y = startingPos.y;
        while (elapsedTime < seconds)
        {
            objectToMove.transform.position = Vector3.Lerp(startingPos, end, (elapsedTime / seconds));
            elapsedTime += Time.deltaTime;
            yield return null;
        }
        objectToMove.transform.position = end;
    }

    public Vector3? GetClickPosition()
    {
        if (Input.GetMouseButtonDown(1))
        {
            RaycastHit hitInfo;
            Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
            if (Physics.Raycast(ray, out hitInfo))
            {
                //Debug.Log("Tag = " + hitInfo.collider.gameObject.tag);
                return hitInfo.point;
            }
        }
        return null;
    }



    // Start is called before the first frame update
    void Start()
    {
        mInvoker = new Invoker();
    }

    // Update is called once per frame
    void Update()
    {
        Vector3 dir = Vector3.zero;

        if (Input.GetKeyDown(KeyCode.UpArrow))
            dir.z = 1.0f;
        else if (Input.GetKeyDown(KeyCode.DownArrow))
            dir.z = -1.0f;
        else if (Input.GetKeyDown(KeyCode.LeftArrow))
            dir.x = -1.0f;
        else if (Input.GetKeyDown(KeyCode.RightArrow))
            dir.x = 1.0f;

        if (dir != Vector3.zero)
        {
            //----------------------------------------------------//
            //Using normal implementation.
            //mPlayer.transform.position += dir;
            //----------------------------------------------------//


            //----------------------------------------------------//
            //Using command pattern implementation.
            ICommand move = new CommandMove(mPlayer, dir);
            mInvoker.Execute(move);
            //----------------------------------------------------//
        }

        var clickPoint = GetClickPosition();

        //----------------------------------------------------//
        //Using normal implementation for right click moveto.
        //if (clickPoint != null)
        //{
        //    IEnumerator moveto = MoveToInSeconds(mPlayer, clickPoint.Value, 0.5f);
        //    StartCoroutine(moveto);
        //}
        //----------------------------------------------------//

        //----------------------------------------------------//
        //Using command pattern right click moveto.
        if (clickPoint != null)
        {
            CommandMoveTo moveto = new CommandMoveTo(this, mPlayer.transform.position, clickPoint.Value);
            mInvoker.Execute(moveto);
        }
        //----------------------------------------------------//


        //----------------------------------------------------//
        // Undo 
        if (Input.GetKeyDown(KeyCode.U))
        {
            mInvoker.Undo();
        }
        //----------------------------------------------------//
    }

    public void MoveTo(Vector3 pt)
    {
        IEnumerator moveto = MoveToInSeconds(mPlayer, pt, 0.5f);
        StartCoroutine(moveto);
    }
}

References

Wikidepia Design Patterns

Wikipedia Command Design Pattern

Refactoring Guru

Game Programming Patterns

Design Patterns in Game Programming

Leave a Reply

Your email address will not be published. Required fields are marked *