序章:把环境搭起来
一个能编译、能进游戏、在模组列表里显示 `ACTIVE` 的空模组工程。之后所有教程都从这份工程继续。
写模组最常见的卡点不是代码,而是「环境不对」:JDK 版本低了、API jar 拿错版本、jar 没放进 mods\、入口类名写错。任何一个都会让游戏静默地不加载你的模组。
这一章只做一件事:把最短路径走通并验证。跑通之后再写代码,出问题就只可能是代码问题。
- 宿主(host):游戏本体。它负责加载模组、提供 API、在合适的时候回调你的代码。
- mod-api:一个只含接口定义的瘦 jar(
mod-api-3.2.jar)。编译时依赖它,运行时由游戏提供——不要把它打进自己的模组 jar。 - 模组 jar:你最终产出的文件,放进游戏根目录的
mods\后由宿主加载。
你需要准备
| 东西 | 说明 | 怎么确认 |
|---|---|---|
| JDK 17 或更高 | 编译模组用 | 命令行执行 javac -version,输出 17 以上 |
| 游戏本体 | 提供 mods\ 目录与运行环境 | 能正常启动游戏 |
mod-api-3.2.jar | 编译期 API | 来自文档站「资源」页 |
gson-2.10.1.jar | API 签名里用到 JsonObject,编译期需要 | 同上(模板已内置) |
| 模板工程 | 自带 build.bat 与示例代码 | 文档站「资源」页的 mods-template.zip |
宿主内部有成千上万个类,但模组只应该看见「被承诺过」的那一小部分。mod-api 就是这份承诺:只有接口和少量值对象,没有实现。
好处有两条:你的代码不会意外依赖宿主内部实现(宿主升级不会把你打挂);宿主能在加载时限制你能访问什么——引用内核类会被拒绝加载。
步骤
打开命令行:
javac -version
java -version两条都要是 17 或更高。提示「不是内部或外部命令」说明 JDK 没装或没进 PATH,先解决这个再往下走。
把 mods-template.zip 解压到任意目录(路径不要带空格和中文,部分工具链对空格敏感):
my-mod/
├── mod-api-3.2.jar ← 编译期 API(模板已带)
├── gson-2.10.1.jar ← 编译期依赖(模板已带)
├── build.bat ← 一键构建脚本
├── pom.xml ← Maven 路线(与 build.bat 二选一)
├── README.md
└── src/main/
├── java/com/example/mymod/ExampleMod.java
└── resources/assets/example_mod/src/main/java 放代码,src/main/resources 放资源;资源在 jar 里的路径就是 assets/...。
用记事本打开 build.bat,找到顶部这几行改成你自己的:
set "MOD_ID=hello_mod"
set "MOD_NAME=Hello Mod"
set "MOD_VERSION=0.1.0"
set "MOD_CLASS=com.example.hellomod.HelloMod"
set "API_VERSION=3.2"四条注意:
MOD_ID只能用小写字母、数字、_、.、-;它同时是资源命名空间和数据目录名;MOD_CLASS必须是入口类的全限定名,要和package+ 类名完全一致;API_VERSION写3.2;- 改完把示例的包名/类名一起改成
com.example.hellomod.HelloMod,或者反过来把MOD_CLASS指向示例类。
双击 build.bat,或在命令行执行:
build.bat成功时产物在 target\hello_mod-0.1.0.jar。脚本实际做了四件事:javac 编译 → 复制资源 → 写入 manifest(入口类、模组 id、版本、API 版本等)→ jar 打包。
- 终端最后没有
error:字样; target\下出现hello_mod-0.1.0.jar;- 用压缩软件打开 jar,能看到
META-INF/MANIFEST.MF和你的.class。
把 jar 复制到游戏根目录的 mods\:
<游戏目录>/
├── mods/
│ └── hello_mod-0.1.0.jar
└── (游戏本体文件…)重启游戏,打开模组列表找到你的模组。状态必须是 ACTIVE。
| 状态 | 含义 | 该做什么 |
|---|---|---|
ACTIVE | 加载成功并已激活 | 继续下一步 |
REGISTERED | 已注册但没启用 | 在列表里启用,然后重启游戏 |
LOAD_FAILED | 启用但加载失败 | 看列表里的失败原因 + 游戏日志 |
注册在启动期一次性完成:宿主先让所有模组跑完 onPreInit,随即冻结注册表,之后运行期只读。这样判定与渲染热路径不用每帧检查「注册表有没有变」,代价就是改启用状态必须重启。
在游戏日志里搜模组 id(日志行前缀是 [<模组id>],logger 名为 seecmd.mod.<模组id>)。能看到入口类打印的初始化日志,就说明代码真的跑起来了。
常见失败对照
| 现象 | 原因 | 处理 |
|---|---|---|
LOAD_FAILED,原因含「此模组要求 API X,宿主支持 Y」 | API_VERSION 与宿主 major 不一致 | 改成 3.2,重新构建 |
LOAD_FAILED,原因含「找不到入口类」 | MOD_CLASS 与实际包名/类名不一致 | 核对 package 与 manifest |
LOAD_FAILED,原因含「内核访问被拒绝」 | 代码 import 了 com.seecmd.* 下的宿主实现类 | 只 import com.seecmd.mod.api.* |
| 列表里看不到模组 | jar 没放进 mods\,或目录层级不对 | 确认是 <游戏目录>\mods\xxx.jar |
状态 REGISTERED | 模组未启用 | 启用并重启游戏 |
javac -version≥ 17build.bat无错误,产出target\<id>-<ver>.jar- jar 已放入
mods\ - 模组列表状态为
ACTIVE - 日志中能看到
[<模组id>]前缀的输出
接下来
环境通了,下一步写真正的最小模组:第一个模组。