前言
在游玩Screeps中,不可避免的會需要手動調(diào)節(jié)參數(shù)、發(fā)起指令,傳統(tǒng)的手操一般有以下方式
- 在控制臺中直接修改 Memory: Memory.xxx.xxx = xxx
- 在控制臺調(diào)用global上提前掛載好的方法: BuyOrder("orderid",10000)
- 插旗子、移動旗子
- 手動放置construction sites,摧毀建筑等
從筆者個人的實踐出發(fā),第一、二種方式的體驗是比較糟糕的,在控制臺敲代碼既沒有補全,也沒有提示,參數(shù)也比較多。而且隨著業(yè)務(wù)量的增大,掛載的方法也越來越多,如何統(tǒng)一管理這些方法,避免沖突也越來越重要。
系統(tǒng)介紹
因此,在“懶”的驅(qū)動下,筆者設(shè)計并實現(xiàn)了一套控制臺系統(tǒng),統(tǒng)一管理全局掛載的方法。這套系統(tǒng)有以下特點
- 節(jié)點和指令以樹狀形式構(gòu)成了整個系統(tǒng),節(jié)點包含了若干個子節(jié)點、指令
- 鍵入節(jié)點的名稱或縮寫,可以進入節(jié)點的語境,同時掛載其子節(jié)點和指令
- 鍵入指令的名稱或縮寫,以及參數(shù)(如有),可以發(fā)起指令
- 節(jié)點可以是靜態(tài)的(寫死),也可以是動態(tài)的(進入語境時生成)
- 每個節(jié)點,都有唯一的“路徑”,路徑本身就包含了一些信息,這些信息可以作為默認的參數(shù),在調(diào)用指令時,不需要再傳入這些信息。
- 節(jié)點和節(jié)點之間相互隔離,即使指令重名了,但因為語境不同,也不會產(chǎn)生沖突。
- 支持添加鉤子函數(shù),在節(jié)點掛載,退出時調(diào)用
效果展示


在候選菜單中,子節(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)非常適合對接這些可選的可視化功能。下面展示一個使用案例,或許會給你帶來一些靈感。
