Screeps 浅谈游戏中的原型拓展

screeps 系列教程

简介

在游戏的教程中,我们了解到可以通过游戏给定的基本 api 如Game.creeps Game.spawns等获取游戏对象,然后设计一层层的封装,并将这些游戏对象作为参数传递给封装好的函数,进而完成我们的逻辑。

但是这么做有一点缺陷,假如我们代码封装做的比较多的话,就会出现游戏对象被一层层的传递下去,如下:

// 获取游戏对象
const creep = Game.creeps['creep1']

// 传递给任务分发函数
work(creep)

// work 函数里传递给指定的角色函数
upgrader(creep) 

// upgrader 函数里再传递给状态更新函数
updateState(creep)

…

可以看到 creep 对象被一步步的传递给不同的函数,这样的设计会增加参数的个数,不方便理解,进而增加代码的维护成本。

那么有没有一种更简洁的方式来避免这种设计呢,答案就是本文要讲的 游戏原型拓展

什么是原型?

js 中的继承是通过原型链实现的,每一个对象都有一个链接指向了另一个对象,被指向的对象就称为前者的原型

比如 screeps 中的每一个creep的原型都是Creep注意区分这两者的大小写,我们常用的.moveTo().harvest()就是在Creep原型上定义的。

而如果我们尝试调用某个对象上不存在的属性时,它就会去它的原型上去查找,如果原型对象上有的话,就会执行原型上的方法。这也就是我们可以进行原型拓展的根本。

简单的例子

刚才讲了一大堆概念,可能对没有系统学过 js 的同学有点难以理解。接下来就举一个简单的例子。我们在游戏代码入口处进行如下定义:

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

然后随便抓个正在干活的creep执行下面代码:

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

然后你就会发现控制台输出了hello world!。完美!我们成功的修改了Creep,让每一个creep都获得了sayHello方法。接下来我们讲解一下上面的例子:

我们在Creep.prototype上定义了sayHello,并把一个函数赋值给它。这里的Creep.prototype就是Creep原型对象,当creep上找不到sayHello方法的时候就会去prototype上寻找,正好就找到了我们刚才定义的方法。

很好,我们来更近一步,把上面的代码替换如下:

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

然后再找一个creep执行sayHello方法,就会出现如下情况:

image.png

creep他自己说话了!没错,在原型对象上定义的方法中,你可以使用this访问到其他所有的属性!这么一来,我们就可以将常用的方法挂载到creep来简化我们的代码结构,比如下面这样:

// 建设房间内的建筑工地
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])
        }
    }   
}

这样我们只需要执行creep.buildStructure()就可以让creep自己跑到建筑工地然后干活了。至于原型拓展的用处还有很多,这里不再深入,有兴趣的可以参考官方的这篇文档 《screeps modifying-prototypes》 来了解更多用法。

接下来,我们讲一下如何优雅清晰的规模化拓展原型。

更好的代码结构

和其他的逻辑代码一样,我们总不能把所有的原型拓展代码都写到主入口module.exports.loop里吧,接下来就分享一种比较清晰的代码结构。当然,如果你有自己的想法的话大可不必按照我的来做。

首先假设我们想拓展Creep原型,那么可以新建一个名为mount.creep.js的文件,其内容如下:

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

// 自定义的 Creep 的拓展
const creepExtension = {
    // 自定义敌人检测
    checkEnemy() { 
        // 代码实现...
    },
    // 填充所有 spawn 和 extension
    fillSpawnEngry() { 
        // 代码实现...
    },
    // 填充所有 tower
    fillTower() {
        // 代码实现...
    },
    // 其他更多自定义拓展
}

我们将所有的自定义属性都存放到creepExtension对象中,然后使用lodashassign方法将其一次性全部签入到Creep原型中。

然后我们再新建mount.js文件,这个文件引入mount.creep.js并将封装成一个单独的入口函数。这样,如果有新的拓展,也可以统一挂载到这层封装中:

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

// 挂载所有的额外属性和方法
module.exports = function () {
    console.log('[mount] 重新挂载拓展')

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

最后,我们在 main.js 中引入 mount.js 并执行就好了。注意,这里的方法调用应该写在 loop 之外,这样只会在全局重置(我们的拓展就是这时被清空的 )时重新挂载,这样就不用每个 tick 都执行从而带来不必要的消耗:

require('./mount')()

module.exports.loop = function () {
    // 其他逻辑代码
}

这里放一张拓展挂载的流程图来方便理解。

拓展挂载流程图

总结

本文简单介绍了 screeps 中如何拓展原型,只需要在对应原型的prototype属性上添加新的属性即可,添加完成后所有由该原型派生出的对象都可以调用该属性,并且在 screeps - api 中提到的原型都可以通过这种方式进行拓展。

最后我们介绍了一种封装拓展的代码结构,做到了拓展的统一挂载,并且方便了日后的维护。想要了解更多内容欢迎访问 《Screeps 文集》

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

推荐阅读更多精彩内容