Unity沙盒建造游戏设计(2) - 命令模式

1,462 阅读4分钟

前言

在近期的公司项目中,需求项目开发一个类沙盒建造功能。我有幸负责项目中的关键部分,这一经历充满了挑战与收获。鉴于这个部分内容的丰富性和技术性,我决定将涉及相关设计以及本人的一部分思绪分成四期内容。本文是系列文章的第二期,将重点讨论建造中的命令模式设计。

为什么会有命令模式相关的内容呢? 允许用户根据不同的请求对操作进行参数化、排队、记录日志以及撤销操作等。这是大部分带有编辑模式内容不可会缺的一环, 本篇将从基础的命令模式内容讲起, 到业务层基础的命令搭建, 再到整体的框架设计,这篇文章是系列讲解中的重点之一.

命令模式

命令模式(Command Pattern)是一种行为设计模式,它将请求或简单操作封装为一个对象,这样可以使用不同的请求、队列或日志来参数化其他对象。同时,它也支持可撤销的操作。

关于行为设计模式

命令模式通常包括四个部分: 命令接口具体命令(ConcreteCommand)接收者(Receiver)调用者(Invoker)

命令接口

这部分内容比较简单,基本就是命令的基本声明,如ExecuteUndoTypeRedo等等

不同语言的实现方式也有不用, 可以用基类, 也可以使用接口, 甚至是协议

实现几个空方法即可

这里笔者由于使用的语言语法糖相对落后 游戏开发者苦Lua久已

所以使用的是基类的方法实现 还比上述多一个Init, 然后也没有Redo的需求

具体命令

再详细编写写命令之前, 首先需要抽象需求内容, 比如笔者这边是几个需求:

CreateDeleteMoveRotateStyleReplace

需要针对他们创建不同的具体命令子类

关于命令类型

根据抽象出来的命令种类, 执行对应的方法

在这一步笔者的做法是 将每一个方法的执行地图元素Element(传统上命令模式的接收者)放到了基类的 Init

ExecuteUndo中执行Element的对应方法

从而达成每一个命令的执行逻辑

接收者

上文其实也有所提及, 命名听起来与理解起来还是有点绕的, 但它其实就是命令内容的执行者.

笔者这里是将基类命令的初始化函数传入了接收者, 不在初始化部分传入也是可以的, 倘若你只有一个单一接收者, 那么在所有命令的操作中再次传入即可.

调用者

调用者是命令模式的灵魂之一, 没有调用者维护命令的数据结构那么命令就是一盘散沙, 在通常理解中调用者大概有两个形式, 栈与队列, 各有优劣

  • 栈: 由于栈后进先出的特性, 能够非常完美的实现撤销重做操作, 与用户期望操作顺序相同, 是绝大部分命令模式的首先调用者格式, 同时也是作者本次选用的格式
  • 队列: 由于队列先进先出的特性, 能够实现异步处理批处理, 这些需要指定运行顺序的内容, 虽然在需求上没有那么常见, 但是对性能优化上起着这关无上的作用

笔者这次需要实现的需求是两个内容: 当前命令操作栈所有命令维护数组

操作栈

主要负责用户输入命令的添加, 撤回, 但其实上内容就是一个普通的范型栈, 低能lua依然不支持, 当然他会比普通的栈多两个方法, 撤销当前命令撤销当前栈命令, 两者有个遍历关系, 当用户确认操作无误后将当前命令操作栈记录到栈数组中

命令维护数组

该数组负责所有命令相关的撤销与确认, 同时客户端上需要负责diff, 并将结果同步.

这里主要聊聊diff操作:

主要分为三个部分, 创建、更新、删除

创建主要跟Create命令强相关

删除主要跟Delete命令强相关, 此时需要注意, 如果是新创建的需要从创建部分删去, 否则从更新部分删除并添加至删除部分

更新就是其他普遍命令了

其他形式

更新方式其实有很多样的结果, 比如每次行程快照进行比对, 甚至可以把相关命令,传上去都是可以操作的选择

尾语

这部分就是命令模式的大体内容了, 因为这个模式的思路会比代码更重要, 所以其实笔者并没有针对某个语言去详细展开, 所以也就是没有直接上相关代码, 万变不离其宗, 望大家有所收获, 下一篇是有关状态模式的内容敬请期待.