简介
作为一款代码主导的游戏,有代码的地方就有设计模式。本文就作为一篇拓展阅读性质的文章,来简单介绍一下 Screeps 中出现的两大设计模式:角色驱动型 和 任务驱动型。
角色驱动型
角色驱动型设计模式是指 将 creep 划分成不同的角色,每个角色都有着固定的任务和行动模式,不同角色之间的相互协作共同维持了一个房间乃至整个殖民地的正常运行。可以非常肯定的说,几乎所有的新玩家的代码都使用了角色驱动型的设计模式。
角色驱动的优点
角色驱动型设计容易入门、方便调试,每个 creep 的工作非常的明确,所以在代码出现问题时你可以很容易的根据报错 creep 的角色定位到出现问题的代码。
并且由于代码结构简单,角色驱动在让 creep 执行一成不变的简单逻辑时产生的基础 CPU 消耗会更少。
角色驱动的缺点
这种设计模式最大的问题在于,随着游戏进度的不断深入,就需要设计越来越多的角色来应对新的任务。而且如果你的代码抽象没有做好的话,每次新增角色都将是一次痛苦的体验。
并且,角色型设计更适合于“静态”的工作逻辑。比如 "采矿 > 建造" 或者 "采矿 > 升级"。面对复杂、多变、多目标的工作逻辑,例如后期的资源搬运任务时,角色驱动就显得力不从心了。为了处理复杂的工作任务,你可能会选择将不同的任务按照优先级“分层”:
非常的复杂,并且在每个任务之前都需要使用room.find
或者其他的方法判断是不是要执行该任务,要执行的任务优先级越靠后,就会导致在前面任务的判断消耗更多的cpu
。
并且这个任务列表越长,其中的优先级就越难以进行排序。例如在房间内出现敌人时要优先满足 Tower 的能量需求,而在 Lab 合成所需的原料来自于 Termial 时,就需要先从 Termial 中获取资源再转移至 Lab。为了适配这种“突发性”的优先级调整,会让你的代码逻辑越来越混乱,同时增加了出现 bug 的几率。
在接触到新的游戏内容后,你会非常纠结是在现有的角色上添加新的任务还是新增一个角色。
任务驱动型
在实际开发过程中,我们很容易就能发现:如果按照身体组成进行划分的话,日常基地维护的 creep 仅仅只需要分为如下两种:
- 以
WORK
为主的 工作型 creep - 以
CARRY
、MOVE
为主的 运输型 creep
任务驱动型设计模式是 将所有的行动内容划分成不同的任务,每个任务都有自己的执行方法和要达成的目标,而 creep 只划分为几个基本的角色,每个 creep 都将在一组规则的指导下领取适合自己的任务并执行。
这个设计模式的核心是 每个 creep 的工作内容并不是一成不变的。在当前的任务完成后完全可以切换至其他的任务。
在角色驱动型设计中,任务的发现和执行完全是由 creep 负责的,而在任务驱动型设计中,任务的发现和执行完全被拆开了(没有人不喜欢低耦合)。creep 接受任务,然后执行任务。而任务的发布完全由其他代码完成。在大多数情况下,每个建筑都会根据自身的情况决定是否发布一个任务。
例如,Tower 会检查自己的能量是不是没有满,以此来决定是否发布一个从 Storage 到自己的能量转移任务。而 Termial 会根据自己是否收到了一笔新的资源,来决定是否要发布一个从自己到 Storage 的资源转移任务。
这些任务都会被发布到一个固定的位置 (任务队列,或是任务池)。这样 creep 完全不用关心任务是怎么产生的,只需要埋头干活就好了。
优点
由于 creep 会只有俩个基本角色(工作型 / 运输型),而且同一时刻一个 creep 只会明确的做一件事。所以角色驱动型的缺点 角色过多 和 任务优先级混乱 在任务驱动这里完全不成问题。
而且由于任务发布逻辑和执行逻辑的解耦,代码的结构会变得更加清晰,从而方便后期的维护工作。并且因为 creep 只监听任务队列而不会主动去发现任务,所以在空闲时会带来更低的 CPU 消耗。
回想一下角色驱动,在空闲时会根据优先级执行所有任务的检查工作,从而造成了 CPU 的浪费。
缺点
任务驱动什么都好,唯一不好的就是:开发难度大。是的,对于任务驱动型设计来说,角色驱动设计里数量众多的角色并不是被消灭了,而是转化成了体量更小,更加灵活的“任务”,你依旧需要设计每个任务的逻辑。
并且不同于角色驱动型里简单编写代码就可以让 creep 进行工作。任务驱动型需要扎实健壮的“基础设施建设”,比如任务发布逻辑、任务队列的维护和检查、任务分配调度、兼容所有任务的 creep 运行框架 等。在这一整套代码完成之前,你甚至没办法完成一个简单的 creep 采矿然后升级控制器的工作流程。
并且,由于 creep 当前的工作完全取决于自己接到了什么任务。所以一旦出现 bug。你可能很难在后续检查中重现这个问题。你可能会陷入“这个 creep 刚才一直好好的,突然就报了个错,然后又正常运行了” 的问题循环中,想要追踪、重现和进行测试,你需要额外设计一些平时用不到,但是方便检查的模块。这无疑加大了开发成本。
二者的边界
在实际的开发游玩中,角色驱动型设计和任务驱动型设计并不是二元对立的。在很多时候,你完全可以将二者融合在一个项目里。例如,整体上使用角色驱动,而在“物流运输”等难以处理的地方使用任务驱动来减轻设计和维护上的压力。
并且需要着重强调的是,不要一昧的推崇任务驱动,不要觉得任务驱动很优雅就尝试把所有功能都并入到任务系统里。
想要合并所有功能,你需要从种类繁杂的任务中抽象出统一的模型,这对你的游戏理解和编程能力是一个极大的考验(事实上,对游戏理解的考验更大,因为编程能力强的玩家更容易在一开始就进行这种超重量级的开发,最后很多都因游戏理解不足而铩羽 )。并且,复杂的框架会加重心智负担和维护成本,让你把更多的时间用在修修补补而不是新功能开发上。请记住下面两条基本原则,这适用在任何需要编程的项目里:
- 越简单、越稳定、越美好(KISS)
- 为了炫技的代码是坏代码,能实现功能的代码才是好代码
写在最后
本文简单介绍了一下游戏中出现的比较重要的设计模式和开发思路,希望能为你在平时的开发里增添一些灵感,当然游戏还是开心最重要,如果你认为本文不太适合你的代码设计,那按照自己的想法写就完事了!想要了解更多其他 screeps 小知识?欢迎点击 Screep 中文教程 !