命令模式(2)
實(shí)現(xiàn)undo和redo-------基于Unity3D
我們已經(jīng)知道了什么是命令模式,一個(gè)命令即是一個(gè)對(duì)象。
撤銷和重做是命令模式成名之作, 利用撤銷,我們可以回滾一些不滿意的操作。例如在策略游戲中,我們常常需要排兵布陣,布置自己的戰(zhàn)術(shù),往往在沒(méi)有確定之前,對(duì)某個(gè)或某些操作不是很滿意,進(jìn)而希望撤銷之前的操作,或者自己的誤操作撤銷了某些步驟,希望重做。在策略游戲中,我們更希望玩家的注意力集中在策略上,而不是因?yàn)檎`操作而無(wú)法回滾。
如果我們不使用命令模式,我們將很難實(shí)現(xiàn)撤銷和重做的功能,但事實(shí)上,我們利用命令模式就可以輕而易舉的實(shí)現(xiàn)這個(gè)功能。還是很之前一樣,我將基于Unity3D來(lái)實(shí)現(xiàn)這些功能。
為了簡(jiǎn)單起見(jiàn),我將完成一個(gè)最簡(jiǎn)單的undo和redo的功能。這個(gè)可以u(píng)ndo和redo的命令是:移動(dòng)玩家一個(gè)單位。
這個(gè)命令和之前的命令有所區(qū)別,最本質(zhì)的區(qū)別在于之前的命令只要?jiǎng)?chuàng)建一次,例如Jump功能,默認(rèn)會(huì)綁定給K,在之后如果不進(jìn)行修改的話,這個(gè)命令始終會(huì)保持只有一個(gè)實(shí)例。而之上我們描述的命令更加具體,這也就意味著每次玩家選擇一個(gè)動(dòng)作,輸入處理程序都要?jiǎng)?chuàng)建一個(gè)新的命令實(shí)例。
既然我們希望撤銷和重做,那么毫無(wú)疑問(wèn)的是我們必須保存下來(lái)這些命令,如果不保存,那么當(dāng)我們需要撤銷或重做的時(shí)候,去哪里找這些已經(jīng)執(zhí)行了的命令呢?
我們?cè)賮?lái)仔細(xì)想想,通常情況下,我們后執(zhí)行的命令會(huì)被先撤銷。后撤銷的命令會(huì)被先執(zhí)行。后……先……,沒(méi)錯(cuò)stack,我們可以利用棧這一數(shù)據(jù)結(jié)構(gòu)來(lái)保存我們使用的命令。具體來(lái)說(shuō)我是這樣設(shè)計(jì)的:
- commands棧,當(dāng)每執(zhí)行一個(gè)命令的時(shí)候(在這里我們執(zhí)行的對(duì)象移動(dòng)的操作),將這個(gè)命令壓入到commands這個(gè)棧中去。
- redoCommands棧,當(dāng)我們每撤銷一個(gè)命令的時(shí)候,我們將這個(gè)撤銷的命令壓入到 redoCommands這個(gè)棧中去,當(dāng)我們需要重做時(shí),將命令彈出。注意此時(shí)的命令相當(dāng)于是又執(zhí)行了命令,于是我們?cè)俅螌⑦@個(gè)命令壓入到commands這個(gè)棧中去。當(dāng)有新的命令產(chǎn)生的時(shí)候,將清空redoCommands這個(gè)棧
接下來(lái)我會(huì)對(duì)命令模式(1)中的代碼進(jìn)行進(jìn)一步的修改,來(lái)達(dá)到undo和redo的效果。
1. 創(chuàng)建一個(gè)move的命令類:
x_,y_代表此命令,希望傳遞進(jìn)來(lái)的游戲?qū)ο笠苿?dòng)到的位置坐標(biāo)xBefore_,yBefore_代表游戲?qū)ο笤趫?zhí)行這個(gè)命令之前的坐標(biāo)execute和undo分別會(huì)調(diào)用actor的方法,讓其移動(dòng)。
using System.Collections; using System.Collections.Generic; using UnityEngine;public class MoveCommand : Command {private int x_, y_;private int xBefore_, yBefore_;public MoveCommand(ActorAction actor, int x,int y){x_ = x;y_ = y;xBefore_ = actor.getX();yBefore_ = actor.getY();}public override void execute(ref ActorAction actor){actor.moveTo(x_,y_);}public override void undo(ref ActorAction actor){actor.moveTo(xBefore_, yBefore_);} }2. ActorAction中增加移動(dòng)的方法
using System.Collections; using System; using System.Collections.Generic; using UnityEngine;public class ActorAction : MonoBehaviour {private int x_, y_;private Transform actorTrans_;void Start(){actorTrans_ = this.gameObject.GetComponent<Transform>();}public int getX(){return x_;}public int getY(){return y_;}public void moveTo(int x,int y){x_ = x;y_ = y;actorTrans_.position = new Vector3(x, y, actorTrans_.position.z);}public void attack(){Debug.Log("attack");}public void jump(){Debug.Log("jump");}public void avoid(){Debug.Log("avoid");}}3. 在InputHandler實(shí)現(xiàn)上述的兩個(gè)棧
在InputHandler類中添加兩個(gè)新成員
private Stack commands;
private Stack redoCommands;
在開(kāi)始的時(shí)候初始化它們(在Start函數(shù)中進(jìn)行初始化)
commands = new Stack();
redoCommands = new Stack();將handleInput這個(gè)函數(shù)做出修改
當(dāng)前我們可以撤銷的操作是向上下左右四個(gè)方向進(jìn)行移動(dòng)。當(dāng)接到新的命令時(shí),我們將清空redo棧,即來(lái)了新的命令以后,就不可以重做了。接著我們根據(jù)輸入創(chuàng)建新的移動(dòng)命令,并壓入到commands棧中,返回這個(gè)命令。實(shí)現(xiàn)undo方法
當(dāng)commands里不為空的時(shí)候,我們將當(dāng)前的命令壓入要redoCommands棧中,然后彈出這個(gè)命令并執(zhí)行實(shí)現(xiàn)redo方法
當(dāng)redoCommands不為空時(shí),同樣將其壓入commands中,然后彈出并執(zhí)行總而言之
如果要是在撤銷和重做之間來(lái)回切換的話,執(zhí)行的操作也就是在commands和redoCommands這兩個(gè)棧之間進(jìn)行pop和push操作。
以下是完整的代碼
using System.Collections; using System.Collections.Generic; using System; using UnityEngine;public class InputHandler :MonoBehaviour{private Command buttonJ_;private Command buttonK_;private Command buttonL_;private Command buttonUp_;private Command buttonDown_;private Command buttonLeft_;private Command buttonRight_;private ActorAction actor_;private Stack<Command> commands;private Stack<Command> redoCommands;void Start(){actor_ = this.gameObject.GetComponent<ActorAction>();commands = new Stack<Command>();redoCommands = new Stack<Command>();buttonJ_ = new AttackCommand();if (buttonJ_ == null) Debug.Log("buttonJ_ is null!");buttonK_ = new JumpCommand();if (buttonK_ == null) Debug.Log("buttonK_ is null!");buttonL_ = new AvoidCommand();if (buttonL_ == null) Debug.Log("buttonL_ is null!");}public void bindCommand(string buttonName,Command command){switch (buttonName){case "J":case "j": buttonJ_ = command; break;case "K":case "k": buttonK_ = command; break;case "L":case "l": buttonL_ = command; break;}}public Command handleInput(string keyName){switch (keyName){case "J":return buttonJ_;case "K":return buttonK_;case "L":return buttonL_;case "Up":redoCommands.Clear();buttonUp_ = new MoveCommand(actor_, actor_.getX(), actor_.getY() + 1);if (buttonUp_ == null) Debug.Log("buttonUp_ is null!");commands.Push(buttonUp_);return buttonUp_;case "Down":redoCommands.Clear();buttonDown_ = new MoveCommand(actor_, actor_.getX(), actor_.getY() - 1);if (buttonDown_ == null) Debug.Log("buttonDown_ is null!");commands.Push(buttonDown_);return buttonDown_;case "Left":redoCommands.Clear();buttonLeft_ = new MoveCommand(actor_, actor_.getX() - 1, actor_.getY());if (buttonLeft_ == null) Debug.Log("buttonLeft_ is null!");commands.Push(buttonLeft_);return buttonLeft_;case "Right":redoCommands.Clear();buttonRight_ = new MoveCommand(actor_, actor_.getX() + 1, actor_.getY());if (buttonRight_ == null) Debug.Log("buttonRight_ is null!");commands.Push(buttonRight_);return buttonRight_;default:return null;}}public void undo(){if(commands.Count!=0){redoCommands.Push(commands.Peek());commands.Pop().undo(ref actor_);} }public void redo(){if (redoCommands.Count != 0){commands.Push(redoCommands.Peek());redoCommands.Pop().execute(ref actor_);}} }4. 修改判斷輸入的主邏輯
在判斷輸入的地方加上Z和X對(duì)應(yīng)的功能,我定義Z為undoX為redo。
else if (Input.GetKeyDown(KeyCode.Z))inputHandler.undo(); else if (Input.GetKeyDown(KeyCode.X))inputHandler.redo();至此我們已經(jīng)完成了基本的undo和redo的功能了!
轉(zhuǎn)載于:https://www.cnblogs.com/WAoyu/p/8459249.html
總結(jié)
- 上一篇: hdu 4857 Little Devi
- 下一篇: 小甲鱼OD学习第21讲