【Unity】公式プロジェクトから、デザインパターンを学んでみよう(Part 9:Commandパターン)

# ○はじめに
コマンドパターンを使用すると、**ソフトとかパズルゲームなどによくあるUndo、Redo(元に戻す、やりなおし)を実装できます**。(単刀直入)

あとは、キーコンフィグなども楽に実装できるそうです。↓参照

https://www.youtube.com/watch?v=czNAWqhOL9o


<!-- 前回の記事
前回の記事→[前回の記事]()
-->



# ○説明
## ●コマンドパターンとは
**命令をオブジェクトとすることができるデザインパターンです。**
実際に理解した方が分かりやすいよ



## ●クラス図
![commandPattern.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2419795/583f9ec1-4eaa-0ff4-bd73-a98f72c003d2.jpeg)

公式プロジェクトの例をクラス図にすると、こんな感じになります。
`MoveCommand`というのが今回使用するコマンドで、`CommandInvoker`というのがコマンドを実行するクラスです。

# ○実行結果
![CommandPattern.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2419795/c459510c-5591-bf5a-b3fc-2404514f8ee7.gif)
UndoRedoで戻ったり進めたりできます。


# ○コード
## ●ICommand
コマンドのインターフェースです。
`Execute`と`Undo`を定義します。
<details><summary><b>📄クリックしてコードを表示</b></summary>

```.cs
public interface ICommand
{
    public void Execute();
    public void Undo();
}
```

</details>

:::note
**Point💡**
- `Execute`で何かしらの処理をして、`Undo`で戻す処理をする感じです。
:::


## ●MoveCommand
プレイヤーの移動コマンドです。
<details><summary><b>📄クリックしてコードを表示</b></summary>

```.cs
public class MoveCommand : ICommand
{
    private PlayerMover _playerMover;
    private Vector3 _movement;

    // pass parameters into the constructor
    public MoveCommand(PlayerMover player, Vector3 moveVector)
    {
        this._playerMover = player;
        this._movement = moveVector;
    }

    // logic of thing to do goes here
    public void Execute()
    {
        // add point to path visualization
        _playerMover?.PlayerPath.AddToPath(_playerMover.transform.position + _movement);

        // move by vector
        _playerMover.Move(_movement);
    }
    // undo logic goes here
    public void Undo()
    {
        // move opposite direction
        _playerMover.Move(-_movement);

        // remove point from path visualization
        _playerMover?.PlayerPath.RemoveFromPath();

    }
}
```

</details>

:::note
**Point💡**
- コンストラクタでプレイヤー情報と移動方向をセットします。
- **`Execute`で移動方向に進み、`Undo`で以前の位置に戻ります。**
:::



## ●CommandInvoker
コマンドを呼び出すクラスです。
ここで`Undo`や`Redo`の管理がされています。

また、`Undo`や`Redo`は静的メソッドなので、外部から簡単に操作できます。
<details><summary><b>📄クリックしてコードを表示</b></summary>

```.cs
public class CommandInvoker
{
    // stack of command objects to undo
    private static Stack<ICommand> _undoStack = new Stack<ICommand>();

    // second stack of redoable commands
    private static Stack<ICommand> _redoStack = new Stack<ICommand>();

    // execute a command object directly and save to the undo stack
    public static void ExecuteCommand(ICommand command)
    {
        command.Execute();
        _undoStack.Push(command);

        // clear out the redo stack if we make a new move
        _redoStack.Clear();
    }

    public static void UndoCommand()
    {
        if (_undoStack.Count > 0)
        {
            ICommand activeCommand = _undoStack.Pop();
            _redoStack.Push(activeCommand);
            activeCommand.Undo();
        }
    }

    public static void RedoCommand()
    {
        if (_redoStack.Count > 0)
        {
            ICommand activeCommand = _redoStack.Pop();
            _undoStack.Push(activeCommand);
            activeCommand.Execute();
        }
    }
}
```

</details>

:::note
**Point💡**
- undoStackやredoStackで、コマンドをstackに格納して管理します。
- ExecuteCommand()でコマンドを実行し、undoStackに追加します。
- UndoCommand()でundoStackからコマンドを取り出し、redoStackに追加して、そのコマンドのUndo()を実行します。
- RedoCommand()でredoStackからコマンドを取り出し、undoStackに追加して、そのコマンドのExecute()を実行します。

:::



## ●InputManager
プレイヤーの入力処理を管理するクラスです。
<ins>※ボタンの数が多いので、重複部分は省略しています。ご了承ください。</ins>
<details><summary><b>📄クリックしてコードを表示</b></summary>

```.cs
public class InputManager : MonoBehaviour
{
    // UI Button controls
    [Header("Button Controls")]
    [SerializeField] Button forwardButton;
    ...

    [SerializeField] private PlayerMover player;

    private void Start()
    {
        // button setup
        forwardButton.onClick.AddListener(OnForwardInput);
        ...
    }

    private void RunPlayerCommand(PlayerMover playerMover, Vector3 movement)
    {
        if (playerMover == null)
        {
            return;
        }

        // check if movement is unobstructed
        if (playerMover.IsValidMove(movement))
        {
            // issue the command and save to undo stack
            ICommand command = new MoveCommand(playerMover, movement);

            // we run the command immediately here, but you can also delay this for extra control over the timing
            CommandInvoker.ExecuteCommand(command);
        }
    }

    private void OnLeftInput()
    {
        RunPlayerCommand(player, Vector3.left);
    }
    ...

    private void OnUndoInput()
    {
        CommandInvoker.UndoCommand();
    }

    private void OnRedoInput()
    {
        CommandInvoker.RedoCommand();
    }
}
```

</details>

:::note
**Point💡**
- `RunPlayerCommand()`で`playerMover`と移動方向を指定します。
- ボタンによって異なる方向に進むように、`OnLeftInput()`のような関数を各方向分作成します。
- 移動方向に進める場合、
    - **`MoveCommand`を`new`でインスタンス化します。**
    - 次に、**`CommandInvoker`の`ExecuteCommand()`でインスタンス化した`MoveCommand`の`Execute()`を呼び出します。**
- **`OnUndoInput`で、ボタンを押されたときに`CommandInvoker`の`UndoCommand()`が呼び出されます。**
- **同様に、`OnRedoInput`では、`CommandInvoker`の`RedoCommand()`が呼び出されます。**

:::



# ○さいごに
なれたら結構分かりやすいデザインパターンです。いい感じに使いたいですね！(適当)

<!-- 次回の記事
次回の記事→[次回の記事]()
-->

## ●参考サイト



<!---------------------->

<!-- テンプレ
# ○見出し1
## ●見出し2
### ・見出し3

-->