Screeps 淺談?dòng)螒蛑械脑屯卣?/h2>
screeps 系列教程

簡(jiǎn)介

在游戲的教程中,我們了解到可以通過游戲給定的基本 api 如Game.creeps Game.spawns等獲取游戲?qū)ο?,然后設(shè)計(jì)一層層的封裝,并將這些游戲?qū)ο笞鳛閰?shù)傳遞給封裝好的函數(shù),進(jìn)而完成我們的邏輯。

但是這么做有一點(diǎn)缺陷,假如我們代碼封裝做的比較多的話,就會(huì)出現(xiàn)游戲?qū)ο蟊灰粚訉拥膫鬟f下去,如下:

// 獲取游戲?qū)ο?const creep = Game.creeps['creep1']

// 傳遞給任務(wù)分發(fā)函數(shù)
work(creep)

// work 函數(shù)里傳遞給指定的角色函數(shù)
upgrader(creep) 

// upgrader 函數(shù)里再傳遞給狀態(tài)更新函數(shù)
updateState(creep)

…

可以看到 creep 對(duì)象被一步步的傳遞給不同的函數(shù),這樣的設(shè)計(jì)會(huì)增加參數(shù)的個(gè)數(shù),不方便理解,進(jìn)而增加代碼的維護(hù)成本。

那么有沒有一種更簡(jiǎn)潔的方式來(lái)避免這種設(shè)計(jì)呢,答案就是本文要講的 游戲原型拓展。

什么是原型?

js 中的繼承是通過原型鏈實(shí)現(xiàn)的,每一個(gè)對(duì)象都有一個(gè)鏈接指向了另一個(gè)對(duì)象,被指向的對(duì)象就稱為前者的原型

比如 screeps 中的每一個(gè)creep的原型都是Creep,注意區(qū)分這兩者的大小寫,我們常用的.moveTo()、.harvest()就是在Creep原型上定義的。

而如果我們嘗試調(diào)用某個(gè)對(duì)象上不存在的屬性時(shí),它就會(huì)去它的原型上去查找,如果原型對(duì)象上有的話,就會(huì)執(zhí)行原型上的方法。這也就是我們可以進(jìn)行原型拓展的根本。

簡(jiǎn)單的例子

剛才講了一大堆概念,可能對(duì)沒有系統(tǒng)學(xué)過 js 的同學(xué)有點(diǎn)難以理解。接下來(lái)就舉一個(gè)簡(jiǎn)單的例子。我們?cè)谟螒虼a入口處進(jìn)行如下定義:

module.exports.loop = function () {
    Creep.prototype.sayHello = function () {
        console.log('hello world!')
    }
}

然后隨便抓個(gè)正在干活的creep執(zhí)行下面代碼:

Game.creeps['creep name'].sayHello()

然后你就會(huì)發(fā)現(xiàn)控制臺(tái)輸出了hello world!。完美!我們成功的修改了Creep,讓每一個(gè)creep都獲得了sayHello方法。接下來(lái)我們講解一下上面的例子:

我們?cè)?code>Creep.prototype上定義了sayHello,并把一個(gè)函數(shù)賦值給它。這里的Creep.prototype就是Creep原型對(duì)象,當(dāng)creep上找不到sayHello方法的時(shí)候就會(huì)去prototype上尋找,正好就找到了我們剛才定義的方法。

很好,我們來(lái)更近一步,把上面的代碼替換如下:

Creep.prototype.sayHello = function () {
    this.say(`我的名字是${this.name}`)
}

然后再找一個(gè)creep執(zhí)行sayHello方法,就會(huì)出現(xiàn)如下情況:

image.png

creep他自己說話了!沒錯(cuò),在原型對(duì)象上定義的方法中,你可以使用this訪問到其他所有的屬性!這么一來(lái),我們就可以將常用的方法掛載到creep來(lái)簡(jiǎn)化我們的代碼結(jié)構(gòu),比如下面這樣:

// 建設(shè)房間內(nèi)的建筑工地
Creep.prototype.buildStructure = function () {
    const targets = this.room.find(FIND_CONSTRUCTION_SITES)
    // 找到就去建造
    if (targets.length > 0) {
        if(this.build(targets[0]) == ERR_NOT_IN_RANGE) {
            this.moveTo(targets[0])
        }
    }   
}

這樣我們只需要執(zhí)行creep.buildStructure()就可以讓creep自己跑到建筑工地然后干活了。至于原型拓展的用處還有很多,這里不再深入,有興趣的可以參考官方的這篇文檔 《screeps modifying-prototypes》 來(lái)了解更多用法。

接下來(lái),我們講一下如何優(yōu)雅清晰的規(guī)?;卣乖?。

更好的代碼結(jié)構(gòu)

和其他的邏輯代碼一樣,我們總不能把所有的原型拓展代碼都寫到主入口module.exports.loop里吧,接下來(lái)就分享一種比較清晰的代碼結(jié)構(gòu)。當(dāng)然,如果你有自己的想法的話大可不必按照我的來(lái)做。

首先假設(shè)我們想拓展Creep原型,那么可以新建一個(gè)名為mount.creep.js的文件,其內(nèi)容如下:

// 將拓展簽入 Creep 原型
module.exports = function () {
    _.assign(Creep.prototype, creepExtension)
}

// 自定義的 Creep 的拓展
const creepExtension = {
    // 自定義敵人檢測(cè)
    checkEnemy() { 
        // 代碼實(shí)現(xiàn)...
    },
    // 填充所有 spawn 和 extension
    fillSpawnEngry() { 
        // 代碼實(shí)現(xiàn)...
    },
    // 填充所有 tower
    fillTower() {
        // 代碼實(shí)現(xiàn)...
    },
    // 其他更多自定義拓展
}

我們將所有的自定義屬性都存放到creepExtension對(duì)象中,然后使用lodashassign方法將其一次性全部簽入到Creep原型中。

然后我們?cè)傩陆?code>mount.js文件,這個(gè)文件引入mount.creep.js并將封裝成一個(gè)單獨(dú)的入口函數(shù)。這樣,如果有新的拓展,也可以統(tǒng)一掛載到這層封裝中:

const mountCreep = require('./mount.creep')
// const mountFlag = require('./mount.flag')
// const mountRoom = require('./mount.room')

// 掛載所有的額外屬性和方法
module.exports = function () {
    console.log('[mount] 重新掛載拓展')

    mountCreep()
    // mountFlag()
    // mountRoom()
    // 其他更多拓展...
}

最后,我們?cè)?main.js 中引入 mount.js 并執(zhí)行就好了。注意,這里的方法調(diào)用應(yīng)該寫在 loop 之外,這樣只會(huì)在全局重置(我們的拓展就是這時(shí)被清空的 )時(shí)重新掛載,這樣就不用每個(gè) tick 都執(zhí)行從而帶來(lái)不必要的消耗:

require('./mount')()

module.exports.loop = function () {
    // 其他邏輯代碼
}

這里放一張拓展掛載的流程圖來(lái)方便理解。

拓展掛載流程圖

總結(jié)

本文簡(jiǎn)單介紹了 screeps 中如何拓展原型,只需要在對(duì)應(yīng)原型的prototype屬性上添加新的屬性即可,添加完成后所有由該原型派生出的對(duì)象都可以調(diào)用該屬性,并且在 screeps - api 中提到的原型都可以通過這種方式進(jìn)行拓展。

最后我們介紹了一種封裝拓展的代碼結(jié)構(gòu),做到了拓展的統(tǒng)一掛載,并且方便了日后的維護(hù)。想要了解更多內(nèi)容歡迎訪問 《Screeps 文集》!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容