Screeps 控制臺結(jié)合可視化——優(yōu)化手操體驗

前言

在游玩Screeps中,不可避免的會需要手動調(diào)節(jié)參數(shù)、發(fā)起指令,傳統(tǒng)的手操一般有以下方式

  1. 在控制臺中直接修改 Memory: Memory.xxx.xxx = xxx
  2. 在控制臺調(diào)用global上提前掛載好的方法: BuyOrder("orderid",10000)
  3. 插旗子、移動旗子
  4. 手動放置construction sites,摧毀建筑等

從筆者個人的實踐出發(fā),第一、二種方式的體驗是比較糟糕的,在控制臺敲代碼既沒有補全,也沒有提示,參數(shù)也比較多。而且隨著業(yè)務(wù)量的增大,掛載的方法也越來越多,如何統(tǒng)一管理這些方法,避免沖突也越來越重要。

系統(tǒng)介紹

因此,在“懶”的驅(qū)動下,筆者設(shè)計并實現(xiàn)了一套控制臺系統(tǒng),統(tǒng)一管理全局掛載的方法。這套系統(tǒng)有以下特點

  1. 節(jié)點和指令以樹狀形式構(gòu)成了整個系統(tǒng),節(jié)點包含了若干個子節(jié)點、指令
  2. 鍵入節(jié)點的名稱或縮寫,可以進入節(jié)點的語境,同時掛載其子節(jié)點和指令
  3. 鍵入指令的名稱或縮寫,以及參數(shù)(如有),可以發(fā)起指令
  4. 節(jié)點可以是靜態(tài)的(寫死),也可以是動態(tài)的(進入語境時生成)
  5. 每個節(jié)點,都有唯一的“路徑”,路徑本身就包含了一些信息,這些信息可以作為默認的參數(shù),在調(diào)用指令時,不需要再傳入這些信息。
  6. 節(jié)點和節(jié)點之間相互隔離,即使指令重名了,但因為語境不同,也不會產(chǎn)生沖突。
  7. 支持添加鉤子函數(shù),在節(jié)點掛載,退出時調(diào)用

效果展示

基礎(chǔ)指令

效果展示

在候選菜單中,子節(jié)點,都是以"/"結(jié)尾的,鍵入子節(jié)點的名稱或縮寫,會進入子節(jié)點

值得注意的是每次鍵入節(jié)點的名稱,都會自動觸發(fā) list 指令,展示當前節(jié)點下所有可用的子節(jié)點、指令。圖中的"myroom"下的子節(jié)點,以及他的縮寫,都是動態(tài)生成出來的。而且因為都配置了縮寫,敲1-2個字母,就能在菜單間快速切換了。

數(shù)據(jù)結(jié)構(gòu)

// 指令
interface Command {
    // 名稱
    name: string;
    // 縮寫
    alias?: string;
    // 描述
    description: string;
    // 參數(shù)
    parameters: string[];
    // 回調(diào)函數(shù)
    callback(...args: any[]): string;
}
// 節(jié)點
interface Dir {
    name: string;
    alias?: string;
    // 離開節(jié)點時的鉤子函數(shù)
    onLeave?(): string;
    description: string;
    // 子節(jié)點
    dirs: Dir[];
    // 指令
    cmds: Command[];
}

如何實現(xiàn)動態(tài)的節(jié)點和指令

使用 getter 和 setter 在訪問時計算出節(jié)點

動態(tài)節(jié)點的一個例子

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() {
                    // 子節(jié)點的子節(jié)點也是動態(tài)的
                    return myRoomDirs(roomName);
                }
            };
            dirs.push(dir);
            i += 1;
        });
        return dirs;
    }
};

控制多個房間時,縮寫會按照 r0,r1,r2 .... 的順序自動生成

動態(tài)指令的一個例子

function level(roomName: string): Dir {
    return {
        name: "level",
        alias: "l",
        description: "每級建筑規(guī)劃",
        onLeave: () => {
            Memory._lpRoomName = "";
            return `關(guān)閉${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}的建筑規(guī)劃`,
                    parameters: [],
                    callback: () => {
                        global._lpStructures = getLevelPlan(roomName, i);
                        return `查看等級${i}的建筑規(guī)劃`;
                    }
                };
                cmds.push(cmd);
            }
            return cmds;
        }
    };
}

同樣用到了 getter setter

如何掛載節(jié)點和指令

使用 Object.defineProperty,記得設(shè)置 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;
        }
    });

進入某個節(jié)點的語境時,需要掛載他所有的子節(jié)點、指令,還要記得掛載它們的縮寫。對于有參數(shù)的指令,需要掛載到 value上,而不是使用 getter。
為了記錄路徑,需要維護一個 list,存放當前進入過的節(jié)點,每進入一個子節(jié)點,就push一次,每退出一個節(jié)點,就pop一次,然后再掛載最右側(cè)節(jié)點的子節(jié)點及指令。如果list為空,就掛載home節(jié)點。

一個提升體驗的優(yōu)化

因為節(jié)點的信息是對象,所以存放在global下,每次global reset的時候,都會丟失這部分信息,這在sim環(huán)境下是很不方便的,因此我還序列化存儲了路徑:home/myroom/sim/constructionPlan/design,在global reset后,自動按次序鍵入這些節(jié)點,以此來保證sim環(huán)境下的流暢使用。

    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;
            }
        }
    }

結(jié)合可視化的使用案例

Screeps通過 RoomVisual 支持房間中的可視化。在實際使用中,除開那些一直開啟的報表類的可視化,一些可選的可視化功能,經(jīng)常需要手操啟用或關(guān)閉,筆者的控制臺系統(tǒng)非常適合對接這些可選的可視化功能。下面展示一個使用案例,或許會給你帶來一些靈感。


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

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

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