
简介一份面向Mindustry 6.0模组开发者的JavaScript工具包专注于解决自定义合成器配置繁琐的问题目标是让配方注册过程更直观、更省代码。脚本通过暴露newCrafter接口让开发者以配置对象方式声明输入物品、液体、数量及输出配方并支持物品与液体混合输入、多选一等灵活模式显著降低MultiCrafter功能的开发门槛。压缩包内共2个文件包含一个JavaScript脚本和一份Markdown说明文档整体仅4KB轻量且便于直接嵌入现有mod工程。说明文档提供了在mod入口脚本中引入模块、设置this.window到调用newCrafter的完整示例分别对铜、铅等基础资源组合输入进行了演示并解释了amount、rdminput等关键字段的含义适合已有一定JS基础、希望快速上手MultiCrafter开发的模组作者迁移使用。目前已有166人学习下载是扩展Mindustry合成系统、自定义多步配方时可直接参考的轻量脚本。 我最早接触到 MultiCrafter 这个 mod是在 Mindustry 6.0 的创意工坊里翻配方表的时候。当时就觉得这玩意儿思路很有意思——它不往游戏里硬塞一堆新方块而是给你一个自定义合成配方的方法。后来自己上手用 JS 给 MultiCrafter 写定制逻辑踩了不少坑也摸出了一些门道。这篇东西就围绕“MultiCrafter Mindustry 6.0 JS”这条线把整个项目的核心思路、实操步骤、还有我实际踩过的坑一次性讲清楚。先说清楚这个标题组合到底在做什么。Mindustry 6.0 的 mod 系统支持 Java 和 JavaScript 两种语言JS 的优势是启动快、热重载方便、对底层不熟的玩家也能快速上手。MultiCrafter 这个 mod 本身提供了一个名为MultiCrafter.Crafter的方块类型它允许你在不写 Java 代码的前提下通过 JSON 或 JS 定义自己的多功能合成器。把这两者结合起来就是你用一段 JS 脚本在游戏运行时动态注册一个全新的 MultiCrafter 合成器方块它的配方、输入输出、耗电、液体消耗全部由你的 JS 代码决定。这篇文章适合两类人一类是想给自己的 Mindustry 自定义 mod 加“可编程生产”玩法的玩家另一类是刚接触游戏 mod 开发、想知道 JS 在 Mod 体系里能干什么的新手。1. 核心思路拆解为什么是 MultiCrafter为什么用 JS1.1 MultiCrafter 解决什么问题原版 Mindustry 的合成方块比如普通的制造机、冶炼厂每一个的配方都是写死在游戏代码里的。如果你想加一个新配方要么改游戏本体要么用 Java 写一个完整的新 mod 方块。这两种方式都有门槛尤其是 Java 写 mod涉及构建环境、依赖管理、打包流程对很多玩家来说太重了。MultiCrafter 的思路是“把配方数据化”。它只做一个通用型的合成方块这个方块本身没有固定配方配方全部由外部定义。你可以在这个方块上配置任意多个合成配方每个配方指定输入物品、输出物品、需要消耗的液体、电力、合成时间甚至还可以选择闲置时是否持续耗电、是否显示自定义图标。这样一来加新内容就变成了“填数据”而不是“写代码”。1.2 JS 在这套体系里的定位Mindustry 6.0 的 mod 支持通过assets/scripts目录下的.js文件来扩展游戏逻辑。游戏使用 Rhino 作为 JS 引擎允许脚本直接访问 Java 层的类和方法。这意味着 JS 能做的很多事Java 也能做但 JS 的好处是修改后重启游戏即可生效不需要重新编译、打包像调试配方、调节参数这种事情效率高得多。MultiCrafter 的配方定义本质上就是数据JS 的优势恰好在于“生成数据”。你可以用 for 循环批量生成一堆配方可以用函数动态生成不同等级的合成方块还可以根据玩家设定的全局变量调整配方内容。这在原版 Java 代码里写起来啰嗦的事情JS 写起来几行就完事。所以“MultiCrafter 6.0 mod JS”这个组合本质是“一个数据化模组 一个灵活脚本语言”的配合。1.3 适合什么场景如果只是想在原版基础上加一两个新配方MultiCrafter 配上 JSON 就够用了。但是一旦你想做以下事情JS 就派上用场了配方数量多手动填 JSON 容易出错。需要同一方块在不同阶段比如科技树升级后有不同的配方集合。想让配方和玩家某个行为联动比如杀敌数量、资源总量等。想给不同地图、不同模式提供不同的配方配置文件。我实际做的最多的是第三种——动态生成配方表。比如做一个“进阶熔炉”根据玩家当前解锁的科技等级决定它可以把哪些矿物熔成合金。这种逻辑用 JSON 写会很死板但 JS 写起来就是循环遍历 条件判断的事。2. 环境准备与 JS Mod 工程搭建2.1 Mindustry 6.0 Mod 的基础目录结构先放一个标准的 JS Mod 目录结构这是我在实际项目中稳定使用的MyMod/ ├── mod.json ├── content/ │ └── items/ │ └── my-item.json └── assets/ └── scripts/ ├── main.js └── multiCrafter.jsmod.json是 mod 的元信息文件里面声明了 mod 名称、版本、依赖等等。这里特别注意如果你要用 MultiCrafter需要在mod.json里声明依赖{ name: my-multi-crafter-mod, displayName: My Multi Crafter Mod, author: yourName, version: 1.0.0, minGameVersion: 6.0, dependencies: [multi-crafter] }dependencies字段写的是 MultiCrafter mod 的 mod 名不是显示名。如果这个名字写错游戏加载时会提示找不到依赖甚至直接不加载你的 mod。我第一次就挂在这一步后面会细讲。2.2 JS 脚本的加载机制Mindustry 启动时会扫描 mod 的assets/scripts目录按文件名顺序加载.js文件。main.js通常作为入口在脚本加载完成后会自动执行。更准确地说是脚本文件中的所有顶层代码都会执行所以你可以在main.js里直接写注册逻辑也可以定义一个load()函数mod 加载时会自动调用。我需要强调一点Mindustry 的 JS 是基于 Rhino 的它支持大多数 ES5、部分 ES6 语法但有一些高级语法比如let在某些版本上的块级作用域处理、class关键字支持度需要小心。我基本只写 ES5 风格的函数和var在 6.0 环境里最稳。别跟浏览器端的 JS 语法较劲能用就行。2.3 引入 MultiCrafter 类MultiCrafter 的 JS 脚本里暴露了MultiCrafter对象。你需要在main.js里先获取它之后才能注册方块。获取方式大概是const multiCrafter require(multi-crafter);注意这里require是 Mindustry 提供的模块加载函数参数是依赖 mod 的名称。如果你在mod.json里声明的依赖名跟这里不一致脚本会报Cannot find module之类的错误。拿到这个对象之后就可以使用它的核心类MultiCrafter.Crafter了。这里我补充一个常识——Mindustry 中创建新内容是靠内容注册器Events.on(ContentInitEvent)或直接在主脚本里调用content相关方法。MultiCrafter 的注册逻辑也是在加载阶段完成的所以你在load()里注册方块是正确时机。3. 核心实操用 JS 注册一个可用的 MultiCrafter 方块3.1 最简配方方块下面这个例子是“最简可用”的实验目的是跑通整个流程。我们创建一个名为“test-crafter”的方块它消耗铜和铅产出硅耗时 60 tick游戏秒是 60 tick。var mcrafter require(multi-crafter); var testCrafter new mcrafter.Crafter(test-crafter); testCrafter.setRecipes([ { input: { items: { copper: 2, lead: 1 } }, output: { items: { silicon: 2 } }, craftTime: 60, powerConsume: 0.2 } ]);这段代码段的核心在new mcrafter.Crafter(test-crafter)。第一个参数是方块 ID会在游戏内以test-crafter形式显示。这个 ID 必须唯一不能和已有内容冲突。setRecipes方法接收一个数组数组里每个元素就是一个配方。配方的字段我用的是常见的input、output、craftTime、powerConsume格式。这是 MultiCrafter 官方脚本中最常见的结构。如果你这是第一次跑建议只放一个配方然后在游戏里用沙盒模式放置这个方块看看能不能正常合成。如果方块显示“空的”或者没有配方列表多半是配方格式不对或者 mod 没正常加载。3.2 带液体和电力的复杂配方MultiCrafter 也支持液体输入输出和电力消耗。制作合金类方块时经常需要消耗水或冷却液下面这个示例演示了如何配置var mcrafter require(multi-crafter); var liquidCrafter new mcrafter.Crafter(liquid-crafter); liquidCrafter.setRecipes([ { input: { items: { titanium: 2 }, liquids: { water: 10 } }, output: { items: { thorium: 1 }, liquids: { slag: 5 } }, craftTime: 120, powerConsume: 0.5 } ]);这里的liquids对象键是液体名称值是一次合成消耗或产出的液体单位。需要注意output.liquids表示产生液体如果产出的液体没有排出渠道方块会堵住提示“输出口堵塞”。这个跟原版冶炼厂的行为很像。powerConsume的单位是电力/秒power units per second。Mindustry 6.0 中绝大多数工厂类方块的耗电在 0.1~1 power/s 之间。如果你不打算耗电完全可以不传这个字段默认就是 0。3.3 动态生成多配方JS 的杀手锏场景前面说过JS 比 JSON 强的地方在动态生成。比如我想做一个“矿粉混合器”它能处理任意两种矿石按照比例生成对应的混合矿粉。手动写配方可能几十个但用 JS 遍历就不一样了var mcrafter require(multi-crafter); var oreList [copper, lead, titanium, thorium]; var recipes []; oreList.forEach(function(oreA, i) { oreList.forEach(function(oreB, j) { if (i j) return; // 避免重复 recipes.push({ input: { items: {} }, output: { items: {} }, craftTime: 30 }); recipes[recipes.length - 1].input.items[oreA] 1; recipes[recipes.length - 1].input.items[oreB] 1; recipes[recipes.length - 1].output.items[oreA - oreB -mix] 2; }); }); var mixer new mcrafter.Crafter(ore-mixer); mixer.setRecipes(recipes);这个例子演示了一个重要操作input.items和output.items是一个对象对象里的键是物品 ID值是数量。你可以先创建空对象再动态往里面加键值对。这比手写一张大 JSON 表要清晰得多而且不容易漏项。当然上面例子里的oreA - oreB -mix需要你预先在content/items里定义这些物品否则游戏加载时会报“unknown item”错误。动态配方的前提是物品 ID 都是真实存在的。3.4 用属性给方块做视觉/行为定制MultiCrafter 还支持很多外观和行为参数我常用的几个size方块尺寸默认 2表示 2x2。health血量默认 160。category在建造菜单中的分类一般是crafting。requirements建造所需材料数组格式比如[Item.copper, 50, Item.lead, 20]。buildVisibility可建造范围sandboxOnly或shootingOnly等。示例var mcrafter require(multi-crafter); var advCrafter new mcrafter.Crafter(advanced-crafter); advCrafter.size 3; advCrafter.health 600; advCrafter.category crafting; advCrafter.requirements [Item.copper, 100, Item.lead, 50, Item.silicon, 25]; advCrafter.setRecipes([ { input: { items: { titanium: 3 } }, output: { items: { thorium: 1 } }, craftTime: 90, powerConsume: 0.4 } ]);这里Item.copper是 Mindustry 内置的物品枚举值不用你自己去字符串化。category决定它在建造菜单里出现在哪个标签下写错会导致找不到方块。我一般固定用crafting稳妥。4. 常见问题与排查技巧实录4.1 配方不生效方块显示为空这是我遇到最多的问题。三种最常见的原因mod 没正常加载去主菜单的 Mod 列表里看你的 mod 是否亮着有没有红色的错误标识。如果依赖 MultiCrafter确认 MultiCrafter 已启用且版本兼容 6.0。配方格式错误MultiCrafter 对配方的字段名比较严格input写错成inputs、craftTime写错成time都会导致配方被丢弃。它不会报错只会让你看到空配方列表。物品 ID 不存在如果配方里引用了不存在的物品 ID加载时会报Item not found: xxx这样的错误。打开日志看一下游戏崩溃窗口或 stdout很容易定位。我给一个自查表方便运行时快速定位现象可能原因排查方法方块在菜单里找不到category写错或未注册成功检查脚本加载日志方块存在但没有配方列表setRecipes未调用或传了空数组检查配方格式与物品 ID点击配方无法开始合成输入物品数量或液体不足检查输入配置、液体管道连接合成中卡住不产出输出口被堵检查输出物品/液体是否有空间排出脚本报错Cannot load modmod.json语法或依赖名错误检查 JSON 格式、依赖名是否匹配4.2 JS 代码里 API 名称混淆MultiCrafter 在不同版本中的 API 有过微调。我用的版本是 6.0 早期版本Crafter类名和setRecipes方法名是稳定的。但如果你用的 MultiCrafter 是从创意工坊下载的最新版建议去 mod 的源码头文件里翻一下getRecipes、setRecipes这些方法名有没有变化。有一个小技巧在游戏内启动时按住 Shift 打开日志或者在主菜单直接运行脚本测试。你可以用print()函数输出调试信息到控制台比如打印testCrafter是否有配方print(recipes: testCrafter.getRecipes().length);print是 Mindustry 的全局函数输出会显示在日志窗口。如果输出的数字是 0就说明配方没按预期写入。4.3 依赖和版本兼容问题Mindustry 6.0 的 mod 依赖声明在 6.0 版本还不完善有时候你声明了dependencies但游戏不会强制要求用户启用依赖 mod。结果就是用户没有启用 MultiCrafter你的脚本直接require失败整个 mod 加载失败。一个稳妥的做法是在main.js最前面判断MultiCrafter是否存在if (typeof MultiCrafter undefined) { Log.info(MultiCrafter not found, disabling mod parts.); return; }实际项目里我倾向于把 MultiCrafter 相关的注册逻辑放到一个独立文件比如multiCrafter.js然后在main.js里用require(multiCrafter.js)引入这样如果 MultiCrafter 不存在错误范围被限制不影响其他脚本运行。4.4 JS 语法和 Rhino 引擎的坑Mindustry 6.0 使用的 Rhino 版本不是最新很多 ES6、ES2015 语法可能有问题。我遇到过的const在部分版本中报错建议统一用var。箭头函数${}模板字符串部分版本支持但保险起见我都是字符串拼接。Array.includes可能不存在需要用indexOf替代。Object.keys、forEach这些是安全的放心用。我个人的代码规范是在 Mindustry 里写 JS把它当成“带函数的 Java”来写不用太花哨的语法。这样最省心排查问题也快。4.5 热重载注意事项Mindustry 在加载 mod 时会执行脚本但如果你的脚本修改了在创意工坊 Mod 列表里直接重新加载 mod 的体验其实不太稳定。我强烈建议做法是每次修改 JS 后重启游戏而不是依赖热重载。虽然热重载对部分内容有效但对 MultiCrafter 这种向 game content 注册新方块的逻辑来说热重载容易导致重复注册出现两个同 ID 的方块甚至让存档坏掉。我有一次连续热重载了三次结果存档里出现一堆名字带有(1)后缀的重复方块只能手动拆掉重建非常麻烦。从那之后我老老实实重启游戏。5. 从“能用”到“好用”我积累的几个经验5.1 用数值驱动方式管理大量配方当你需要管理上百个配方时硬编码setRecipes里的数组会变得极长难以维护。我习惯把配方数据拆成独立 JS 对象按物品类型分组然后用一个函数统一处理。比如这样var recipes { base: [ { input: {copper:1}, output: {silicon:1}, time: 30 }, { input: {lead:1}, output: {graphite:1}, time: 30 } ], advanced: [ { input: {titanium:2}, output: {thorium:1}, time: 90 } ] }; function buildRecipes(category) { return recipes[category].map(function(r) { return { input: { items: r.input }, output: { items: r.output }, craftTime: r.time }; }); }这样你只需要维护最底层的物品数量映射不用担心 MultiCrafter 的格式细节。5.2 善用脚本检查物品 ID物品 ID 写错是新手最多的问题。我写了一个小工具脚本在游戏里打印出所有已注册的物品 IDVars.content.items().each(function(item) { print(item.name ); });这个脚本在main.js里执行一次即可输出所有物品 ID方便核对拼写。不要靠记忆直接看输出最稳妥。5.3 多写日志少猜问题Mindustry 的print输出会显示在主菜单左下角的日志区域也可以在玩家目录里的日志文件中查看。我写 JS 代码的习惯是多重print比如在注册完成后打印“crafter registered”以及配方数量这样一旦出问题能快速定位是哪一步没执行。5.4 关于存档兼容性有一点特别重要如果你在开发过程中修改了方块 ID、配方数量、甚至方块尺寸旧存档里的方块可能会出现问题。MultiCrafter 方块的数据是保存在存档里的ID 变了会对不上。所以我在开发阶段尽量固定方块 ID不随意改。如果真的改了 ID就手动拆掉旧方块重建。别指望旧自动迁移——不是官方机制容易出意外。6. 扩展思路除了合成器还能做什么MultiCrafter 虽然主打“自定义合成”但它的思路可以迁移到其他地方。我这里提供几个我觉得很有意思的扩展方向连锁合成定义一系列合成器A 的输出是 B 的输入用 JS 自动生成整条生产链。科学装置用 MultiCrafter 做“研究站”消耗一种稀有资源产出研究点数物品再配合自定义科技树逻辑。物流辅助设备比如把多种液体转为固体解决管道输出拥堵问题。另外MultiCrafter 可以和别的 mod 联动。比如你定义了新物品其他 mod 的配方里如果引用了这些物品也能正常工作。JS 脚本可以读取其他 mod 的类所以理论上可以做跨 mod 内容整合但因为不同 mod 的 API 不同难度会大很多。如果说有什么建议那就是先把基础版跑通再想花活。MultiCrafter 的文档不算完善很多参数我是靠反编译源码才看明白的。如果你遇到官方文档没有的字段直接在 mod 的 jar 文件里找 class 文件用反编译工具看看Crafter类的字段定义比在网上搜答案快得多。最后分享一个小技巧在写 MultiCrafter 配方的时候craftTime不要拍脑袋定建议先用原版类似方块的合成时间做参考。比如原版制造台合成硅是 30 tick你做同类配方也设为 30玩家会觉得节奏正常如果你设成 5一来破坏平衡二来显得不自然。我一开始把高级合金设成 600 tick结果玩家抱怨等太久了后来改成 150 tick反馈好很多。模组数值设计上贴近原版手感永远是第一原则。本文还有配套的精品资源点击获取