在游戏开发中使用命令模式的一个例子
在每个游戏中都有一块代码读取用户的输入——按钮按下,键盘敲击,鼠标点击,诸如此类。 这块代码会获取用户的输入,然后将其变为游戏中有意义的行为。
一种简单死了的实现会是这样:
void InputHandler::handleInput(){ if (isPressed(BUTTON_X)) jump(); else if (isPressed(BUTTON_Y)) fireGun(); else if (isPressed(BUTTON_A)) swapWeapon(); else if (isPressed(BUTTON_B)) lurchIneffectively(); } |
这个函数通常在游戏循环中每帧调用一次,我确信你可以理解它做了什么。这代码在我们想将用户的输入和程序行为硬编码在一起时可以正常工作,但是许多游戏允许玩家设定按键的功能。为了支持这点,需要将这些对jump()和fireGun()的直接调用转化为可以更换的东西。 “更换”听起来有点像声明变量,因此我们需要表示游戏行为的对象。进入:命令模式。
先实现命令:
class JumpCommand: ICommand { public void execute() { //这此方法可能来自另外的类,这里只是表达一下调用的意思。 jump(); } }
class FireCommand : ICommand { public void execute() { fireGun(); } } ///下面省略几千行 |
构造一个控制器,在输入控制器,为每个键存储一个指向命令的引用,相当于前面说的Invoker:
class InputHandler { private ICommand commandX; private ICommand commandY; private ICommand commandA; private ICommand commandB;
//处理输入的操作 public void handleInput() { if (isPressed(BUTTON_X)) commandX.execute(); else if (isPressed(BUTTON_Y)) commandY.execute(); else if (isPressed(BUTTON_A)) commandA.execute(); else if (isPressed(BUTTON_B)) commandB.execute(); }
//这是另外一个使用方式,与上面的是一样的,这里把command传出去 ICommand InputHandler::handleInput() { if (isPressed(BUTTON_X)) return commandX; if (isPressed(BUTTON_Y)) return commandY; if (isPressed(BUTTON_A)) return commandA; if (isPressed(BUTTON_B)) return commandB;
// Nothing pressed, so do nothing. return NULL; } }
|
这是命令模式的简短使用。如果你能够看出它的好处,可以接着往下看了。
在实践中,这并不是常用的功能,但是这经常会有类似的用例跳出来。到目前为止,我们只考虑了玩家控制的角色,但是游戏中的其他角色呢?它们被游戏AI控制。我们可以在AI和角色之间使用相同的命令模式;AI代码只是简单的放出Command对象。在选择命令的AI和展现命令的游戏角色间解耦给了我们很大的灵活度。 我们可以对不同的角色使用不同的AI,或者为了不同的行为而混合AI。 想要一个更加有攻击性的同伴?插入一个更加有攻击性的AI为其生成命令。 事实上,我们甚至可以为玩家角色加上AI, 这在原型阶段,游戏需要自动驾驶员是很有用的。把控制角色的命令变为第一公民对象,去除直接方法调用中严厉的束缚。 取而代之的是,将其视为命令队列,或者是命令流:
一些代码(输入控制器或者AI)产生一系列指令然后将其放入流中。 另一些指令(调度器或者角色自身)消耗指令并调用他们。 通过在中间加入了一个队列,我们解耦了消费者和生产者。
撤销和重做
这个例子是这种模式最广为人知的使用情况。如果一个命令对象可以做一件事,那么它亦可以撤销这件事。在一些策略游戏中使用撤销,这样你就可以回滚那些你不喜欢的操作。在人们创造游戏时,这是必不可少的工具。不能撤销肥手指导致的错误的编辑器,肯定会让游戏设计者恨你。
没有了命令模式,实现撤销非常困难,有了它,就是小菜一碟。假设我们在制作单人回合制游戏,想让玩家能撤销移动,这样他们就可以集中注意力在策略上而不是猜测上。我们已经使用了命令来抽象输入控制,所以每个玩家的举动都已经被封装其中。举个例子,移动一个单位的代码可能如下:
后续内容请参照: