《游戏编程模式》-Bob Nystrom
书籍汉化版网址:https://gpp.tkchu.me/
笔记目录:https://hmxs.games/posts/2000/
命令模式 基本定义与概念 命令模式(Command Pattern)是一种将函数调用对象化的设计模式,“When I’ve used it in the right place, it’s neatly untangled some really gnarly code.”,其可以增加代码的条理性,同时使得代码有更好的拓展性。命令模式的核心是将回调用面向对象的方法进行实现,让方法的调用对象化,当方法成为对象,记录与替换它们就变得很容易。
应用场景 有回溯/撤销命令的需求时 有按键更改/方法替换的需求时 有记录指令日志的需求时 优点 当在正确的地方使用时,可以将复杂的代码清理干净 让命令本身变为实例,提高代码的拓展性 可以为实现“指令的撤销”带来极大的便利 缺点 Unity简单实现 命令基类/接口 此处为C#的接口实现,也可通过抽象类+虚函数的方法实现
1 2 3 4 5 public interface ICommand { void Execute (GameObject obj ) ; }
具体命令 此处创建了两个具体命令:JumpCommand,WalkCommand,具体命令需继承自ICommand接口,并实现Execute方法
实现的Execute函数为命令的具体逻辑,如果需要可以通过构造函数传入参数
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 public class JumpCommand : ICommand { public void Execute (GameObject obj ) { Debug.Log(obj.name + "Jump" ); } } public class WalkCommand : ICommand { private Vector2 _targetPos; public WalkCommand (Vector2 pos ) { _targetPos = pos; } public void Execute (GameObject obj ) { Debug.Log(obj.name + "Walk to" + _targetPos); } }
命令调用 创建继承MonoBehaviour类的脚本用于调用命令
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 public class InputHandler : MonoBehaviour { private Vector2 _pos; private ICommand _currentCommand; private void Update () { _currentCommand = InputCommand(); if (_currentCommand != null ) { _currentCommand.Execute(gameObject); } } private ICommand InputCommand () { if (Input.GetKeyDown(KeyCode.A)) return new JumpCommand(); if (Input.GetKeyDown(KeyCode.D)) { _pos += Vector2.one; return new WalkCommand(_pos); } return null ; } }
在上面的例子中,我们可以通过命令模式在“输入”与“调用函数”之间添加了一层间接寻址,使得“输入”与“调用函数”之间不存在硬编码:
命令的撤销 在命令模式中,每一条命令都是一个实际存在的类,由此我们可以很方便的实现回滚操作/撤销指令等功能,这在回合制游戏中有着重要的作用
以下代码实现了指令的撤销:
为接口与命令类实现Undo方法,在其中编写撤销代码,当我们需要撤销某一条命令时,我们便只需要调用该命令的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 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 public interface ICommand { void Execute () ; void Undo () ; } public class JumpCommand : ICommand { public void Execute () { Debug.Log( "Jump" ); } public void Undo () { Debug.Log("Jump Undo" ); } } public class WalkCommand : ICommand { private GameObject _obj; private Vector2 _beforePos; private Vector2 _targetPos; public WalkCommand (GameObject obj,Vector2 pos ) { _obj = obj; _beforePos = obj.GetComponent<InputHandler>().pos; _targetPos = _beforePos + pos; } public void Execute () { _obj.GetComponent<InputHandler>().pos = _targetPos; Debug.Log(_obj.name + "Walk To" + _targetPos); } public void Undo () { _obj.GetComponent<InputHandler>().pos = _beforePos; Debug.Log(_obj.name + "Back To" + _beforePos); } }
在调用命令的脚本中,通过Unity提供的List来存储指令,通过一个int变量_commandHead表示List的指针,指向当前命令,通过对commandHead的改变便可以灵活调用指令
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 40 41 42 43 44 public class InputHandler : MonoBehaviour { private List<ICommand> _commands = new (); private int _commandHead = -1 ; public Vector2 pos; private ICommand _currentCommand; private void Update () { _currentCommand = InputCommand(); if (_currentCommand != null ) { _commandHead += 1 ; if (_commands.Count <= _commandHead) _commands.Add(_currentCommand); else _commands[_commandHead] = _currentCommand; _commands[_commandHead].Execute(); } if (Input.GetKeyDown(KeyCode.Z)) { if (_commandHead >= 0 ) { _commands[_commandHead].Undo(); _commandHead -= 1 ; } } } private ICommand InputCommand () { if (Input.GetKeyDown(KeyCode.A)) return new JumpCommand(); if (Input.GetKeyDown(KeyCode.D)) { return new WalkCommand(gameObject, Vector2.one); } return null ; } }