命令模式(Command Pattern
):當你想要追蹤一系列特定動作時,可以使用命令模式(command pattern
)。如果你玩過帶有撤銷(undo
)/重做(redo
)功能或是能夠在列表中保留輸入歷史的遊戲,你很可能已經見過命令模式。想像一個策略遊戲,玩家可以在實際執行之前計劃多個動作,這就是命令模式。
在設計模式中,命令模式有以下角色參與其中
Command
:抽象命令,可能為一個介面(Interface
)或是抽象類(Abstract Class
),例如ICommand
ConcreteCommand
:具體命令,即實作抽象命令的Class
Invoker
:命令的調用者,發起者,管理者
Receiver
:接收者,實際執行命令者,被Command訪問與操作
- 使用命令的客戶端(
Client
)
命令模式特點:
- 命令模式不直接呼叫方法(method)而是會將單個或多個方法封裝為命令物件(
command object
)。
- 命令物件會存放到一個容器(像是
Stack
或是Queue
)中,讓你可以在想要的時間點去執行或是撤銷這些命令物件。
- 新增命令不會影響其他地方,符合了開閉原則(Open-closed principle)
- 除了撤銷(
undo
)/重做(redo
)功能,也可以用來實現回放(Play back
)功能
接收者(Receiver
)類:PlayerMover
,在命令物件執行時真正執行動作。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| public class PlayerMover : MonoBehaviour { [SerializeField] private LayerMask obstacleLayer; private const float boardSpacing = 1f; private PlayerPath playerPath; public PlayerPath PlayerPath => playerPath;
private void Start() { playerPath = gameObject.GetComponent<PlayerPath>(); }
public void Move(Vector3 movement) { Vector3 destination = transform.position + movement; transform.position = destination; }
public bool IsValidMove(Vector3 movement) { return !Physics.Raycast(transform.position, movement, boardSpacing, obstacleLayer); } }
|
定義一個抽象命令介面(ICommand
)
1 2 3 4 5
| public interface ICommand { void Execute(); void Undo(); }
|
定義一個具體命令
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| public class MoveCommand : ICommand { private PlayerMover _playerMover; private Vector3 _movement;
public MoveCommand(PlayerMover player, Vector3 moveVector) { this._playerMover = player; this._movement = moveVector; }
public void Execute() { _playerMover?.PlayerPath.AddToPath(_playerMover.transform.position + _movement); _playerMover.Move(_movement); }
public void Undo() { _playerMover.Move(-_movement); _playerMover?.PlayerPath.RemoveFromPath(); } }
|
定義一個使用命令的命令调用者類CommandInvoker
,它只負責執行(ExecuteCommand()
)和撤銷(UndoCommand()
)命令。
- 它含有一個容器(
undoStack
),用來儲存一系列的命令。
- 當執行命令時,從容器中取出一個命令物件執行方法(
Execute
)
- 當撤消命令時,從容器中取出一個命令物件,並執行撤銷方法(
Undo
)1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| public class CommandInvoker { private static Stack<ICommand> _undoStack = new Stack<ICommand>(); private static Stack<ICommand> _redoStack = new Stack<ICommand>();
public static void ExecuteCommand(ICommand command) { command.Execute(); _undoStack.Push(command); _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(); } } }
|
使用命令的客戶端(Client
)類,InputManager
,它透過CommandInvoker
執行命令
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| public class InputManager : MonoBehaviour { [Header("Button Controls")] [SerializeField] Button forwardButton; [SerializeField] Button backButton; [SerializeField] Button leftButton; [SerializeField] Button rightButton; [SerializeField] Button undoButton; [SerializeField] Button redoButton;
[SerializeField] private PlayerMover player;
private void Start() { forwardButton.onClick.AddListener(OnForwardInput); backButton.onClick.AddListener(OnBackInput); rightButton.onClick.AddListener(OnRightInput); leftButton.onClick.AddListener(OnLeftInput); undoButton.onClick.AddListener(OnUndoInput); redoButton.onClick.AddListener(OnRedoInput); }
private void RunPlayerCommand(PlayerMover playerMover, Vector3 movement) { if (playerMover == null) return; if (playerMover.IsValidMove(movement)) { ICommand command = new MoveCommand(playerMover, movement); CommandInvoker.ExecuteCommand(command); } }
private void OnLeftInput() { RunPlayerCommand(player, Vector3.left); } private void OnRightInput() { RunPlayerCommand(player, Vector3.right); } private void OnForwardInput() { RunPlayerCommand(player, Vector3.forward); } private void OnBackInput() { RunPlayerCommand(player, Vector3.back); } private void OnUndoInput() { CommandInvoker.UndoCommand(); } private void OnRedoInput() { CommandInvoker.RedoCommand(); } }
|
Reference: https://github.com/Unity-Technologies/game-programming-patterns-demo/tree/main/Assets/9%20Command