Screeps 浅谈代码优化

screeps 系列教程

代码优化可以说是每一个在 shard3 生活的玩家都要经历的。这篇文章我们就来分析一下 cpu 消耗的来源和如何进行优化。

CPU 消耗的来源

在游戏中,cpu 消耗主要有三个大头,分别是:搜索消耗、寻路消耗和固定消耗。在介绍之前,我们先来看一下如何在 api 文档中查看各个接口的性能消耗:以 StructureLab 的文档为例,下图中圈出的 蓝色方框条目 都是可执行的方法(空心的代表继承自其它原型),这些方法都是有消耗的。

该原型上的可执行方法

如下,我们可以在 api 介绍的右上角找到他的性能消耗,该方法的消耗越大,它的颜色就越重、横杠就越多。而拥有 A 标志的方法则代表该方法的消耗是固定 0.2 CPU + 代码执行消耗。也就是说,只要该方法执行成功(返回 OK),那么它一定会吃掉 0.2 CPU。

cpu消耗量
固定 cpu 消耗

所以,在调用某个方法前,应先检查它的消耗,并由此决定它在你代码中的调用权重。接下来,我们分别讲一下刚才提到的三大消耗:

搜索消耗

搜索消耗的典型例子就是 Room.findRoomPosition.findInRange方法。这两个方法需要先遍历房间中的所有对象,筛选出所有符合 FIND_* 常量的同类型对象,然后再遍历执行 filter 来找到最终目标。由于教程的原因,在新手的代码中经常会出现 Room.find 从而导致消耗掉了大量 cpu。

内置的搜索缓存

为了节省消耗,这种搜索类方法都拥有一个内置缓存,游戏会将对应的 FIND_* 搜索结果缓存下来,也就是说,在同一 tick 中,如果你执行了两遍 Room.find(FIND_CREEP) 方法,第二次搜索就会自动应用缓存从而节省消耗。

内置缓存

注意,这个缓存会在 tick 开始时清除。并且,这个缓存只会保存所有符合 FIND_* 常量的搜索结果,而你使用 filter 带来的 cpu 消耗是无法避免的,哪怕你两次搜索所用的 filter 完全相同。

在此之外还有一些方法也会产生搜索消耗,例如.look系列方法,这里不再赘述,在 api 文档中自行搜索即可。

除了房间对象搜索之外,Game.market.getAllOrders 也需要注意,由于需要检查市场上的所有订单,这个方法的 cpu 消耗是巨大的,不过注意它的介绍:“该方法支持resourceType内置索引”,也就是说,在 filter 里携带 resourceType 属性可以大大减少其消耗。

消耗直接拉满

寻路消耗

相对于搜索消耗,寻路带来的消耗要大的多,几个比较常见的会产生寻路消耗的方法如下:

  • Creep.moveTo()
  • Room.findPath()
  • RoomPosition.findClosestByPath()
  • RoomPosition.findPathTo()
  • Game.map.findRoute()
  • PathFinder.search()

大体瞅一眼就可以发现,这几个方法基本都是非常常用的,哦真糟糕。像是 Creep.moveTo 方法我又不得不用。是的,如何避免寻路消耗是 Screeps 中的一大重要研究课题。现在常见的方法是将常用的路径缓存来减少寻路次数。而对于新手来说,这里还有种更简单的方法来节省寻路消耗:

内置的寻路缓存

Creep.moveTo()同样包含一个内置缓存,如下:

你可以通过简单的提升该值的大小来节省 cpu 消耗,但是要注意:这个值越高,你的 creep 反应也就越迟钝。如果它之前缓存的路径上有个无法越过的障碍物,它就会一直卡在那里直到缓存时间结束。

这里要重点提一下RoomPosition.findClosestByPath方法:

该方法会造成大量的遍历运算,所以在代码编写中你应该少用这种方法,如果要用的话,也请根据情况指定默认算法或者对其结果进行缓存来减少搜索次数。

固定消耗

这种消耗几乎存在游戏世界中的各个角落,它有一个特点:会对游戏世界产生影响,例如Creep.withdrawCreep.transfer,他们会将资源移动到其他位置(产生了影响),所以说这两种方法就会包含固定消耗,除此之外还有诸如Creep.harvestTower.attack等等很多很多。

这个消耗是游戏制订的规则,所以说几乎无法避免。这个消耗会随着你的 creep 和建筑数量的增多而增多,并构成了你每 tick 的基础消耗。虽然无法避免,但是我们可以通过提高其每次执行的效率来节省 cpu。如何节省我们下文再提。

代码执行成本

除了上面三个消耗大头外,执行代码也会产生一定的消耗,这个几乎是无法避免的,想要节省此类消耗需要你对 js 有更深层的了解,并且由于这种消耗比较零碎,所以并不推荐刻意的对其进行优化,上面三种消耗哪怕你能节省任何一点,所带来的收益都比优化代码执行成本要大的多。

这里有一点需要注意的,游戏的 Memory 内存对象需要每 tick 调用JSON.stringifyJSON.parse进行解析和储存,内存越大消耗也就越大,所以请节约内存的使用。

缓存的种类

在介绍如何优化之前,我们先来看一下游戏中的缓存种类,已经有很多类似的文章了,所以我们这里只简单提一下:

  • 持久化存储:游戏的Memory对象,只有这个地方能实现真正可靠的长时间存储。
  • 半持久存储:js 的 Global对象,对象原型都属于半持久存储,这种存储会在游戏全局重置时被清除,一般存放允许丢失的数据。
  • 非持久存储:直接定义在游戏对象(非原型)上的属性都属于非持久存储,例如Game.rooms.W1N1.myCustomProp = 123,这种存储只有本 tick 能访问到,用来存放 tick 内协同作业需要的数据。

更详细的分析见下文:

如何优化 CPU 消耗

首先,请把 过早的优化是万恶的根源! 这句话重读三遍并牢牢记住。在你的 cpu 消耗大于三分之二之前,不要刻意优化;在你的房间升到 8 级之前,不要考虑修改房间运营逻辑;在你的整体框架还可以应对现有需求时,不要考虑对代码进行重构。

在重构时因为突然出现的新需求导致需要重构现有的重构工作、因为对游戏了解不够全面导致重构后的框架变成了更大的屎山,这种糟糕的体验会让你不想再打开这个游戏,因此弃坑的玩家也大有人在。所以,请把上面那段文字重读一遍后再继续阅读下面的内容。

ok,我们现在来讲一下如何节省这些消耗。

搜索消耗

首先是搜索消耗,优化搜索消耗主要靠 缓存结果和减少重复搜索 来完成。例如自己房间内的建筑,你可以在搜索之后将其 id 缓存在Memory或者Global下,之后通过指定的方法或者属性直接调用。如果你了解过原型拓展的话,你也可以新建类似 Room.sources 之类的属性来更加方便的管理这些缓存。这里 提到了如何创建这些缓存。

而像同一 tick 内同个房间的多个 tower 都执行了敌人搜索,这种就属于完全无意义的重复搜索,你可以通过在房间下挂载非持久缓存的形式解决这个问题:

// tower 将会执行的方法
function towerWork(tower) {
    // 如果之前没有 tower 搜索过
    if (!tower.room._hasRunTowerFind) {
        const enemy = tower.room.find(FIND_HOSTILE_CREEPS)
        // 处理逻辑 ...
        // 后面的 tower 不再搜索
        tower.room._hasRunTowerFind = true
    }
}

你也可以把搜索结果挂在 Room 对象下来让房间内的所有 tower 都可以获取到目标。

寻路消耗

减少寻路消耗的主要方法是 重用寻路结果来减少寻路次数。例如从 Spawn 到 Source 的路线是固定的;从 Storage 到 Controller 的路线也是固定的。那么就可以将这类路径存储到非持久缓存或者 Memory 中来让 creep 可以一直按照固定路线移动而无需寻路。你可以使用Creep.moveByPath()或者自行封装Creep.move()来实现 creep 按照指定路线移动的逻辑。

这里提一个醒,如果你要把寻路结果保存在 Memory 中的话,请先将搜索结果序列化成字符串的形式进行保存,这样可以节省内存空间。而Room.serializePath方法不支持压缩PathFinder.search()的搜索结果,所以如果你在用PathFinder的话就可能需要手写压缩方法,或者你也可以直接将结果保存到非持久缓存中。

固定消耗

由于固定消耗无法避免,所以我们要尽可能的提升每次执行的效果。例如 Creep.harvest方法,虽然一个 creep 只有 5 个WORK就可以采干一个 Source。但是我们依旧可以通过继续提升WORK的数量来减少Creep.harvest()的执行次数,从而节省 cpu 消耗。

同理,我们也可以通过增大CARRY的数量来提升每次搬运的资源数量。提高HEAL的数量来提升每次治疗的效果等等。这也是为什么官方更推荐用增加身体部件而不是 creep 数量的形式来提升效率。

提升身体部件数量而不是 creep 数量

不止 creep,建筑也可以用这种方式来提升效率,例如Link等到存储满了之后再发送,Terminal一次交易更多的数量。在日常开发时就要考虑到这个问题。

好的开发习惯

这个涉及的范围就比较大了,大家了解一下就好,对于节省消耗来说:能重用的地方就不要再次新建变量、尽可能的少出现循环嵌套。剩下的就不说太多了,毕竟大家是来玩游戏不是来上班的。如果确实有兴趣的话,链接就在下面:

写在最后

OK!本文简单介绍了一下 Screeps 中几个 cpu 消耗大头以及如何优化它们,并没有出现多少代码,毕竟每个人的架构不同,强行宣传某一种优化方法也并不一定适用于所有玩家,还是那句话:过早重构、盲目重构只会让你的游戏体验更加糟糕。

了解更多 Screeps 的中文教程?欢迎访问 Screeps - 中文系列教程

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,222评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,455评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,720评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,568评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,696评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,879评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,028评论 3 409
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,773评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,220评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,550评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,697评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,360评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,002评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,782评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,010评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,433评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,587评论 2 350

推荐阅读更多精彩内容

  • 作为一个多人在线沙盒游戏,和别的玩家发生冲突可以说是不可避免的事情。本文简单介绍一下在 Screeps 中的防御方...
    HoPGoldy阅读 5,522评论 0 7
  • 简介 作为新手玩家在游戏进程中遇到的第一个”BOSS“,很多人会对如何拓展自己的疆域感到无从下手,那么本文就简单介...
    HoPGoldy阅读 10,647评论 4 12
  • 为了不让自己下线时出现 creep 都凉了的情况,你的代码里或多或少都有一个用于控制他们数量的模块。在教程中,官方...
    HoPGoldy阅读 8,555评论 9 28
  • 简介 作为一款代码主导的游戏,有代码的地方就有设计模式。本文就作为一篇拓展阅读性质的文章,来简单介绍一下 Scre...
    HoPGoldy阅读 7,705评论 16 10
  • 简介 在游戏的教程中,我们了解到可以通过游戏给定的基本 api 如Game.creeps Game.spawns等...
    HoPGoldy阅读 8,317评论 26 24