代碼優(yōu)化可以說是每一個在 shard3 生活的玩家都要經歷的养盗。這篇文章我們就來分析一下 cpu 消耗的來源和如何進行優(yōu)化。
CPU 消耗的來源
在游戲中届腐,cpu 消耗主要有三個大頭铁坎,分別是:搜索消耗、尋路消耗和固定消耗犁苏。在介紹之前硬萍,我們先來看一下如何在 api 文檔中查看各個接口的性能消耗:以 StructureLab 的文檔為例,下圖中圈出的 藍色方框條目 都是可執(zhí)行的方法(空心的代表繼承自其它原型)围详,這些方法都是有消耗的朴乖。
如下,我們可以在 api 介紹的右上角找到他的性能消耗短曾,該方法的消耗越大,它的顏色就越重赐劣、橫杠就越多嫉拐。而擁有 A 標志的方法則代表該方法的消耗是固定 0.2 CPU + 代碼執(zhí)行消耗。也就是說魁兼,只要該方法執(zhí)行成功(返回 OK)婉徘,那么它一定會吃掉 0.2 CPU漠嵌。
所以,在調用某個方法前盖呼,應先檢查它的消耗儒鹿,并由此決定它在你代碼中的調用權重。接下來几晤,我們分別講一下剛才提到的三大消耗:
搜索消耗
搜索消耗的典型例子就是 Room.find
和 RoomPosition.findInRange
方法约炎。這兩個方法需要先遍歷房間中的所有對象,篩選出所有符合 FIND_*
常量的同類型對象蟹瘾,然后再遍歷執(zhí)行 filter 來找到最終目標圾浅。由于教程的原因,在新手的代碼中經常會出現(xiàn) Room.find
從而導致消耗掉了大量 cpu憾朴。
內置的搜索緩存
為了節(jié)省消耗狸捕,這種搜索類方法都擁有一個內置緩存,游戲會將對應的 FIND_*
搜索結果緩存下來众雷,也就是說灸拍,在同一 tick 中,如果你執(zhí)行了兩遍 Room.find(FIND_CREEP)
方法砾省,第二次搜索就會自動應用緩存從而節(jié)省消耗鸡岗。
注意,這個緩存會在 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()
大體瞅一眼就可以發(fā)現(xiàn)茫经,這幾個方法基本都是非常常用的巷波,哦真糟糕萎津。像是 Creep.moveTo
方法我又不得不用。是的抹镊,如何避免尋路消耗是 Screeps 中的一大重要研究課題★鼻現(xiàn)在常見的方法是將常用的路徑緩存來減少尋路次數(shù)。而對于新手來說垮耳,這里還有種更簡單的方法來節(jié)省尋路消耗:
內置的尋路緩存
Creep.moveTo()
同樣包含一個內置緩存颈渊,如下:
你可以通過簡單的提升該值的大小來節(jié)省 cpu 消耗,但是要注意:這個值越高氨菇,你的 creep 反應也就越遲鈍儡炼。如果它之前緩存的路徑上有個無法越過的障礙物,它就會一直卡在那里直到緩存時間結束查蓉。
這里要重點提一下RoomPosition.findClosestByPath
方法:
該方法會造成大量的遍歷運算乌询,所以在代碼編寫中你應該少用這種方法,如果要用的話豌研,也請根據(jù)情況指定默認算法或者對其結果進行緩存來減少搜索次數(shù)妹田。
固定消耗
這種消耗幾乎存在游戲世界中的各個角落,它有一個特點:會對游戲世界產生影響鹃共,例如Creep.withdraw
和Creep.transfer
鬼佣,他們會將資源移動到其他位置(產生了影響),所以說這兩種方法就會包含固定消耗霜浴,除此之外還有諸如Creep.harvest
晶衷、Tower.attack
等等很多很多。
這個消耗是游戲制訂的規(guī)則阴孟,所以說幾乎無法避免晌纫。這個消耗會隨著你的 creep 和建筑數(shù)量的增多而增多,并構成了你每 tick 的基礎消耗永丝。雖然無法避免锹漱,但是我們可以通過提高其每次執(zhí)行的效率來節(jié)省 cpu。如何節(jié)省我們下文再提慕嚷。
代碼執(zhí)行成本
除了上面三個消耗大頭外哥牍,執(zhí)行代碼也會產生一定的消耗,這個幾乎是無法避免的喝检,想要節(jié)省此類消耗需要你對 js 有更深層的了解嗅辣,并且由于這種消耗比較零碎,所以并不推薦刻意的對其進行優(yōu)化挠说,上面三種消耗哪怕你能節(jié)省任何一點澡谭,所帶來的收益都比優(yōu)化代碼執(zhí)行成本要大的多。
這里有一點需要注意的纺涤,游戲的 Memory 內存對象需要每 tick 調用JSON.stringify
和JSON.parse
進行解析和儲存译暂,內存越大消耗也就越大,所以請節(jié)約內存的使用撩炊。
緩存的種類
在介紹如何優(yōu)化之前外永,我們先來看一下游戲中的緩存種類,已經有很多類似的文章了拧咳,所以我們這里只簡單提一下:
-
持久化存儲:游戲的
Memory
對象伯顶,只有這個地方能實現(xiàn)真正可靠的長時間存儲。 -
半持久存儲:js 的
Global
對象骆膝,對象原型都屬于半持久存儲祭衩,這種存儲會在游戲全局重置時被清除,一般存放允許丟失的數(shù)據(jù)阅签。 -
非持久存儲:直接定義在游戲對象(非原型)上的屬性都屬于非持久存儲掐暮,例如
Game.rooms.W1N1.myCustomProp = 123
,這種存儲只有本 tick 能訪問到政钟,用來存放 tick 內協(xié)同作業(yè)需要的數(shù)據(jù)路克。
更詳細的分析見下文:
如何優(yōu)化 CPU 消耗
首先,請把 過早的優(yōu)化是萬惡的根源! 這句話重讀三遍并牢牢記住养交。在你的 cpu 消耗大于三分之二之前精算,不要刻意優(yōu)化;在你的房間升到 8 級之前碎连,不要考慮修改房間運營邏輯灰羽;在你的整體框架還可以應對現(xiàn)有需求時,不要考慮對代碼進行重構鱼辙。
在重構時因為突然出現(xiàn)的新需求導致需要重構現(xiàn)有的重構工作廉嚼、因為對游戲了解不夠全面導致重構后的框架變成了更大的屎山,這種糟糕的體驗會讓你不想再打開這個游戲座每,因此棄坑的玩家也大有人在前鹅。所以,請把上面那段文字重讀一遍后再繼續(xù)閱讀下面的內容峭梳。
ok舰绘,我們現(xiàn)在來講一下如何節(jié)省這些消耗。
搜索消耗
首先是搜索消耗葱椭,優(yōu)化搜索消耗主要靠 緩存結果和減少重復搜索 來完成捂寿。例如自己房間內的建筑,你可以在搜索之后將其 id 緩存在Memory
或者Global
下孵运,之后通過指定的方法或者屬性直接調用秦陋。如果你了解過原型拓展的話,你也可以新建類似 Room.sources
之類的屬性來更加方便的管理這些緩存治笨。這里 提到了如何創(chuàng)建這些緩存驳概。
而像同一 tick 內同個房間的多個 tower 都執(zhí)行了敵人搜索赤嚼,這種就屬于完全無意義的重復搜索,你可以通過在房間下掛載非持久緩存的形式解決這個問題:
// tower 將會執(zhí)行的方法
function towerWork(tower) {
// 如果之前沒有 tower 搜索過
if (!tower.room._hasRunTowerFind) {
const enemy = tower.room.find(FIND_HOSTILE_CREEPS)
// 處理邏輯 ...
// 后面的 tower 不再搜索
tower.room._hasRunTowerFind = true
}
}
你也可以把搜索結果掛在 Room 對象下來讓房間內的所有 tower 都可以獲取到目標顺又。
尋路消耗
減少尋路消耗的主要方法是 重用尋路結果來減少尋路次數(shù)更卒。例如從 Spawn 到 Source 的路線是固定的;從 Storage 到 Controller 的路線也是固定的稚照。那么就可以將這類路徑存儲到非持久緩存或者 Memory 中來讓 creep 可以一直按照固定路線移動而無需尋路蹂空。你可以使用Creep.moveByPath()
或者自行封裝Creep.move()
來實現(xiàn) creep 按照指定路線移動的邏輯。
這里提一個醒果录,如果你要把尋路結果保存在 Memory 中的話上枕,請先將搜索結果序列化成字符串的形式進行保存,這樣可以節(jié)省內存空間弱恒。而Room.serializePath
方法不支持壓縮PathFinder.search()
的搜索結果辨萍,所以如果你在用PathFinder
的話就可能需要手寫壓縮方法,或者你也可以直接將結果保存到非持久緩存中返弹。
固定消耗
由于固定消耗無法避免分瘦,所以我們要盡可能的提升每次執(zhí)行的效果。例如 Creep.harvest
方法琉苇,雖然一個 creep 只有 5 個WORK
就可以采干一個 Source嘲玫。但是我們依舊可以通過繼續(xù)提升WORK
的數(shù)量來減少Creep.harvest()
的執(zhí)行次數(shù),從而節(jié)省 cpu 消耗并扇。
同理去团,我們也可以通過增大CARRY
的數(shù)量來提升每次搬運的資源數(shù)量。提高HEAL
的數(shù)量來提升每次治療的效果等等穷蛹。這也是為什么官方更推薦用增加身體部件而不是 creep 數(shù)量的形式來提升效率土陪。
不止 creep,建筑也可以用這種方式來提升效率肴熏,例如Link
等到存儲滿了之后再發(fā)送鬼雀,Terminal
一次交易更多的數(shù)量。在日常開發(fā)時就要考慮到這個問題蛙吏。
好的開發(fā)習慣
這個涉及的范圍就比較大了源哩,大家了解一下就好,對于節(jié)省消耗來說:能重用的地方就不要再次新建變量鸦做、盡可能的少出現(xiàn)循環(huán)嵌套励烦。剩下的就不說太多了,畢竟大家是來玩游戲不是來上班的泼诱。如果確實有興趣的話坛掠,鏈接就在下面:
寫在最后
OK!本文簡單介紹了一下 Screeps 中幾個 cpu 消耗大頭以及如何優(yōu)化它們,并沒有出現(xiàn)多少代碼屉栓,畢竟每個人的架構不同舷蒲,強行宣傳某一種優(yōu)化方法也并不一定適用于所有玩家,還是那句話:過早重構友多、盲目重構只會讓你的游戲體驗更加糟糕阿纤。
了解更多 Screeps 的中文教程?歡迎訪問 Screeps - 中文系列教程夷陋!