美文网首页Screeps 游戏指南
Screeps 控制台结合可视化——优化手操体验

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

作者: masterkeze | 来源:发表于2022-03-23 18:06 被阅读0次

    前言

    在游玩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

    相关文章

      网友评论

        本文标题:Screeps 控制台结合可视化——优化手操体验

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