○はじめに
コマンドパターンを使用すると、ソフトとかパズルゲームなどによくあるUndo、Redo(元に戻す、やりなおし)を実装できます。(単刀直入)
あとは、キーコンフィグなども楽に実装できるそうです。↓参照
○説明
●コマンドパターンとは
命令をオブジェクトとすることができるデザインパターンです。
実際に理解した方が分かりやすいよ
●クラス図
公式プロジェクトの例をクラス図にすると、こんな感じになります。
MoveCommand
というのが今回使用するコマンドで、CommandInvoker
というのがコマンドを実行するクラスです。
○実行結果
○コード
●ICommand
コマンドのインターフェースです。
Execute
とUndo
を定義します。
📄クリックしてコードを表示
.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
コマンドを呼び出すクラスです。
ここでUndo
やRedo
の管理がされています。
また、Undo
やRedo
は静的メソッドなので、外部から簡単に操作できます。
📄クリックしてコードを表示
.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()
のような関数を各方向分作成します。 - 移動方向に進める場合、
MoveCommand
をnew
でインスタンス化します。- 次に、
CommandInvoker
のExecuteCommand()
でインスタンス化したMoveCommand
のExecute()
を呼び出します。
OnUndoInput
で、ボタンを押されたときにCommandInvoker
のUndoCommand()
が呼び出されます。- 同様に、
OnRedoInput
では、CommandInvoker
のRedoCommand()
が呼び出されます。
○さいごに
なれたら結構分かりやすいデザインパターンです。いい感じに使いたいですね!(適当)