Screeps 設計數(shù)量控制系統(tǒng)

screeps 系列教程

為了不讓自己下線時出現(xiàn) creep 都涼了的情況键袱,你的代碼里或多或少都有一個用于控制他們數(shù)量的模塊。在教程中摹闽,官方給出了一個簡單有效的方法:檢查每個角色的數(shù)量蹄咖,當數(shù)量低于指定數(shù)量時就進行生成。而本文主要講述的內(nèi)容付鹿,就是介紹數(shù)量控制系統(tǒng)的設計思路比藻。如果有不同的意見和看法歡迎評論區(qū)留言探討~

系統(tǒng)組成

首先,讓我們先來了解下一個完整的數(shù)量控制系統(tǒng)的組成部分:

  • 數(shù)量檢查邏輯:這一部分負責發(fā)現(xiàn)需要進行生成的 creep倘屹,并把要生成的 creep 的信息(如名稱银亲、內(nèi)存、身體部分)提交給 spawn纽匙。
  • spawn 生成邏輯:這一部分負責接收數(shù)量檢查邏輯傳過來的任務务蝠,并用其生成 creep。
  • 期望數(shù)量配置:這部分將給數(shù)量檢查邏輯提供指定 creep 期望的數(shù)量烛缔,可以手動維護馏段,也可以通過分析當前殖民地狀態(tài)自動修改轩拨。
數(shù)量控制系統(tǒng)的組成

這三個模塊共同構成了一個數(shù)量控制系統(tǒng),缺一不可院喜。接下來亡蓉,我將對這三部分進行分別介紹:

數(shù)量檢查:集中式還是分布式?

在教程的有這么一行代碼:

var harvesters = _.filter(Game.creeps, creep => creep.memory.role == 'harvester');

這行代碼統(tǒng)計了采集者harvester的數(shù)量喷舀,并在后面進行對比砍濒。這就是一個典型的集中式數(shù)量檢查,簡單明了硫麻。集中式的優(yōu)點就是非嘲中希可靠,通過實時的監(jiān)控拿愧,每一個角色的數(shù)量永遠都是已知的杠河。無論因為什么原因導致的 creep 死亡,數(shù)量控制系統(tǒng)都可以立刻得知浇辜,并重新安排生成券敌。

集中式檢查

集中式數(shù)量檢查的缺點

但是這種可靠性的代價就是代碼復雜度的提高和 cpu 的消耗。在開發(fā)數(shù)量控制系統(tǒng)時有一個重要的前提:每個房間的運營 creep 數(shù)量都是不同的柳洋。也就是說待诅,我們要針對每個房間運行一次數(shù)量檢查邏輯,從而保證房間的正常運營膳灶。

而除此之外,有一些例如外礦采集者和對外作戰(zhàn)的 creep 的數(shù)量是不屬于某個具體的房間的立由,所以我們還需要針對這些 creep 做一次數(shù)量檢查轧钓。

越來越多的額外邏輯會讓你的代碼不再簡潔,慢慢的變成一座屎山锐膜,從而摧毀你的游戲體驗毕箍。

分布式設計 - creep 自檢查

那么與之對應,分布式的設計思想就是把數(shù)量檢查的邏輯分散到每個 creep 中:每個 creep 會定期檢查自己是否健康道盏,如果不健康的話就通知 spawn 進行生成而柑。從而“自發(fā)的”維持指定的數(shù)量。

Creep.prototype.work = function() {
    // ...

    // 如果 creep 還沒有發(fā)送重生信息的話荷逞,執(zhí)行健康檢查媒咳,保證只發(fā)送一次生成任務
    // 健康檢查不通過則向 spawnList 發(fā)送自己的生成任務
    if (!this.memory.hasSendRebirth) {
        const health = this.isHealthy()
        if (!health) {
            // 向指定 spawn 推送生成任務
            // ...
            this.memory.hasSendRebirth = true
        }
    }
}

// creep 監(jiān)控狀態(tài)檢查
Creep.prototype.isHealthy = function() {
    if (this.ticksToLive <= 10) return false
    else return true
}

分布式數(shù)量檢查的優(yōu)點

分布式數(shù)量檢查的優(yōu)點就在于結構簡單,因為每個 creep 都只關注自身种远,所以不會產(chǎn)生多房間的數(shù)量需要統(tǒng)計多次的問題涩澡。

并且,分布式檢查還有一個其他模式難以實現(xiàn)的優(yōu)點坠敷,可以自由控制發(fā)送重生任務的時機妙同,你可以由此實現(xiàn) creep 的無縫替換射富,例如計算從出生點到工作崗位的距離,然后算出到達崗位的時間粥帚,從而提前生成接班的 creep胰耗,避免空閑。

分布式檢查

分布式數(shù)量檢查的缺點

這種設計思路最大的缺點是 不夠可靠芒涡,為什么呢柴灯?假如有入侵者殺死了 creep ,或者有核彈直接殺死了所有的 creep 呢拖陆?如果還沒有發(fā)送任務就已經(jīng)死了弛槐,那么這個角色的數(shù)量就會 -1 并且不會回到正常的狀態(tài),因為可以發(fā)送生成任務的 creep 已經(jīng)不在了依啰。

所以說乎串,優(yōu)點也是缺點,問題的根本在于 很難決定什么時候應該發(fā)送生成任務速警。如果你使用這種方式來管理 creep 的數(shù)量叹誉,那你就要額外設計一個定時檢查任務,來確保沒有 creep 提前倒下闷旧。

最直接的方法 - 死亡檢查

那么有沒有一種簡單不燒腦长豁!也不燒 cpu 的方法來解決這個問題呢!答案是有的忙灼!并且這個設計方式甚至在教程中已經(jīng)給出來了匠襟。他就是通過檢查死去 creep 的記憶:

for(const name in Memory.creeps) {
    if(!Game.creeps[name]) {
        // 不再刪除了!
        // delete Memory.creeps[name]

        // 向 spawn 發(fā)送生成任務
        // ...
    }
}

當我們發(fā)現(xiàn)有 creep 死亡時该园,就可以根據(jù)其 memory 中的角色直接將其加入生成隊列酸舍,非常的簡單無腦。

creep 的同名問題

如果新生成的 creep 和原來的保持同名里初,那么它將 直接繼承原來的記憶啃勉。這在某種情況下是個壞處,例如這個 creep 需要在生成之后執(zhí)行一個準備階段双妨,但是它內(nèi)存中的一個字段表示它已經(jīng)完成了準備 (繼承了上個 creep 的記憶)淮阐。從而導致 creep 錯誤的跳過了準備階段。

雖然解決了 creep 的持續(xù)生成問題刁品,但是這種設計理念并不會檢查配置項的變動(剛才講的 creep 自檢查也不會 )泣特,也就是說新增的角色并不會自動生成。不過這種問題就屬于小問題了挑随,定時檢查配置項的變動群扶,并在變動時將新增的 creep 加入生成。這并不是什么難事不是么?

Spawn 生成邏輯

結束了數(shù)量檢查竞阐,spawn 生成邏輯就要簡單很多缴饭,可能細心的你已經(jīng)發(fā)現(xiàn)了,上文中所有提到新生成 creep 的時候都是用的 "向 Spawn 推送生成任務" 而非 "調(diào)用 Spawn 進行生成"骆莹。

為什么要這么說呢颗搂?首先強調(diào)一個結論:無論在什么情況下,都推薦 使用任務隊列進行 creep 生成幕垦。這樣寫可以將 spawn 的生成邏輯和其他邏輯解耦丢氢,方便維護。并且這種寫法足夠簡單先改,也足夠清晰疚察。

OK,來介紹一下流程:首先仇奶,其他想要進行 creep 生成的模塊不允許直接訪問Spawn.spawnCreep方法貌嫡,而是調(diào)用 加入生成隊列 的函數(shù)將 creep 生成任務追加到隊列的末尾。同時该溯,每個 tick 里 spawn 只需要檢查任務隊列就可以 了岛抄,如果有的話就從隊列中彈出第一個任務進行生成。下面是流程圖:

任務隊列流程

并且狈茉,游戲也非常貼心的在每個 spawn 上都分配了一塊內(nèi)存可以使用夫椭,我們的任務隊列就保存在這里。

接下來我們來實現(xiàn)一下氯庆,這里我們在Spawn原型上拓展三個方法蹭秋,一個是 spawn 的工作:檢查任務隊列,一個用來 讓其他模塊添加生成任務堤撵,還有一個 封裝 creep 生成的主要實現(xiàn)

// 檢查任務隊列
Spawn.prototype.work = function() { 
    // 代碼...
}

// 將生成任務推入隊列
Spawn.prototype.addTask = function(taskName) { 
    // 代碼...
}

// creep 生成主要實現(xiàn)
Spawn.prototype.mainSpawn = function(taskName) { 
    // 代碼...
}

首先是第一個拓展仁讨,work方法:首先進行檢查,在無法生成時直接跳過來節(jié)省 cpu粒督。然后嘗試進行生成陪竿,如果生成成功的話就將完成生成的任務從隊列中移除禽翼。

// 檢查任務隊列
Spawn.prototype.work = function() { 
    // 自己已經(jīng)在生成了 / 內(nèi)存里沒有生成隊列 / 生產(chǎn)隊列為空 就啥都不干
    if (this.spawning || !this.memory.spawnList || this.memory.spawnList.length == 0) return 
    // 進行生成
    const spawnSuccess = this.mainSpawn(this.memory.spawnList[0])
    // 生成成功后移除任務
    if (spawnSuccess) this.memory.spawnList.shift()
}

然后是添加生成任務的方法屠橄,addTask:這個方法將任務的名稱追加到任務隊列的末尾,然后返回它的排隊位置:

// 將生成任務推入隊列
Spawn.prototype.addTask = function(taskName) { 
    // 任務加入隊列
    this.memory.spawnList.push(taskName)
    return this.memory.spawnList.length
}

這樣闰挡,其他模塊想要生成 creep 就只需要調(diào)用這個方法锐墙,然后傳入期望要生成 creep 的任務名稱即可,極大的減輕了和其他模塊的耦合度长酗。

最后一個拓展方法mainSpawn溪北,為了讓 spawn 的work()邏輯保持清晰,我更傾向于將其封裝成一個獨立的方法。這個方法包含了 creep 生成的核心實現(xiàn)之拨,例如 通過任務名稱taskName獲取任務的具體配置項茉继、內(nèi)存初始化、指定身體部件等蚀乔。因為 creep 生成實現(xiàn)的方法每個人的區(qū)別都比較大烁竭,所以這里不進行過多介紹,具體生成實現(xiàn)請結合自己的代碼進行修改吉挣。唯一一點需要注意的就是 在生成成功是返回true派撕,work方法將根據(jù)這個返回值決定是不是要移除已經(jīng)嘗試過生成的任務。

拓展:為什么要使用生成隊列睬魂?

假如我們有一個角色數(shù)量配置列表终吼,在進行生成檢查時,代碼會 按照這個列表的順序依次檢查氯哮,那么無論什么情況下际跪,在列表上方的角色都會被優(yōu)先生成。如果配置列表下方有一個重要角色A蛙粘,那么它上面的所有角色垫卤、無論重要不重要,都會先生成完出牧,才會輪到A穴肘。在很多情況下,這會導致導致房間運營因為某個重要角色的缺失從而陷入癱瘓舔痕。

如果你采用的是 creep 自檢查的話评抚,為了避免重復生成,一個 creep 一生只會發(fā)送一次重生任務伯复,假如 spawn 正在生成沒辦法立刻響應這個重生任務慨代,同時自身也沒有任務隊列保存這個任務,那么這個 creep 的任務就會被遺棄掉啸如,從而導致問題的產(chǎn)生侍匙。

接下來,我們來講一下最容易被忽視的一點:期望數(shù)量配置叮雳。

期望數(shù)量配置

在角色數(shù)量逐漸增多的時候想暗,很多人都會選擇進行第一次重構 —— 將所有角色數(shù)量及其配置放置在一個列表中來方便維護,如下:

const creepConfigs = [
    {
        role: 'harvester',
        bodys: [ WORK, CARRY, MOVE ],
        number: 1
    }, {
        role: 'upgrader',
        bodys: [ WORK, CARRY, MOVE ],
        number: 1
    },
    // 更多角色 ...
]

這是很重要的一部分帘不,可以極大的減輕你代碼的復雜程度说莫,并在某些時刻節(jié)省 cpu 消耗,例如我們在上一小節(jié)中介紹的 Spawn 生成邏輯 中寞焙,并沒有直接將 creep 的詳細生成信息一股腦的塞進生成隊列中储狭,而是將 任務名稱 放進隊列互婿,這里的任務名稱就可以是上面列表中的role字段。然后在mainSpawn中通過這個任務名稱來取出對應的配置項辽狈。

這么做有什么好處呢慈参,首先,保存在內(nèi)存中的數(shù)據(jù)每 tick 都要經(jīng)過JSON.stringify的序列化刮萌,所以 內(nèi)存中的數(shù)據(jù)越簡單懂牧,所消耗的 cpu 也就越少。其次尊勿,這么做可以提高系統(tǒng)的響應效率僧凤,無論我們對其配置項進行什么修改,spawn 通過任務名稱取到的永遠是最新的配置元扔。如果 spawn 隊列里保存了整個任務的話躯保,就有可能導致保存了舊版本的角色配置,從而生成出一個不符合預期的 creep 來澎语。

動態(tài)調(diào)控期望

當我們將配置項抽象成一個獨立的列表后途事,就可以采取一些更”智能“的方式來管理它了。例如擅羞,我們可以額外設計一個模塊尸变,這個模塊會監(jiān)測房間的狀態(tài),從而調(diào)整某個角色的數(shù)量或者身體部件:

自動調(diào)控數(shù)量

借此减俏,我們就可以完成房間的自動化運維召烂,從而在不影響代碼可讀性的前提下節(jié)省我們?nèi)肆S護成本。

不過需要注意的是娃承,如果你覺得自己對于游戲的了解還不夠深入的話奏夫,那么我是 不推薦在自己的代碼里加入這個自動數(shù)量維護的,因為你可以能會花不少的時間來設計這個模塊历筝,但最后因為經(jīng)驗不足導致需要大量重構酗昼,從而影響游戲體驗。至少在房間 8 級之前梳猪,你可以先手工維護來積累經(jīng)驗麻削。

設計小 tips

文章的最后,我們簡單的提幾點在設計上述系統(tǒng)時應該注意的幾點要素春弥。

  • 支持多房間
    首先呛哟,這個系統(tǒng)要支持多房間,因為我們?nèi)粘9芾砜隙ㄊ且苑块g為單位的惕稻。每個房間的數(shù)量配置都是不一樣的竖共。

  • 獨立的配置文件
    其次蝙叛,代碼應該和配置文件解耦俺祠,將配置項單獨放在一個文件中,因為隨著基地的不斷發(fā)展,我們的creep的數(shù)量蜘渣、身體的組成部分都是會頻繁變動的淌铐,沒有人會希望在修改這些配置時還要改一堆的代碼。

  • Spawn兼容
    在教程中蔫缸,只使用了一個出生點腿准,但是在實際發(fā)展的時候,我們每個房間都可以擁有多個Spawn的拾碌,所以在開發(fā)時我們也要考慮到這一點吐葱。例如,你可以不用 Spawn 自帶的內(nèi)存空間校翔,而是把孵化隊列存放到房間內(nèi)存中弟跑,這樣就可以多個 Spawn 共同處理孵化任務。

總結

本文我們將 creep 的數(shù)量控制系統(tǒng)劃分為三個模塊防症,數(shù)量檢查孟辑、spawn 生成 以及 期望數(shù)量配置。并簡單介紹了這三個模塊的設計思路以及彼此之間的關系蔫敲。當然饲嗽,本文所講的并不一定是最好的設計模式,如果你有更好的想法的話奈嘿,歡迎評論或者私信交流 ~

想要查看更多教程貌虾?歡迎訪問 《Screeps 中文教程》

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末裙犹,一起剝皮案震驚了整個濱河市酝惧,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌伯诬,老刑警劉巖晚唇,帶你破解...
    沈念sama閱讀 216,997評論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異盗似,居然都是意外死亡哩陕,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,603評論 3 392
  • 文/潘曉璐 我一進店門赫舒,熙熙樓的掌柜王于貴愁眉苦臉地迎上來悍及,“玉大人,你說我怎么就攤上這事接癌⌒母希” “怎么了?”我有些...
    開封第一講書人閱讀 163,359評論 0 353
  • 文/不壞的土叔 我叫張陵缺猛,是天一觀的道長缨叫。 經(jīng)常有香客問我椭符,道長,這世上最難降的妖魔是什么耻姥? 我笑而不...
    開封第一講書人閱讀 58,309評論 1 292
  • 正文 為了忘掉前任销钝,我火速辦了婚禮,結果婚禮上琐簇,老公的妹妹穿的比我還像新娘蒸健。我一直安慰自己,他們只是感情好婉商,可當我...
    茶點故事閱讀 67,346評論 6 390
  • 文/花漫 我一把揭開白布似忧。 她就那樣靜靜地躺著,像睡著了一般丈秩。 火紅的嫁衣襯著肌膚如雪橡娄。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,258評論 1 300
  • 那天癣籽,我揣著相機與錄音挽唉,去河邊找鬼。 笑死筷狼,一個胖子當著我的面吹牛瓶籽,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播埂材,決...
    沈念sama閱讀 40,122評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼塑顺,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了俏险?” 一聲冷哼從身側響起严拒,我...
    開封第一講書人閱讀 38,970評論 0 275
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎竖独,沒想到半個月后裤唠,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,403評論 1 313
  • 正文 獨居荒郊野嶺守林人離奇死亡莹痢,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,596評論 3 334
  • 正文 我和宋清朗相戀三年种蘸,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片竞膳。...
    茶點故事閱讀 39,769評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡航瞭,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出坦辟,到底是詐尸還是另有隱情刊侯,我是刑警寧澤,帶...
    沈念sama閱讀 35,464評論 5 344
  • 正文 年R本政府宣布锉走,位于F島的核電站滨彻,受9級特大地震影響藕届,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜疮绷,卻給世界環(huán)境...
    茶點故事閱讀 41,075評論 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望嚣潜。 院中可真熱鬧冬骚,春花似錦、人聲如沸懂算。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,705評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽计技。三九已至喜德,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間垮媒,已是汗流浹背舍悯。 一陣腳步聲響...
    開封第一講書人閱讀 32,848評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留睡雇,地道東北人萌衬。 一個月前我還...
    沈念sama閱讀 47,831評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像它抱,于是被迫代替她去往敵國和親秕豫。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,678評論 2 354

推薦閱讀更多精彩內(nèi)容

  • 簡介 在設計自己的角色系統(tǒng)的時候观蓄,很多人都會被角色越來越多的問題所困擾混移,本文不討論如何去削減角色的數(shù)量,而是從“發(fā)...
    HoPGoldy閱讀 9,893評論 7 21
  • 簡介 作為新手玩家在游戲進程中遇到的第一個”BOSS“侮穿,很多人會對如何拓展自己的疆域感到無從下手歌径,那么本文就簡單介...
    HoPGoldy閱讀 10,790評論 4 12
  • feisky云計算、虛擬化與Linux技術筆記posts - 1014, comments - 298, trac...
    不排版閱讀 3,847評論 0 5
  • Swift1> Swift和OC的區(qū)別1.1> Swift沒有地址/指針的概念1.2> 泛型1.3> 類型嚴謹 對...
    cosWriter閱讀 11,098評論 1 32
  • 離開顧村亲茅,賣了天極盛宅花園后沮脖,就再也沒回來過,路過都不曾有芯急,今天坐地鐵勺届,遠遠的望了一眼,經(jīng)年往事就像窗外的風景娶耍,一...
    煙雨樓燕子閱讀 357評論 0 1