快速上手
从零到游戏内运行:获取 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++ 脚本被跳过——这不是你的模块出错。