美文网首页Screeps 游戏指南
Screeps 浅谈游戏中的原型拓展

Screeps 浅谈游戏中的原型拓展

作者: HoPGoldy | 来源:发表于2019-09-23 23:06 被阅读0次
    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 文集》

    相关文章

      网友评论

        本文标题:Screeps 浅谈游戏中的原型拓展

        本文链接:https://www.haomeiwen.com/subject/kdkmuctx.html