用命令模式实现命令的输入与撤销
< 返回列表时间: 2019-11-12来源:OSCHINA
首先说下目的,是为了实现类似手柄的输入功能。假设一个手柄有A/B两个键,可以输入不同的指令并且可以替换(比如吃血瓶/跳跃/射击等操作)。除此之外还有一个额外的需求,就是可以记录玩家最近输入的X次操作,并且可以撤销。
我们先写一个Actor类,作为被控制的主角,这里只写了HP一个字段,因为我们下面暂时将A/B键的功能设为加血和扣血。 public class Actor { public int HP; public Actor(int hp) { HP = hp; } }
然后写好Command基类,需要设置一个被控制的Actor(游戏中可以替换主角/AI等),并且包含一个执行和撤销的方法。 public class Command { protected Actor actor; public Command(Actor a) { actor = a; } ~Command() { } public virtual void Excute() { } public virtual void Undo() { } }
下面写了一个CommandA,执行的操作是对某一个Actor加10点血量,除此之外还写了一个CommandB,执行扣血,因为与CommandA差不多就不展示了。 public class CommandA : Command { ~CommandA() { } public CommandA(Actor a) : base(a) { } public override void Excute() { actor.HP += 10; Debug.Log("actor 加了10hp, 现在hp: " + actor.HP); } public override void Undo() { actor.HP -= 10; Debug.Log("actor 撤销了加血, 现在hp: " + actor.HP); } }
下面是要实现一个命令的栈,可以记录最近的X次命令(我假设的是五次),并可以逐一撤销。 public class CommandNode //命令栈中的节点,记录的当前的命令以及它的上一个和下一个命令 { ~CommandNode() { Debug.Log("我被释放了"); } public Command m_command; public Command Command{ get { return m_command; } } public CommandNode(Command command) { m_command = command; } public CommandNode preCommand; public CommandNode nextCommand; } public class CommandStack { private int m_iCapacity = 0; //stack容量 private int m_iCount = 0; //stack内命令数量 private CommandNode firstNode; //存储的第一次的命令 private CommandNode lastNode; //最后一次命令 public CommandStack(int capacity) { m_iCapacity = capacity; } public bool IsEmpty() { return m_iCount == 0; } public void Push(Command command) { if(IsEmpty()) //如果stack为空,将第一个命令和最后一个命令都设成当前这个 { CommandNode node = new CommandNode(command); node.preCommand = null; node.nextCommand = null; firstNode = node; lastNode = node; } else //stack不为空,将当前的命令设为最后的命令 { CommandNode node = new CommandNode(command); node.preCommand = lastNode; node.nextCommand = null; lastNode.nextCommand = node; lastNode = node; } m_iCount++; if (m_iCount > m_iCapacity) //命令数超过容量了,去除第一个 { firstNode = firstNode.nextCommand; firstNode.preCommand = null; m_iCount--; } } public Command Pop() { Command result = lastNode.Command; //返回最后一次命令 if (lastNode.preCommand != null) //如果最后一次命令之前还有命令,将其设为最后的命令 { lastNode = lastNode.preCommand; lastNode.nextCommand = null; } else //如果没有了,说明stack空了,将第一次和最后一次命令置空 { firstNode = null; lastNode = null; } m_iCount--; return result; } }
写完上面的下面就是我的主要控制类了。 public class CommandPatternMain : MonoBehaviour { private Actor actor = new Actor(100); CommandStack commands = new CommandStack(5); Command command; Command tempCommand = null; private Command m_CommandA; private Command m_CommandB; private void Start() { SetCommands(); } private void SetCommands() { m_CommandA = new CommandA(actor); m_CommandB = new CommandB(actor); } private void Update() { command = InputHandler(); if(command != null) { command.Excute(); commands.Push(command); } if(Input.GetKeyDown(KeyCode.Space) && !commands.IsEmpty()) { tempCommand = commands.Pop(); if(tempCommand != null) tempCommand.Undo(); } } private Command InputHandler() { if (Input.GetKeyDown(KeyCode.A)) { return m_CommandA; } if (Input.GetKeyDown(KeyCode.B)) { return m_CommandB; } return null; } }
这个类里有两个固定的命令A和B,当玩家按下A和B对应的键位时执行这两个命令。至于具体是什么命令,我们在SetCommands方法里赋值了,当然游戏进行中可以随时替换。每当玩家执行一次命令之后会推入一个容量为5的命令栈,当玩家按下撤销的按键时会Pop出最后一次命令,并执行Undo的方法。下面看看测试结果,我们先加五次血,再扣三次血,再撤销五次,然后再加五次血,再扣三次血,再撤销五次,结果如下:

热门排行