21 发布¶
mod 在自己机器上跑通之后,下一步是让别的玩家也能用。
发布意味着什么¶
发布之后,使用环境和本地开发不同:
- 其他玩家拿到的是固定的文件包,没有你的编辑器和调试习惯。
- 出问题时他们看到的是日志里的错误消息,不是源码。
- 你以后更新时,他们本地已经有旧版配置文件。
发布包需要让别人能安装、运行、更新和卸载。这个要求会反过来影响文件结构、版本号、配置和日志。
Mod 身份:元数据¶
附加到入口类的元数据是 BML+ 识别和管理 Mod 的依据:
[bml.mod id="complete.mod" name="Complete Mod" version="1.0.0" author="YourName" bml="0.3.13" description="A complete tutorial mod"]
class CompleteMod {
}
各字段含义¶
| 字段 | 作用 | 要求 |
|---|---|---|
id |
机器标识。配置文件名、日志前缀、依赖声明全靠它 | 发布后永远不改 |
name |
在 mod 列表里显示的名字 | 可以随版本调整 |
version |
当前版本号 | 每次发布必须递增 |
author |
作者名 | 可以写多个,用逗号分隔 |
bml |
需要的最低 BML 版本 | 用了新 API 时必须更新 |
description |
一句话功能描述 | 控制在 80 字符以内 |
id 的选择规则¶
id 相当于 mod 的身份证号。BML 用它决定配置文件路径(ModLoader/Configs/<id>.cfg)、日志标记、依赖声明。
好的 id 是小写字母加点号,例如 fps.display、ball.spawner、level.timer。避免中文、空格或特殊符号。
一旦发布第一个版本,id 就固定了。改了 id,BML 会把它当作另一个 mod,旧配置变成孤儿,用户设置全部丢失。
版本号¶
使用三段式版本号:主版本.次版本.修订版本
什么时候改哪个数字¶
| 你做了什么 | 版本变化 | 示例 |
|---|---|---|
| 修 bug、改措辞、修正日志格式 | 修订 +1 | 1.0.0 -> 1.0.1 |
| 加新功能(命令、配置项),旧行为不变 | 次版本 +1,修订归零 | 1.0.0 -> 1.1.0 |
| 删除命令、改变配置含义、改变默认行为 | 主版本 +1,其余归零 | 1.0.0 -> 2.0.0 |
具体场景¶
假设你的 mod 当前是 1.2.3:
- 发现窗口位置计算有误,修好了 ->
1.2.4 - 加了一个
/cmod reset命令 ->1.3.0 - 把配置项
ShowWindow从布尔改成了整数(0=隐藏、1=小窗、2=大窗)->2.0.0
主版本号变化意味着"用户可能需要重新配置"。这是对用户最重要的信号。
BML+ 版本要求¶
bml="0.3.13" 表示需要 BML+ 0.3.13 或更高版本。版本不满足时 Mod 不会被加载,日志会提示不匹配。
判断方法:检查用到的 API 是在哪个版本引入的。基础回调(OnLoad、OnUnload、GetConfig)可以保持较低要求;用了新 API 就更新到对应版本。不确定时写你开发环境的 BML 版本,至少保证测试过的环境匹配。
文件结构¶
命名约定¶
BML 通过 .mod.as 后缀识别入口文件。一个 mod 有且只有一个 .mod.as 文件。文件名通常和 mod 名称对应:
其他辅助脚本文件使用 .as 后缀(不带 .mod),它们不会被 BML 当作独立 mod 加载。
单文件发布¶
最直接的方式。适合代码量小(几百行以内)的 mod:
用户把这个文件放入 ModLoader/Mods/ 就能用。
目录发布¶
代码量大或需要分模块时使用目录结构:
规则:目录里有且只能有一个 *.mod.as 入口文件。BML 扫描 ModLoader/Mods/ 时,如果遇到子目录,会进入其中寻找入口。
zip 包分发¶
正式发布目录形式的 Mod 时,使用 BML+ SDK 随附的打包脚本:
Set-Location "<Ballance>/ModLoader/Mods/CompleteMod"
& "<BML-SDK>/scripts/Pack-BMLScriptMod.ps1" -Force
默认产物是 dist/CompleteMod.zip。自动化流程仍可通过 -Source 和 -Output
指定其他路径。源目录的顶层必须恰好有一个 *.mod.as 入口;打包脚本会保留
相对路径,并让入口位于 zip 根目录:
用户把 CompleteMod.zip 直接放入 ModLoader/Mods/ 即可,不需要手动解压。
.bmodp 只用于原生 DLL Mod,不能作为脚本 Mod 的扩展名。
打包器会自动排除 dist、编辑器设置、版本控制元数据、as.predefined 和 Python
缓存文件。下面的源码与运行行为检查仍需作者自己完成。
不应包含的内容¶
打包前检查一遍,确保没有把以下内容混入发布包:
| 不该出现的 | 原因 |
|---|---|
| 本机绝对路径 | 别人的电脑路径不同 |
| 调试专用代码(每帧日志输出或测试命令) | 干扰用户体验和日志阅读 |
编辑器临时文件(.swp、.bak、~ 结尾文件) |
与发布无关 |
| 你的 BML 配置文件夹 | 包含你的个人设置 |
| 编译产物或日志文件 | 无用且可能很大 |
如果你有仅开发时使用的测试命令,在发布前移除,或者用配置项默认关闭。不要让玩家在正常使用时看到开发入口。
发布前测试¶
在自己的开发环境跑通不够。你需要模拟"一个从未见过这个 mod 的用户"的安装体验。
步骤 1:清理现有状态¶
- 删掉
ModLoader/Configs/下你的 mod 配置文件 - 删掉游戏目录中任何你 mod 创建的临时文件
- 目的:确认首次运行时,mod 不依赖遗留配置
步骤 2:使用打包后的文件安装¶
不要直接使用开发目录里的源码。在一个干净的 ModLoader/Mods/ 中只放入打包
得到的 CompleteMod.zip。先移走相同 id 的开发目录或单文件,避免 BML 同时
发现两个副本。zip 包默认不参与文件监视,最终验证时应重新启动 Player。
步骤 3:启动游戏并检查日志¶
打开 ModLoader/ModLoader.log,确认:
- mod 被正确识别并加载(应该看到带 id 的加载消息)
- 没有脚本编译错误
- 没有 "null handle" 或 "undefined variable" 警告
- 如果有命令,输入命令能正常响应
步骤 4:完整游戏流程测试¶
- 进入关卡 -> 退出关卡 -> 重新进入 -> 退出游戏
- 确认每个阶段 mod 的行为正常,没有残留对象或崩溃
- 如果 mod 有 OnUnload 清理逻辑,确认卸载时不报错
如果你声明了 bml="0.3.13" 但手头只有更新的 BML 版本,至少在发布说明中注明实际测试的版本。
发布说明¶
每个版本都应该附带说明文档。发布说明写给"准备安装这个 mod 的玩家"看,内容要从玩家视角出发。
模板¶
Complete Mod v1.0.0
需要:BML+ 0.3.13 或更新版本。
安装:把 CompleteMod.zip 直接放入 ModLoader/Mods/,不要解压。
命令:cmod [spawn|despawn|push|status|window]
配置:ShowWindow, AutoSpawn
卸载:删除 mod 文件。如需清掉设置,删除 ModLoader/Configs/complete.mod.cfg。
已验证:Ballance Player + BML+ 0.3.13
每一条都是对用户的承诺:安装路径要写完整(不要假设用户知道 Mods 在哪);命令和配置要列全(用户不一定会看源码);卸载说明要包含配置文件路径。
更新说明¶
从第二个版本开始,还应包含变更内容:
Complete Mod v1.1.0
新增:/cmod reset 命令,重置所有生成的对象
修复:退出关卡时偶尔出现的空指针错误
配置:新增 MaxObjects 项(默认值 10)
注意:旧版配置自动兼容,无需手动修改
配置迁移¶
默认值由代码负责¶
配置的默认值必须在代码中设置:
不要通过分发默认配置文件来提供初始值。原因:用户更新 mod 后,如果你的新配置文件覆盖了他的自定义设置,他会丢失个人配置。
改名时兼容旧 key¶
如果新版本需要修改配置项名称(例如从 OldKey 改为 NewKey),必须写迁移代码:
if (config.HasKey("MyMod", "OldKey") && !config.HasKey("MyMod", "NewKey")) {
BML::ConfigProperty@ old = config.GetProperty("MyMod", "OldKey");
BML::ConfigProperty@ new = config.GetProperty("MyMod", "NewKey");
if (old !is null && new !is null) {
new.SetBoolean(old.GetBoolean(true));
}
}
这段代码的逻辑:
- 检查旧 key 是否存在且新 key 尚未创建
- 如果条件满足,读取旧值写入新 key
- 使用
@句柄和!is null检查防止空引用
这样做的好处:用户从旧版更新后,已有设置会自动迁移到新配置项,无需手动操作。
什么时候不需要迁移¶
- 新增一个以前没有的配置项,直接设默认值即可
- 删除一个配置项,旧值留在文件里不影响运行,无需特殊处理
风险分级与用户沟通¶
风险等级¶
发布前确认你的 mod 属于哪个等级:
| 等级 | 行为范围 | 举例 |
|---|---|---|
| 低风险 | 只读取游戏状态、显示额外信息 | FPS 显示、日志观察窗口 |
| 中风险 | 创建或删除自己的对象 | 木球生成器、临时标记物 |
| 高风险 | 修改原版数据、移动玩家控制的球、改变行为图 | 重力修改、关卡数据编辑 |
如何向用户传达风险¶
低风险 mod 通常不需要特别说明。中、高风险 mod 应在发布说明中写清:
- 影响范围:"本 mod 会修改当前关卡的重力参数"
- 是否可逆:"退出关卡后恢复默认值" 或 "修改持久保存,需手动恢复"
- 与其他 mod 的冲突可能:"如果同时使用了修改物理参数的 mod,可能产生冲突"
玩家宁可看到风险警告,也不想在崩溃后才发现问题。
分发渠道¶
Ballance 社区常见的发布平台:
| 渠道 | 适用场景 | 注意事项 |
|---|---|---|
| BallanceBug 论坛 | 中文社区首选 | 帖子中贴安装说明,附件上传 zip |
| Discord(Ballance 服务器) | 国际社区 | 发到对应频道,写英文说明 |
| GitHub Release | 需要版本管理、issue 跟踪 | 适合持续维护的 mod |
| 直接发送文件 | 小范围测试 | 不适合正式发布,缺少版本追溯 |
无论哪个渠道,确保安装路径、BML 版本要求、mod 版本号、使用说明四项信息可见。
发布检查清单¶
按顺序逐项确认:
| # | 检查项 | 通过标准 |
|---|---|---|
| 1 | 入口文件 | 有且只有一个 *.mod.as 文件 |
| 2 | 元数据完整 | id、name、version、author、bml、description 全部填写 |
| 3 | 版本号递增 | 比上一次发布的版本号大 |
| 4 | 无本机路径 | 代码中没有硬编码的绝对路径 |
| 5 | 无调试残留 | 移除了测试命令和过量的日志输出 |
| 6 | 资源引用 | 使用相对路径或通过 BML 接口获取目录 |
| 7 | 配置默认值 | 所有配置项在代码中设了默认值 |
| 8 | 配置迁移 | 改名的配置项有兼容代码 |
| 9 | 清洁安装测试 | 删掉旧配置后首次运行正常 |
| 10 | 流程测试 | 进关卡、退关卡、重启游戏、卸载 mod,各阶段无报错 |
| 11 | 日志检查 | 启动后日志无错误、无警告 |
| 12 | 发布说明 | 安装、命令、配置、卸载、版本要求全部写清 |
| 13 | 风险说明 | 中高风险 mod 写明影响范围 |
| 14 | 打包格式 | 使用 SDK 打包脚本生成 zip,并能直接从 ModLoader/Mods/ 加载 |
常见发布错误¶
忘记更新版本号¶
现象:用户更新后 BML 显示的版本号和上次一样。解决:改代码后紧跟着改 version 字段,形成习惯。
id 被意外修改¶
现象:更新后旧配置失效,ModLoader/Configs/ 下多出新配置文件。原因:id 拼写变了(如 ball.spawner 变成 ballspawner)。解决:发布后 id 视为不可变。
配置依赖本机环境¶
现象:别人下载后首次运行报错,找不到路径或缺少初始值。解决:所有默认值通过 SetDefaultXxx() 在代码中设置,不依赖外部文件。
zip 中的入口数量不正确¶
现象:zip 放入 Mods/ 后 BML 拒绝加载。原因:包内没有入口,或存在多个
*.mod.as 入口。解决:让源目录顶层恰好保留一个入口,并使用 SDK 的
Pack-BMLScriptMod.ps1 重新打包。
残留调试代码¶
现象:用户反馈日志刷屏,影响性能。原因:OnProcess 里留下了每帧 ctx.LogInfo,或者发布包还保留测试命令。解决:发布前搜索 ctx.Log 和命令注册代码,移除调试专用输出。
bml 版本要求过低¶
现象:旧版 BML 用户加载后报"未定义的方法"。原因:用了新 API 但 bml 字段没更新。解决:不确定时就写当前安装的 BML 版本号。
完成状态¶
mod 经过完整的发布流程:元数据正确、版本号递增、清洁测试通过、打包格式正确、发布说明完整。用户可以通过 zip 文件安装并正常使用。
下一步:22 排错索引