Screeps 控制台结合可视化——优化手操体验

前言

在游玩Screeps中,不可避免的会需要手动调节参数、发起指令,传统的手操一般有以下方式

  1. 在控制台中直接修改 Memory: Memory.xxx.xxx = xxx
  2. 在控制台调用global上提前挂载好的方法: BuyOrder("orderid",10000)
  3. 插旗子、移动旗子
  4. 手动放置construction sites,摧毁建筑等

从笔者个人的实践出发,第一、二种方式的体验是比较糟糕的,在控制台敲代码既没有补全,也没有提示,参数也比较多。而且随着业务量的增大,挂载的方法也越来越多,如何统一管理这些方法,避免冲突也越来越重要。

系统介绍

因此,在“懒”的驱动下,笔者设计并实现了一套控制台系统,统一管理全局挂载的方法。这套系统有以下特点

  1. 节点和指令以树状形式构成了整个系统,节点包含了若干个子节点、指令
  2. 键入节点的名称或缩写,可以进入节点的语境,同时挂载其子节点和指令
  3. 键入指令的名称或缩写,以及参数(如有),可以发起指令
  4. 节点可以是静态的(写死),也可以是动态的(进入语境时生成)
  5. 每个节点,都有唯一的“路径”,路径本身就包含了一些信息,这些信息可以作为默认的参数,在调用指令时,不需要再传入这些信息。
  6. 节点和节点之间相互隔离,即使指令重名了,但因为语境不同,也不会产生冲突。
  7. 支持添加钩子函数,在节点挂载,退出时调用

效果展示

基础指令

效果展示

在候选菜单中,子节点,都是以"/"结尾的,键入子节点的名称或缩写,会进入子节点

值得注意的是每次键入节点的名称,都会自动触发 list 指令,展示当前节点下所有可用的子节点、指令。图中的"myroom"下的子节点,以及他的缩写,都是动态生成出来的。而且因为都配置了缩写,敲1-2个字母,就能在菜单间快速切换了。

数据结构

// 指令
interface Command {
    // 名称
    name: string;
    // 缩写
    alias?: string;
    // 描述
    description: string;
    // 参数
    parameters: string[];
    // 回调函数
    callback(...args: any[]): string;
}
// 节点
interface Dir {
    name: string;
    alias?: string;
    // 离开节点时的钩子函数
    onLeave?(): string;
    description: string;
    // 子节点
    dirs: Dir[];
    // 指令
    cmds: Command[];
}

如何实现动态的节点和指令

使用 getter 和 setter 在访问时计算出节点

动态节点的一个例子

export const myroom: Dir = {
    name: "myroom",
    alias: "mr",
    description: "我的房间",
    cmds: [],
    get dirs() {
        const roomNames = getMyRoomNames();
        let i = 0;
        let dirs: Dir[] = [];
        _.forEach(roomNames, roomName => {
            let dir: Dir = {
                name: roomName,
                alias: `r${i}`,
                description: "管理我的房间",
                cmds: [],
                get dirs() {
                    // 子节点的子节点也是动态的
                    return myRoomDirs(roomName);
                }
            };
            dirs.push(dir);
            i += 1;
        });
        return dirs;
    }
};

控制多个房间时,缩写会按照 r0,r1,r2 .... 的顺序自动生成

动态指令的一个例子

function level(roomName: string): Dir {
    return {
        name: "level",
        alias: "l",
        description: "每级建筑规划",
        onLeave: () => {
            Memory._lpRoomName = "";
            return `关闭${roomName}建筑级别可视化`;
        },
        dirs: [],
        get cmds() {
            Memory._lpRoomName = roomName;
            let cmds: Command[] = [];
            for (let i = 1; i <= 8; i++) {
                let cmd: Command = {
                    name: `level${i}`,
                    alias: `l${i}`,
                    description: `等级${i}的建筑规划`,
                    parameters: [],
                    callback: () => {
                        global._lpStructures = getLevelPlan(roomName, i);
                        return `查看等级${i}的建筑规划`;
                    }
                };
                cmds.push(cmd);
            }
            return cmds;
        }
    };
}

同样用到了 getter setter

如何挂载节点和指令

使用 Object.defineProperty,记得设置 configurable为true

    Object.defineProperty(global, "help", {
        // 这个不加的话,就不能修改了
        configurable: true,
        get: () => {
            let output = "";
            output += "home\t返回主页\n";
            output += "dir\t当前路径\n";
            output += "back\t返回上一级\n";
            output += "list\t可用路径/命令\n";
            output += "help\t帮助";
            return output;
        }
    });

进入某个节点的语境时,需要挂载他所有的子节点、指令,还要记得挂载它们的缩写。对于有参数的指令,需要挂载到 value上,而不是使用 getter。
为了记录路径,需要维护一个 list,存放当前进入过的节点,每进入一个子节点,就push一次,每退出一个节点,就pop一次,然后再挂载最右侧节点的子节点及指令。如果list为空,就挂载home节点。

一个提升体验的优化

因为节点的信息是对象,所以存放在global下,每次global reset的时候,都会丢失这部分信息,这在sim环境下是很不方便的,因此我还序列化存储了路径:home/myroom/sim/constructionPlan/design,在global reset后,自动按次序键入这些节点,以此来保证sim环境下的流畅使用。

    if (Memory._currentDirs) {
        const paths = Memory._currentDirs.split("/");
        for (let i = 0; i < paths.length; i++) {
            const path = paths[i];
            if (!(global as any)[path]) {
                break;
            }
        }
    }

结合可视化的使用案例

Screeps通过 RoomVisual 支持房间中的可视化。在实际使用中,除开那些一直开启的报表类的可视化,一些可选的可视化功能,经常需要手操启用或关闭,笔者的控制台系统非常适合对接这些可选的可视化功能。下面展示一个使用案例,或许会给你带来一些灵感。


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

推荐阅读更多精彩内容