[命令模式]在游戏开发中的应用


设计模式中的每个模式描述了一个在咱们周围不断重复发生的问题,以及该问题的解决方案的核心。这样,你就能一次又一次地使用该方案而没必要作重复劳动。
一个设计模式,它的服务对象是高层模块,在设计模式中称为客户端,所以在描述设计模式的时候都是以客户端做为使用方来进行描述的。
设计模式在类间关系这个粒度上给出常见问题的解决方案。属于软件工程中逻辑架构设计中至关重要的一环。html

快速查阅各种设计模式使用场景可参考此文:设计模式大全java


1、命令模式

定义: 把一个请求看作一个对象。这样用户就能够参数化客户端请求,实现请求的队列操做和日志管理,而且支持对操做进行撤销回退。(Encapsulate a request as an object, thereby letting users parameterize clients with different requests, queue or log requests, and support undoable operations.)git

概述: 在软件开发过程当中,咱们可能会遇到这样的问题,在程序运行前,不知道命令(本文中请求和命令都指代同一个东西,即客户端发起的一个须要处理的操做)的执行者是谁,也不知道须要执行哪一个命令。这时,咱们可使用命令模式将命令的发起者和命令的执行者解耦。把命令化做一个相对独立的对象,在发送者和执行者之间传输。程序运行时,再肯定须要执行的命令和其执行者,并将命令移交给命令管理器处理。若是对命令模式稍微作一点改动,让每一个命令提供执行的同时提供一个撤销的方法,就可以实现对命令的无限次撤销和重作,这在工程中具备很是普遍的应用。数据库

一、核心思想

其实定义就已经讲出了命令模式的核心思想:把一个命令看作一个对象。这样作就能让命令拥有全部对象所拥有的优点。模式中的其余参与者都是围绕它来运做的。编程

二、类图

图片名称

客户端|Client: 命令的发起者。肯定接下来要执行什么命令。
调用者|Invoker: 命令的管理者,不关心每一个命令具体是作什么内容,根据客户端的指示按序执行命令。
命令接口|ICommand:命令接口协议,肯定每一个命令须要提供的功能,这里要求每一个命令类都提供执行方法。
具体命令|ConcreteCommand:包含执行一个命令所需的全部上下文信息,例如执行接收者的哪一个方法,以及方法所须要的参数,甚至命令做为GUI 显示时的相关信息,例如应该显示的图标路径。具体命令类是命令模式中的核心节点,须要重点理解。设计模式

接收者|Receiver:命令所对应任务的实际执行者,位于调用链条的末端。服务器

注意:客户端发起的每个命令都是一个对象,即便是两条相同的命令,也是两个对象,而不是一个对象使用两次。网络

1)隐喻

下面经过一个隐喻来直观地展示命令模式的各个组成部分。架构

咱们假设这样一个场景,公元 6011 年,人类在银河系的上千的星球创建了殖民地,其中大唐和盖亚是两个敌对的国家,大唐准备发动一场战争,冷月是一个大型特种部队的指挥官,拥有护卫舰、运输舰和战列舰等单位的使用权,该部队具备侦查、暗杀、破坏等多种职能。冷月所在星球只有一个小型港口提供舰船的发射与停靠,有一个指挥塔负责舰船的调度。lua

某天,冷月派遣一个破坏小队使用战列舰前往盖亚一个冷僻星球进行破坏占领,派遣一个侦查小队使用护卫舰先行侦查。同时派遣一个侦查小队使用运输舰前往友军三十四军团处待命。冷月将舰船出港许可发送给指挥塔,让指挥塔安排舰船出港。

在上面这个场景中,冷月是命令的发起人,是一个 Client,他根据实际的战斗须要安排舰船和做战人员。每一艘舰船都至关因而一个具体命令对象,舰长知道应该把舰船运送到什么地方(类比命令的执行环境),也知道舰船的乘坐人员(命令的接收者),指挥塔是 Invoker,它不关心出港舰船的类型以及舰船的运载人员,只是接受冷月的舰船的出港请求,而后安排对应舰船出港。当舰船到达目的地以后,舰船运载的做战人员会去执行具体的做战任务。

(笔者构思了好几种场景,发现命令对象化在现实中比较难以找到对应物,一个是由于命令对象具备接收者的引用,具备主动性,若是是人引用人会比较奇怪,相对来讲具备主动性的机器容器引用人会比较合适,另外一方面则是命令对象在 Client 眼中和 Invoker 眼中有不同的抽象层次,也比较难以找到现实的对应物。科幻战争的这个场景虽然可以阐述命令模式的几个关键要素,可是场景自己有较多的干扰元素,有点过于复杂了,在实际中指挥塔不可能不关心舰船的类型,舰船做为命令的基本单位也有一点牵强。不过场景重在乎会,加深理解,笔者认为该场景仍是可以达成这一点,就放上来了,各位看官若是有更好的隐喻,欢迎在文后回复。)



三、应用场景以及优缺点

1)应用场景

适用于各类须要对命令进行调度管理以及传输的场景,也就是各类须要把命令包装成命令对象的场景。

1. 数据库的事务

每一个事务做为一个命令对象。

2. 线程池

每一个线程做为一个命令对象,客户端负责提供接收者

2)优势

  • 解开命令调用和命令执行之间的耦合
  • 方便命令的管理,包括命令组合、命令添加、命令记录和命令延时执行等
  • 容易扩展实现命令的撤销和重作

3)缺点

每一个命令都须要一个具体命令类,若是系统的命令繁多,可能会影响开发效率。

2、游戏应用

一、适用场景

命令模式在工程中的许多应用是很是基础性的,在游戏与非游戏项目中都能使用。下面单以游戏应用为例进行介绍:

1) 玩家输入控制 & 菜单项

在控制玩家输入的时候,使用命令模式可使得按键和按键对应的命令解绑,从而支持用户自定义,还能够把指令的触发和执行时间解绑,实现延时执行。对于多角色游戏,还能为玩家和AI之间提供一套通用的命令接口

2) 宏记录 & GM 指令

所谓宏,就是将一些命令组织在一块儿,做为一个单独命令完成一个特定任务。而宏记录是指将用户的一系列命令记录下来,将记录下来的一组命令做为一个宏。经过宏记录可以实现对玩家全部操做的录像。
GM 指令通常来讲可以模拟玩家操做来快速得到游戏的一些资源或者推动游戏进度。若是为玩家操做的命令类型实现一个 toScript() 方法,将玩家的一个操做直接转换成一个可执行的 GM 指令,那么就能够直接经过宏记录执行 GM 指令回放玩家的操做。关于这个 toScript() 方法稍微再解释一下,通常游戏里面会嵌入脚本引擎,例如 lua ,所以能够方便地将一个操做转化成可执行的 lua 脚本。

3) 网络应用

命令对象能够方便地在网络中进行传输,这样一个一个指令就能够同时在多个机器上运行。例如在移动游戏的PVE战斗中,客户端会先将玩家在战斗中的全部操做记录下来,在战斗结束后将全部操做上传给服务器再从新计算一遍,确保客户端没有做弊。

4) 进度条

将每一个须要加载的操做做为命令对象,并给命令对象实现一个加载时间预估的方法,可能使得进度条可以较为精准得反映加载进度。

5) 新手引导

使用命令模式能够实现某些类型新手引导的无缝切入,例如某个新手引导只须要玩家一路点击 next 直到点击确认进入实际的游戏环境。那么能够把这个新手引导做为一个命令对象,在须要展现该引导的时候新建命令对象,让该命令对象处理 next 的交互逻辑与每一个引导页的展现逻辑,等到完成的时候,命令对象直接调用传入接收者的执行方法完成转换。



二、具体案例

笔者实现了一个简单的 TicTacToe 游戏来介绍命令模式以及其撤销重作的变体。 TicTacToe 的基础游戏逻辑参考了 Andrew Arnott 的 Java 实现,在第三节中有相关引用。

1)伪代码

笔者将案例上传在这里: https://git.coding.net/tangyikejun/TicTacToeInCommandPattern.git
README 中有对每一个代码文件的基本说明。

2)点评

命令模式在游戏开发中真的十分经常使用,能够说是不可避免会遇到的一个模式。其撤销、重作变体更是应用普遍,随处可见该模式的身影。但愿各位看官可以掌握命令模式。
另外一方面该模式的专用性也比较强,在一个游戏项目中,可以抽象成命令的概念并很少,可是一旦遇到了就能比较容易的联想到,是个比较容易识别的模式。

3)评级

游戏中使用频度评级:★★★★☆

3、参考

  1. Game Programming Patterns[via Robert Nystrom] (貌似是本很不错的讲游戏中设计模式的书,美中不足没有中文版,Unity主程大大的平常(阿斌) 正在翻译。在命令模式一节中,做者详细介绍了该模式在游戏角色控制中的应用,而且介绍了撤销重作功能做为命令模式的一个变体该如何实现。)

  2. 游戏开发设计模式之命令模式(unity3d 示例实现)[via wolf96 in CSDN] (博主大概是根据 Game Programming Patterns 写了一个 Unity上的实现,写的没那么详细,可是浅显易懂,也不错。)

  3. Let Your Players Undo Their In-Game Mistakes With the Command Pattern[via Andrew Arnott](使用 Java 实现了井字棋游戏的撤销和重作功能,虽然也是用命令模式,可是实现的方式与 Game Programming Patterns 有一点点不同,这里使用了一个 Command Manager 来管理命令,内部分别保存 undo 和 redo 的命令栈,而不是只使用一个栈。)

  4. "Command" design pattern for games[](做者提到使用命令模式须要解决的两个实际问题,可是彷佛并无人给出合适的解决方案)

  5. Game programming patterns in Unity with C#[via eriknordeus](一样是利用命令模式来控制用户输入,实现撤销重作的功能。在这本书的例子里,做者把重作做为一条命令来进行执行,而且让命令来管理命令列表。笔者以为这样的实现方式不大合适,一来耦合度提升了,二来概念上undo、redo与其余命令其实并非平级关系。)
  6. 游戏编程模式- 再探Command模式[via 仙道菜](游戏编程模式- 再探Command模式 的一个翻译)

  7. 《JAVA与模式》之命令模式[via java_my_life](里面提到一个录音系统的示例,并非很贴合这个模式,可是里面提到了一点,使用命令模式能够方便得实现复合命令,即宏命令)

  8. 图说设计模式-命令模式[via me115](对命令模式相对介绍的比较详细,示例代码用 C++ 给出)

  9. Command pattern-WiKi(对命令模式的概念以及应用领域作了十分详细的说明,本文的应用部分参考了其中的内容)

相关文章
相关标签/搜索