JavaScript 之腳入門
JCM 自 v2 以降,許資源包以 JavaScript 司車輛、裝飾、電梯之渲染與聲音,暨 PIDS 之屏所顯之內容。本頁備載註冊、生命週期、除錯與守則;API 之細務,具於後之三篇。
JS 之腳,何物也
腳本者,隨資源包而散之 JavaScript 碼也。遊戲載資源之際為之解析,運行之期按幀而呼之,所以於原版機制之外,渲染模型、文字、貼圖,或播聲音也。
- 其引擎曰 Rhino——純以 Java 所成之 JavaScript 引擎也。君惟須通語言之本(變數、函數、條件之邏輯),與
Date、Map諸內建之物;瀏覽器之境(HTML/CSS/DOM)一無所用,IDE 網頁類之補全,勿照單全收也。 - Rhino 具 LiveConnect 互通之道:腳本得徑呼 Java 之類與方法。JCM 諸內建 API,多即曝予腳本之 Java 類也。
- JavaScript 與 Java,二語也,非一物;諳 Java 非撰腳本之先決,然解 Java 類型之制,有助於避互通之阱(見撰寫守則)。
Yanyang 之支,於 JS 之外增C++ 原生腳本;二者共用同一生命週期與渲染之管線,甚且得雜處於同一宣告之條(見 晏陽增補)。簡單一次性之邏輯,以 JS 迭代為最疾;高解析逐像素之重繪、繁難之算,則付諸 C++。
四類腳本
每類之腳,各應一專門之景;所得用之全域物件與回呼,微有異焉:
| 類型 | 所司之物 | 典型之用 |
|---|---|---|
| 車輛腳本 | MTR 載具(整列) | 車內之屏、終點之顯、報站之聲、電機之音 |
| 裝飾腳本 | MTR 裝飾之方塊 | 動態之識、閘機之燈、互動之器(右鍵之事件) |
| 電梯腳本 | MTR 電梯(轎廂之樣) | 樓層之顯、到站之鐘 |
| PIDS 腳本 | JCM 到站之屏(預設) | 自定到站顯示之版式、動態之紋理 |
同一份腳本之碼,惟解析一遭;遊戲之中,一一實例(每列車也,每屏也)各持獨立之 state,分別受回呼焉。
腳本之註冊
腳本隨資源包而載。先保資源包為遊戲所識——於包之根目錄置 pack.mcmeta 一件:
{
"pack_format": 13,
"description": "你的資源包描述"
}
pack_format 於 JCM 腳本無所約(不必隨 MC 版本而逐一改之),然必為合法之 JSON 數。
宣告之條
車輛 / 裝飾 / 電梯之腳,宣於 mtr_custom_resources.json(置諸 assets/mtr/ 之下);PIDS 之腳,宣於 joban_custom_resources.json(置諸 assets/jsblock/ 之下)之 pids_images 陣列。MTR 四代之式,「內容」與「腳本」分為二陣列;內容之條,經 scriptId 而引腳本之條——同一份腳本,得多車復用焉:
{
"vehicles": [
{ "id": "demo_sp1900", "name": "Demo SP1900", "scriptId": "demo_lcd" }
],
"vehicleScripts": [
{
"id": "demo_lcd",
"scriptLocations": ["demo:js/sp1900_lcd.js"],
"prependExpressions": ["print('Hello world')"],
"input": { "era": 2004 },
"rateLimit": 0.75,
"dataFetchMode": "ALL"
}
]
}
| 欄位 | 釋義 |
|---|---|
scriptLocations | JS 文件所在之陣列(可多條)。自 JCM v2.3 以降,多數 API 亦得徑收字串之 id。無副檔名之條,循 Yanyang 之規,或辨為 C++ 模塊——見 相雜之條 |
prependExpressions | 內嵌之 JS 碼,先 scriptLocations 而行 |
input | 任意 JSON 之物件;腳本之內,經 SCRIPT_INPUT 唯讀而訪 |
rateLimit | 腳本受呼頻率之下限(秒)。低頻之邏輯調高之,可省其費 |
dataFetchMode | 惟車輛腳本有之:SKIP(預設,不取數據)/ ALL / MANDATORY,詳見 數據獲取之式 |
forceLoad | 同包之內,無內容引此腳本者,JCM 預設拒載;置 true 則強載之(跨包共享腳本之條時用之) |
isScriptControlled | 裝飾 / 電梯內容條之欄:隱原模型,悉以渲染付腳本 |
三類腳本陣列之名:vehicleScripts(車輛,內容在 vehicles)、objectScripts(裝飾,內容在 objects)、liftScripts(電梯,內容在 lifts)。PIDS 無 scriptId 一環——逕於 pids_images 預設之物件上書 scriptFiles / scriptTexts,即視為腳本之預設(鍵名循 JCM 之慣例,見 PIDS 腳本)。
諸欄皆可省;然腳本之條,至少具 scriptLocations 或 prependExpressions 二者之一,方蒙解析。Yanyang 亦受 NTE 文檔示例 script_input 之寫(與 scriptInput 等價)——循舊檔而抄之包,不失其效也。
MTR 3 / NTE 舊格
MTR 三代之宣告,依然有效:車輛書於 custom_trains(欄名 script_files / script_texts / script_input / script_rate_limit),裝飾書於 eyecandy.json。舊格之車輛,預設按 MANDATORY 之式取數。
生命週期
解析之期
遊戲重載資源之時(啟也,或按 F3+T 也),每腳本於主線程行其頂層之碼一遭;既畢,JCM 錄其中 create / render / dispose 三函,以供運行期之呼。模型之載、紋理之讀諸一次性之事,置諸此最宜——然重操作足拖累整個資源之重載,能省則省。
運行之期
let displaySpeed = 1; // 解析之期所行
function create(ctx, state, vehicle) {
state.speed = 0.75; // 每實例首繪之前一遭
}
function render(ctx, state, vehicle) {
state.speed += Timing.delta(); // 每幀一遭(盡力而為)
}
function dispose(ctx, state, vehicle) { } // 實例退場之際行之
print(displaySpeed); // 解析之期所行,出 1
| 參數 | 其義 |
|---|---|
ctx | 上下文也:納繪製之令、播聲音之「動作」門戶也,各類各異(車輛者 VehicleScriptContext 也……) |
state | 尋常 JS 物件也,逐實例恆存——兩列車各執一份,互不相擾;其初為 {} |
wrapper | 唯讀之訊源:車速也,門值也,屏之尺寸也,到站之列也……各類各異 |
- 非同步之行:運行期之回呼,行於專用後臺線程凡四之上(解析之期,仍在主線程)。勿以
Thread.sleep或死循環佔其線程——同一腳本之條,恆繫於同一線程,他腳本或亦列於佇列之中。 - 逐幀而不保其逐幀:
render以每幀一遭為則;前幀未竟,則不入焉。幀率起伏之際,勿設「每秒恆 N 遭」。須按真時而進之量,以Timing.delta()縮放之。 - 運行之誤:日誌以
[Scripting] Error executing script!起首而誌其誤,並指文件行號;既而全份腳本停四秒復試。遊戲之內,可啟除錯覆層,逕於屏上觀其誤。 - JS 線程無所保:諸回呼函數之外,彼此局部之變數不相見——跨呼共享之數據,納諸
state。
create / render / dispose 加逐實例之狀態,即 C++ 原生腳本所沿之模型也(見 腳本體例與生命週期);二語得逐段對譯而遷焉。
開發之輔
- 腳本除錯之覆層:Mod Menu(Fabric)或模組配置之界面(Forge)→ JCM → Configure → 勾 Script debug mode。屏之左上,列每腳本之條與其實例;實例按其所費,藍者(如常)也,黃者(偏遲)也,紅者(遲甚)也。一遭逾五秒,則標為無應;更候五秒,則逕止之。按
[/],於 [MTR] 與 [JCM] 二源之間切換焉。 - 疾速重載:除錯之式既啟,Ctrl+R 重載 MTR 側之腳(車輛 / 裝飾 / 電梯),Shift+R 重載 JCM 側之腳(PIDS 預設)——無須全然之 F3+T。三鍵所實繫者,「衝刺」「潛行」「JCM 重載」之鍵也,可於按鍵之設改之。
- 日誌:
print(...)與console.log(...)出於遊戲之控制臺;解析期之誤,惟於聊天之欄示一句,其細務觀啟動器之logs/latest.log。 - IDE 補全:VSCode 有第三方之擴 MTR Scripting Tools(Visual Studio Marketplace / Open VSX 可搜得之),為 JCM 腳本之類型供補全之能;有問,請告擴之作者。
撰寫守則
語言之層
- 嚴格之式:未宣告而賦值(
x = 1),逕報其誤。全域用var或let;函數之內,一律let。 - const 者,函數之轄域也:Rhino 之
const,不若今世 JS 按塊為轄——於for循環之體中宣告者,第二番迭代必報「重複宣告」。須塊域之義者,用let。 - JS 字串與 Java 字串:MTR/JCM 所返者,多 Java
String也。取其長,用str.length();欲用 JS.length之欄,先拼空串以化之:(""+str).length。JavaList可用list[0]與for (x of list),然 JS 陣列之法(map之屬)不可用也。 - 可空之值:返型帶
?之法,或返null。以if (x != null)、三元之式、可選之鏈x?.getName()與空值之併x?.length() ?? 0處之;於null取欄或呼法,立擲其誤。 - 重載之解:Java 之法,按參數之型擇其重載;數值預設偏好
double。疑而未決,得以簽名之綴直呼之:graphics["drawString(java.lang.String,int,int)"]("文字", 15, 60)、new Color["(int,int,int)"](40, 68, 130)。報「method not found」之時,先疑參數之型。 - 特性所覆:Rhino 不支持一切最新 JS 之特性,詳見 Mozilla 之相容表。
行為之層
- 勿阻塞、勿死循環:無「少待而復行」之寫——計其時而置諸後續之回呼以行之;耗時之事(網路也,巨檔也),付諸
BackgroundWorker。死循環約十秒,則遭強止,依運行之誤而處之。 - 內部 API 之疆界:本檔未書之法 / 欄,
MTRClientData內部之構,VehicleWrapper.getMtrVehicle()以降深層之物,悉屬內部之實,版本之間或逕變焉;公開散布之資源包,勿依賴之。 - 類之白單:預設惟放行此諸包——
com.lx862.jcm.mod.scripting、com.lx862.mtrscripting.util、com.lx862.mtrscripting.wrapper、java.lang、java.awt、java.text、java.math、java.util、java.time、javax.imageio、sun.java2d、sun.font、sun.awt、org.mtr。玩家可於設置解之;然散布之包若須解其限,務於說明之中醒目告之。 - state 之變數宜加前綴:多腳本得同懸一實例之上,
state乃共享者也——以state.myfx_ratelimit之屬為前綴,免其相撞。 - 疾徐有預算:單幀 16 ms,視若足用;然玩家或同時行數十腳本之實例。逐幀重繪巨紋理之前,先算其總帳:一列八節,每節十屏,每幀全量
upload(),即紋理之搬凡八十遭——降解析度、跨門復用、降低更新之頻(RateLimit),皆正道也。客戶端之腳,得其「足矣」即止,勿求 1:1 之複刻。 - 腳本非安全之界:腳本純行於客戶端,人人得於本地改寫之——凡權限之判(如
hasPermissionLevel),皆莫能禦有心之人;勿恃之以為訪問之制。
與 NTE / MTR 3 之淵源
JCM 腳本之體,受 Nemo Transit Expansion(NTE)之啟,乃 NTE 腳本之能於 MTR 四代之上非官方之續也;同名等價之相容 API,存者甚夥(檔中標「已廢」者是也)。其異之要曰:
- 舊格之註冊,行如常;MTR 三代與四代雙棲之腳本,得以
Resources.getAddonVersion("mtr")(舊腳本之Resources.getMTRVersion())辨其版本而後分支。 - NTE 之
Vector3f運算,昔返void者,今返其自身,得鏈式而呼;Resources.manager()於 MTR 四代返null,相關之函亦受null焉。 - ANTE 風格相容之類(
SoundHelper/TickableSound/GlobalRegister/ComponentUtil/ 全域asJavaArray())自 v2.3 而可用,法之簽名與 ANTE 一致。 - MTR 四代惟以鄰近之車站 / 線路數據同步於客戶端——腳本查不得遠處之數據,非 bug 也,乃刻意省其流也;車輛腳本數據獲取之三式,見 數據獲取之式。