快速上手
從零到遊戲內執行:取得 SDK → 寫第一個模組 → 編譯 → 打包進資源包。
先決條件
| 工具 | 版本 | 說明 |
|---|---|---|
| C++ 編譯器 | C++17 | MSVC(Windows)/ GCC / Clang 任一 |
| CMake | ≥ 3.16 | 推薦,但非硬性——SDK 是純標頭檔 |
| JDK | 可選 | 僅執行 SDK 自帶的 JVM 整合測試時需要 |
SDK 全部以標頭檔提供(native/include/mtr/),沒有任何預編譯程式庫依賴;用你熟悉的建置系統直接把 include/ 加進包含路徑即可。
取得 SDK
SDK 隨每個 Release 發佈為 jcm-native-sdk-<版本>.zip(見 GitHub Releases 附件),內容即儲存庫 native/ 目錄原始碼:
jcm-native-sdk-2.3.0-1.0.2/
├── include/mtr/ # 全部 SDK 標頭檔(script.hpp 是總入口)
│ ├── mtr_native.h # C ABI:匯出函式、快照、繪製記錄、宿主服務
│ ├── script.hpp # 腳本基類 + 註冊巨集(#include 這一個就夠)
│ ├── vehicle.hpp pids.hpp lift.hpp eyecandy.hpp
│ ├── gfx.hpp gfx2d.hpp font5x7.hpp text.hpp matrices.hpp
│ ├── api.hpp # mtr::Api(完整 Java API 存取)
│ └── crash_guard.hpp ...
├── examples/ # 三個最小範例模組
├── bench/ # 測試與工具
└── CMakeLists.txt
建立模組專案
最省事的方式:把 SDK 解壓進你的資源包專案,直接在它的 CMakeLists.txt 末尾追加自己的目標——SDK 已經把各平台的連結細節(Windows 無前綴 .dll、macOS .dylib 與 -undefined dynamic_lookup、Android 16 KB 頁對齊)封裝成 mtr_add_script() 函式:
# 追加到 SDK 的 native/CMakeLists.txt 末尾
mtr_add_script(my_pids src/my_pids.cpp)
若想獨立建專案,下面是與該函式等價的完整模板:
cmake_minimum_required(VERSION 3.16)
project(my_native_scripts LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# SDK 標頭檔路徑(把解壓的 SDK include 目錄指進來)
add_library(mtr_script_sdk INTERFACE)
target_include_directories(mtr_script_sdk INTERFACE path/to/sdk/include)
function(mtr_add_script name) # 與 SDK 同款的模組目標函式
add_library(${name} MODULE ${ARGN})
target_link_libraries(${name} PRIVATE mtr_script_sdk)
set_target_properties(${name} PROPERTIES CXX_VISIBILITY_PRESET hidden)
if(WIN32)
set_target_properties(${name} PROPERTIES PREFIX "" SUFFIX ".dll")
elseif(APPLE)
set_target_properties(${name} PROPERTIES PREFIX "lib" SUFFIX ".dylib")
target_link_options(${name} PRIVATE "-Wl,-undefined,dynamic_lookup")
elseif(ANDROID)
target_link_options(${name} PRIVATE "-Wl,-z,max-page-size=16384")
endif()
endfunction()
mtr_add_script(my_pids src/my_pids.cpp)
腳本是 MODULE 程式庫(不連結任何 SDK 二進位),預設符號隱藏、只匯出 ABI 要求的 7 個 mtr* 函式(由 script.hpp 的註冊巨集完成)。C++17 是最低標準。
第一個模組
一個最小的 PIDS 到站顯示模組——完整、可編譯、可直接進遊戲:
#include <mtr/script.hpp>
struct ArrivalState {}; // 逐實例持久狀態(本例為空)
struct ArrivalDisplay : mtr::PidsScript<ArrivalState> {
static constexpr auto ID = "example:arrivals"; // 必須與資源包宣告一致
void render(mtr::PidsContext& ctx, ArrivalState&,
const mtr::Pids& pids) override {
for (int row = 0; row < pids.rows(); row++) {
if (pids.is_row_hidden(row)) continue;
std::string text = pids.get_custom_message(row).str();
if (text.empty() && row < pids.arrivals().size())
text = pids.arrivals().at(row).destination().str();
if (!text.empty())
mtr::Text::create()
.text(text.c_str())
.pos(2, row * 10.0 + 2)
.size(pids.width() - 4, 9)
.draw(ctx);
}
}
};
MTR_REGISTER_PIDS_SCRIPT(ArrivalDisplay)
要點:
- 基類決定腳本類型:
PidsScript/VehicleScript/LiftScript/EyecandyScript,模板參數是逐實例狀態類型。 - 生命週期與 JS 相同:
create/render/dispose三個虛擬函式按需 override(全部可選)。 - 註冊巨集展開全部 ABI 匯出,一個翻譯單元只註冊一個腳本。
更多範例見 SDK 的 examples/:車輛 / 電梯 / 裝飾三類各有最小樣例。
編譯
cmake -S native -B native/build -DCMAKE_BUILD_TYPE=Release
cmake --build native/build --config Release --parallel 4
ctest --test-dir native/build -C Release --output-on-failure # 可選:跑 SDK 自帶測試
| 目標平台 | 產物 | 說明 |
|---|---|---|
| Windows x64 | my_pids.dll | MSVC,/utf-8 由 SDK 專案預設 |
| Linux x64 / ARM64 | libmy_pids.so | GCC / Clang |
| macOS x64 / arm64 | libmy_pids.dylib | 用 -DCMAKE_OSX_ARCHITECTURES=arm64(或 x86_64)選擇架構,-DCMAKE_OSX_DEPLOYMENT_TARGET=11.0 |
| Android arm64 | libmy_pids.so | NDK 工具鏈交叉編譯,API 21+;16 KB 頁對齊已由專案模板處理 |
| iOS / iPadOS arm64 | libmy_pids.dylib | Mach-O 交叉編譯,無需簽署(側載時重新簽署) |
資源包內的程式庫按副檔名綁定平台(.dll → Windows、.so → Linux/Android、.dylib → macOS/iOS),具體目錄規則見下節。
打包進資源包
目錄佈局
把各平台編譯產物放進資源包的 natives/<platform>/ 目錄:
my_pack/
├── natives/
│ ├── windows-x64/my_pids.dll
│ ├── windows-arm64/my_pids.dll
│ ├── linux-x64/libmy_pids.so
│ ├── linux-arm64/libmy_pids.so
│ ├── macos-x64/libmy_pids.dylib
│ ├── macos-arm64/libmy_pids.dylib
│ ├── android-arm64/libmy_pids.so
│ └── ios-arm64/libmy_pids.dylib
└── ...(貼圖、模型等其餘資源)
在 joban_custom_resources.json 中宣告(PIDS 預設)
不需要 language 欄位——模組按 scriptLocations 裏每個項目的特徵自動判定語言,JS 與 C++ 可以在同一項目混用任何數量:
{
"pids_images": [{
"id": "example:arrivals",
"scriptLocations": [
"mtr:my_pack/natives/my_pids"
]
}]
}
項目解析規則:
scriptLocations 項目 | 判定結果 |
|---|---|
…/main.js(大小寫不敏感) | JavaScript |
mtr:my_pack/natives/my_pids(無副檔名) | C++ 平台無關 stem → 各平台自動解析為 natives/linux-x64/libmy_pids.so、natives/windows-x64/my_pids.dll、natives/macos-arm64/libmy_pids.dylib 等;ARM64 Linux 環境自動追加 ios-arm64 / android-arm64 候選 |
…/libfoo.so / ….dll / ….dylib | C++ 完整路徑(綁定該副檔名對應的平台,其他平台跳過) |
{"windows": …, "linux": …, "macos": …} | C++ 按平台對應表;支援 macos-arm64 等精確鍵(精確鍵優先於通用鍵) |
- 四類腳本同規則:
mtr_custom_resources.json的vehicleScripts/objectScripts/liftScripts與joban_custom_resources.json的pids_images預設(scriptLocations)全部支援上述混合宣告。 - 舊格式相容:
"language": "cpp"+nativeLibrary/nativeLibraries的舊宣告繼續有效,也可與.js混用;"language": "javascript"可強制整條按 JS 解析。 - JS 欄位照常:
prependExpressions/input/dataFetchMode等欄位含義不變,C++ 側經api.input()讀取input。 - 視界外繼續渲染:PIDS 預設欄位
outOfBound: true(上游 v2.3.0-prerelease.3)令螢幕移出相機視界時仍繼續渲染——須臾不可停嘅投影機預設所必需。
腳本模組(你編譯的 .dll/.so/.dylib)屬於資源包,放進 natives/<platform>/;模組 jar 自帶的 assets/jcm/natives/ 只有原生橋,兩者不要混淆。遊戲只會載入現時作業系統對應平台的程式庫。
遊戲內驗證
- 安裝好 JCM Yanyang 與 MTR,把資源包放進
resourcepacks/並啟用(或作為內置資源隨存檔一併發佈)。 - 放置對應方塊(如 PIDS),在設定裏選取你的腳本 / 讓宣告項目生效。
- 開啟
logs/latest.log,搜尋native script——模組載入時會列印模組 census 摘要(模組數目、腳本類型、傳輸通道等)。看到你的example:arrivals即為載入成功。 - 改了程式庫檔案後,按 F3+T 重新載入資源包即可重新載入 C++ 腳本(或使用模組提供的「重新載入全部 MTR 腳本」快捷鍵),無需重新啟動遊戲。
必須使用 Release 版 jar 或 Complete Build 產物。push/PR 的快速 CI 產物不含原生橋,日誌會提示 native bridge unavailable、所有 C++ 腳本被跳過——這不是你的模組出錯。