快速上手

先决条件

工具版本说明
C++ 编译器C++17MSVC(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 x64my_pids.dllMSVC,/utf-8 由 SDK 工程预设
Linux x64 / ARM64libmy_pids.soGCC / Clang
macOS x64 / arm64libmy_pids.dylib用 -DCMAKE_OSX_ARCHITECTURES=arm64(或 x86_64)选择架构,-DCMAKE_OSX_DEPLOYMENT_TARGET=11.0
Android arm64libmy_pids.soNDK 工具链交叉编译,API 21+;16 KB 页对齐已由工程模板处理
iOS / iPadOS arm64libmy_pids.dylibMach-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 / ….dylibC++ 完整路径(绑定该扩展名对应的平台,其它平台跳过)
{"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/ 只有原生桥,两者不要混淆。游戏只会加载当前操作系统对应平台的库。

游戏内验证

  1. 装好 JCM Yanyang 与 MTR,把资源包放进 resourcepacks/ 并启用(或作为内置资源随存档分发)。
  2. 放置对应方块(如 PIDS),在配置里选中你的脚本 / 让声明条目生效。
  3. 打开 logs/latest.log,搜索 native script——模组在加载时会打印模块 census 摘要(模块数、脚本类型、传输通道等)。看到你的 example:arrivals 即为加载成功。
  4. 改了库文件后,按 F3+T 重载资源包即可重载 C++ 脚本(或使用模组提供的「重载全部 MTR 脚本」快捷键),无需重启游戏。
重要

必须使用 Release 版 jar 或 Complete Build 产物。push/PR 的快速 CI 产物不含原生桥,日志会提示 native bridge unavailable、所有 C++ 脚本被跳过——这不是你的模块出错。


下一步:脚本模型与生命周期 —— 四类脚本的完整语义;或直接跳 数据快照 查阅可读数据。