3
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

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

Last updated at Posted at 2023-02-07

○はじめに

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

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

○説明

●コマンドパターンとは

命令をオブジェクトとすることができるデザインパターンです。
実際に理解した方が分かりやすいよ

●クラス図

commandPattern.jpg

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

○実行結果

CommandPattern.gif
UndoRedoで戻ったり進めたりできます。

○コード

●ICommand

コマンドのインターフェースです。
ExecuteUndoを定義します。

📄クリックしてコードを表示
.cs
public interface ICommand
{
    public void Execute();
    public void Undo();
}

Point💡

  • Executeで何かしらの処理をして、Undoで戻す処理をする感じです。

●MoveCommand

プレイヤーの移動コマンドです。

📄クリックしてコードを表示
.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();

    }
}

Point💡

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

●CommandInvoker

コマンドを呼び出すクラスです。
ここでUndoRedoの管理がされています。

また、UndoRedoは静的メソッドなので、外部から簡単に操作できます。

📄クリックしてコードを表示
.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();
        }
    }
}

Point💡

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

●InputManager

プレイヤーの入力処理を管理するクラスです。
※ボタンの数が多いので、重複部分は省略しています。ご了承ください。

📄クリックしてコードを表示
.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();
    }
}

Point💡

  • RunPlayerCommand()playerMoverと移動方向を指定します。
  • ボタンによって異なる方向に進むように、OnLeftInput()のような関数を各方向分作成します。
  • 移動方向に進める場合、
    • MoveCommandnewでインスタンス化します。
    • 次に、CommandInvokerExecuteCommand()でインスタンス化したMoveCommandExecute()を呼び出します。
  • OnUndoInputで、ボタンを押されたときにCommandInvokerUndoCommand()が呼び出されます。
  • 同様に、OnRedoInputでは、CommandInvokerRedoCommand()が呼び出されます。

○さいごに

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

●参考サイト

3
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?