قالب وردپرس درنا توس
Home / Apple / Implementation of the command pattern in unity

Implementation of the command pattern in unity



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

The command pattern is also useful for creating Undo and Repeat functionalities in a strategy game.

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

  • The Fundmentals of Command pattern.
  • How to Implement the Command Pattern
  • Queued in input commands and delay execution.
  • Undo and redo the issued commands before their execution.

Note : This guide assumes that you are already familiar with Unity and have intermediate C # knowledge. This tutorial uses Unit 201

9.1 and C # 7 .

Getting Started

To get things started, download the project material using the link at the top or bottom of this tutorial. Unzip the file and open the Starter project in Unity.

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

 command pattern: game view

Clicking Game does not seem to work. It's OK because you want to add that functionality to this tutorial.

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

Take a look at the inspector and you'll see that it has a Bot part attached to it. You will use this component while providing incoming commands.

 Bot component of the inspector

Understand the bot logic

Navigate to RW / Scripts and open Bot script in your code editor. You do not need to know what is going on in the Bot script. But notice the two methods called Move and Shoot . Again, you do not have to worry about what happens in these methods, but you must understand how to use them.

Note that the method Move accepts an input parameter of type CardinalDirection . CardinalDirection is an enumeration. A card of the type CardinalDirection can be either Up Down Right or Left . Based on the selected CardinalDirection the bot will move with exactly one square over the grid in the corresponding direction.

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

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

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

You can't use these methods yet, but you will soon. First you 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 – Erich Gamma, Richard Helm, Ralph Johnson and John Vlissides – or GoF (Gang of Four).

The authors state that “The command pattern encloses a request as an object, thus allowing us to parameterize other objects with different requests, queue or log requests, and support undoable operations. ”

Woah there! What?

I know, this definition is not 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 several objects based on the input parameter. This is what parameterizes to other objects.

The resulting "command" can then be stored with other commands prior to execution. This refers to queued of requests.

Command Queue

Finally, " undoable " here does not mean something that is impossible to achieve, but rather refers to the operations that can be reversed with an Undo functionality.

Interesting …

OK, but what does this mean in code?

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

Multiple instances of a Command class can be passed around as ordinary objects, meaning they can be stored in a data structure, such as a queue, stack, etc.

Finally, to execute a command, Execute must method being called. The class that triggers the execution is called Invoker .

All Clear!

Right now, the project includes an empty class called BotCommand . In the next section you will address the requirements to implement it above, so that Bot can perform actions using the command pattern. :]

Moving the Bot

Implementing the command pattern

In this section you implement the command pattern. There are several 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 into 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 sets; }

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

So what's going on, here?

  1. The command Name is simply used to store a human readable name on a command. This pattern is not important, but you will need it later in the tutorial.
  2. BotCommand the constructor accepts a function and a string. This will help you configure 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 invalid and accept an object of type Bot ( Bot component) as an input parameter.
  4. The property Execute will refer to the encapsulated method. You will use this to call the encapsulated method.
  5. ToString method is overridden to return the command Name . Useful for ease of use and use in UI for example.

Save the changes and – congratulations! You have 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 cases will, respectively, encapsulate the methods for moving Bot GameObject up, down, left and right, and also to do the bot shot.

To do so, paste the following into 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 cases, an anonymous method is sent to the designer. This anonymous method is encapsulated in the associated command object. As you can see, the signature of each of the anonymous methods matches the requirements of the ExecuteCallback delegate.

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

In the first four cases, they call the anonymous methods Move method on the bot object. However, the input parameter varies.

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

Finally, in the fifth case, the anonymous method calls the method on the bot object. This will make the penalty shot a projectile at the execution of this command.

Now that you have created the commands, they must somehow be accessed when the user provides input.

To do this, paste the following code into BotInputHandler just below 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 method HandleInput simply returns a single command instance based on the key pressed by the user. Save your changes before proceeding.

Using the commands

OK, now is the 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 .

UIManager class provides some useful help methods for the terminal UI used on the stage. If a method from UIManager is used, this tutorial will explain what it does, but for the purpose of this tutorial you do not need to know its inner behavior.

Further, bot refers to the bot component associated with Bot GameObject.

Add the following code now SceneManager replaces the existing code comment // 1 :

      // 1
private List  botCommands = new List  ();
private Coroutine perform Routine;

// 2
private invalid 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 ("Execute ...");
// 7
uiManager.ResetScrollToTop ();

// 8
for (int i = 0, count = botCommands.Count; i <count; i ++)
{
var command = botCommands [i];
command.Execute (bot);
// 9
uiManager.RemoveFirstTextLine ();
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 real run of the project in Game view.
You will examine this code afterwards. Save your changes before proceeding.

Running the game to test the command pattern

OK, it's time to build everything and hit Play in the Unity editor.

You should be able to set directional commands using the WASD keys. To enter the shot command, use the F key. Finally, to perform, press the Return key.

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

Notice how the lines are added to terminal UI. The commands are represented with their names in the user interface. This was possible because of the command Name .

Also note how the user interface scrolls to the top before execution and how the lines are removed during execution.

A closer look at the commands [19659043] Now it's time to look into the code you added in the "Using Commands" section:

  1. botCommands The list stores references to BotCommand the instance. Keep in mind that you only created five commands for memory, but there may be multiple references to the same command. Also, the variable executeCoroutine refers to ExecuteCommandsRoutine which handles the execution of the command.
  2. Update checks if the user has pressed the Return key, in which case it calls ExecuteCommands or CheckForBotCommands is called.
  3. CheckForBotCommands uses the static method HandleInput from BotInputHandler to check whether the user has provided an input, in which case a command is returned . The returned command is given to AddToCommands . However, if the commands are executed, that is, if executeRoutine is not null, they will return without giving anything to AddToCommands . As such, the user must wait until execution is complete.
  4. AddToCommands adds a new reference to returned the command instance, to botCommands .
  5. The InsertNewText method in the UIManager class adds a new line of text to the terminal UI. The line is the string that is passed to it as the input parameter. In this case, send commandName to it.
  6. Method Execute commands starts Execute commands Routine .
  7. ResetScrollToTop from UIManager rolls 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 against the method returned by the Execute property. A break of CommandPauseTime seconds is added after each execution.
  9. Method RemoveFirstTextLine from UIManager removes the very first line of text in the UI terminal, if any. As such, after a command has been executed, the name is removed from the UI.
  10. After executing all the commands, botCommands is removed and the bot is reset to the last checkpoint it crossed using ResetToLastCheckpoint . Finally, executeRoutine is set to zero and the user can continue to provide additional input.

Implementing Undo and Redo Functionalities

Run the scene again and try to reach the green checkpoint.

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

Return to SceneManager.cs and add the following variable declaration immediately after List statement for botCommands :

      private Stack  undoStack = new Stack  (); 

undoStack The variable is a Stack (from the collection family) that will store the references to the undo commands.

Now you want to add two methods UndoCommandEntry and RedoCommandEntry to undo and redo respectively. In the class SceneManager 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 executed or if the botCommands list is empty, the UndoCommandEntry method will do nothing. Otherwise, it will push the reference to the most recently entered command, to undoStack . This also removes the command reference from the botCommands list.
  2. RemoveLastTextLine method from UIManager removes the last line of text from terminal UI so that the user interface matches the contents of botCommands when an undo occurs. [19659005] RedoCommandEntry does nothing if undoStack is empty. Otherwise, it shows the last command at the top of undoStack and adds it to the botCommands list via AddToCommands .

Now you want to add keyboard inputs to use these methods. Inside the class SceneManager replace the body with 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. By pressing the R call the RedoCommandEntry method. 19659110] Action Edge Cases

    Great – you're almost done! But before you finish, you should make two things:

    1. If you enter a new command, undoStack will undoStack .
    2. Before executing the commands, undoStack should be removed.

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

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

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

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

          AddNewCommand (botCommand); 

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

          undoStack.Clear () ; 

    And you're done! Really this time!

    Phew!

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

    Try to reach the green checkpoint.

    Where to Go From Here?

    You can download the project material by 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 highly recommend checking out game programming patterns by Robert Nystrom.

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

    Challenge

    As a challenge, see if you can reach the green checkpoint at the end of the maze. I've provided the solution below in case you get stuck. It's just one of the many solutions you can come up with.

    [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 [19659005] 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, 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. [19659150]

    Source link