简介:面向Mindustry 6.0模组开发者的MultiCrafter工具包,以JavaScript脚本形式提供自定义多方块合成器的快速实现方案。脚本封装了newCrafter()注册接口,支持配置输入物品/液体槽位、数量以及“任意一种输入即可”或全部满足等判定模式,大幅简化原先繁琐的合成逻辑;配套的README说明文档给出在main.js中启用模块并调用该接口的典型示例,适合有基础JS语法、希望为Mindustry添加自定义内容的玩家。资源整体仅2个文件,分别为js脚本和md说明,压缩包大小4KB,轻量易用。目前已有166人学习,可帮助模组作者快速上手自定义合成配方设计,同时理解Mindustry mod脚本的基本组织与依赖调用方式,减少反复调试底层接口的成本。 说个真实的场景:我在Mindustry里造产线,想把铜矿和铅矿同时送进一台机器,出口出两个成品带一点废渣。原版的熔炉最多给你两个物品进、一个物品出,液体更是只有一条路,想要"两进两出"甚至"三进三出",游戏自带的合成器根本做不出来。后来我在社区挖到了MultiCrafter这个mod,它是专门为Mindustry 6.0准备的一把"合成器扩展钥匙",底层用Java实现,但对mod作者开放的是JavaScript接口,也就是你经常听到的"mod的js"那部分。装好之后,你可以通过脚本或配置文件批量定义多输入多输出的方块和配方,再配合JS写点判断逻辑,就能做出一台完全自定义的工业机器。
这篇文章不是翻译文档,是我实际用MultiCrafter写了几个mod之后攒下来的经验。从mod.json怎么配、JS脚本怎么挂,到配方为什么不生效、贴图为什么乱转,我都会按顺序讲清楚。适合两类人看:一类是想给Mindustry写mod但没摸过脚本的新手,另一类是已经写过mod但被原版合成逻辑卡住的进阶玩家。
1. 先搞懂MultiCrafter到底解决了什么问题
1.1 原版合成逻辑的边界在哪
在Mindustry 6.0里,GenericCrafter几乎是最常用的生产方块类型,熔炉、硅冶炼厂、塑料加工机全是它的子类。它的基础逻辑很简单:满足输入条件后,按craftTime计算进度,进度满了就生成输出。听起来没问题,但原版对输出的处理非常克制——成品只有一个物品槽位,液体输出也只有一条管线,想再加一条副产物输出,你得自己改build逻辑,这对纯JS玩家来说门槛不低。
有人会说"那就多叠几台机器嘛"。但实际部署工厂时,产线最怕的就是副产品:主产物进总线,副产物卡在出口没人取,整台机器就会堵住,后面的工序全部停摆。所以mod里做"多输出"不是花活,是产线设计里的刚需。我在不少mod社区都见过类似的求助帖,最后都是用MultiCrafter这类方案解决的。
1.2 MultiCrafter的核心能力
MultiCrafter的思路很直接:把"配方"从代码里抽出来,变成一份份配置数据。你定义一个方块,再给它挂一组配方,每个配方可以拆成任意多个输入物品、输入液体、输出物品、输出液体,工艺时间、合成特效、条件约束都可以独立配。用JS读取和切换这些配方也方便,不需要碰底层Java。
这样一来,一台机器就不再是"输入A+输入B -> 输出C"的固定公式,而是一张可以随时换的加工菜单。比如同一个精炼炉,白天炼铅,晚上炼铜,只要在JS里根据时间判断切换配方就行。我用一句话总结MultiCrafter的价值:它把"能不能做多产物"变成了"想不想做多产物"。
为了让你直观理解原版和它的差距,我整理了一个对比表:
| 能力 | 原版GenericCrafter | MultiCrafter |
|---|---|---|
| 输入物品数 | 1-2种 | 多种,自定义 |
| 输入液体数 | 1种 | 多种,可同时 |
| 输出物品数 | 1种 | 多种,自动入槽 |
| 输出液体数 | 1种 | 多种,可独立管线 |
| 配方切换 | 需改代码 | 配置文件/JS动态切换 |
| 副产品处理 | 容易堵住 | 独立输出槽,不互堵 |
2. 开工前要准备的环境与工程结构
2.1 mod.json里最容易踩坑的几个字段
在6.0里,mod固定在mods目录下,一个mod就是一个文件夹。首先要写mod.json,它相当于mod的身份证。我踩过的一个坑是:mod怎么都加载不进来,最后发现是name里用了中文——Mindustry对mod的name要求必须是英文字符,否则依赖系统直接认不出。另外,dependencies字段要写MultiCrafter的mod name,不是显示名,装完MultiCrafter后先打开它的mod.json核对,再写进自己的依赖里。
{ "name": "advanced-refinery", "displayName": "Advanced Refinery", "author": "YourName", "description": "基于MultiCrafter的多产物精炼工厂", "version": "1.0.0", "minGameVersion": "120", "dependencies": ["multi-crafter"] }关键点有两个。一是minGameVersion填120,对应6.0系列,别为了装新填太高,否则6.0会报版本过低直接拒绝加载。二是name字段一旦写错,后续依赖和脚本里的require都会跟着出错,所以mod.json的每个字段我都建议在游戏主界面的"Mods"面板里看一眼加载日志确认。
2.2 目录结构与脚本入口
标准的JS mod结构长这样:
advanced-refinery/ ├── mod.json ├── icon.png ├── content/ │ └── blocks/ └── scripts/ ├── main.js └── lib.jsscripts/main.js是Mindustry固定加载的入口文件。如果想拆成多个文件,不要用import,Rhino环境不支持ESModule,而是用Mindustry提供的require("lib"),它会从scripts目录下加载同名js文件。我拆文件的原则很简单:凡是超过200行的逻辑就单独抽文件,入口只保留require和初始化调用,省得后期改配方时在几百行里翻。
调试这块,Mindustry没有独立的脚本控制台,你在JS里写print("hello"),输出会出现在游戏启动器或终端的日志流里,而不是游戏内聊天框。我调试配方的习惯是每个关键节点都print一行,比如配方ID、输出物品数量、当前进度,宁可多打几行,也不去猜问题在哪。
3. 核心实操:装配第一台多输入多输出合成机
3.1 注册方块并绑定MultiCrafter
我先放一个完整可跑的例子。假设我的mod叫advanced-refinery,要做一台3x3大小的"等离子精炼厂",输入铅矿和水,输出铜和矿渣。步骤分两段:第一段在main.js里注册方块,第二段给这个方块挂配制方。
// scripts/main.js const Refinery = extendContent(GenericCrafter, "plasma-refinery", {}); Refinery.size = 3; Refinery.health = 640; Refinery.craftTime = 80; Refinery.hasItems = true; Refinery.hasLiquids = true; Refinery.liquidCapacity = 30; Refinery.craftEffect = Fx.smeltsmoke; Refinery.updateEffect = Fx.plasticburn; Refinery.requirements = ItemStack.with(Items.copper, 200, Items.lead, 120, Items.titanium, 80); Refinery.buildType = () => new ObjectCraftBuild();这段是纯Mindustry 6.0的API,逻辑很直白:extendContent复制一个GenericCrafter的类型,然后改数值。注意最后一行buildType,6.0里如果你不显式返回构建类型,部分自定义方块会没有实际能建造的build实例,这是新手最容易漏的一步,漏了之后方块在沙盒里根本摆不出来。
3.2 用文件或JS注入配方
挂配方这一步,不同版本的MultiCrafter API略有差异。有的版本支持在content目录下放独立配置文件,有的是在JS里调MultiCrafter.addRecipe。我以最常见的JS方式为例,如果是用配置文件,字段名也差不多,对着mod自带的sample改就行:
const MultiCrafter = require("multi-crafter"); MultiCrafter.addRecipe("plasma-refinery", { craftTime: 80, inputs: [ { type: "item", item: Items.lead, amount: 2 }, { type: "liquid", liquid: Liquids.water, amount: 10 } ], outputs: [ { type: "item", item: Items.copper, amount: 1 }, { type: "item", item: Items.slag, amount: 4 } ] });这里要理解一件事:MultiCrafter其实是接管了GenericCrafter的build逻辑,输入缓存、进度、输出缓存全由它自己的build类处理,配方只是数据源。所以它能做到多输出且不会像原版那样把副产品卡在出口,因为它会把副产物按顺序推进输出槽,而不是只留一个outputItem位。
3.3 验证配方是否生效
在游戏里建好方块后,先用沙盒模式刷一台出来。确认三件事:方块能正常建造,不会被红字报错;输入带和输出带都能识别物品;合成进度跑完,主产物和副产物都出来。我把这个流程称为"三查",任何mod机器都应该先过这三关再继续调数值。如果发现进度跑满但没输出,优先怀疑输出槽满了,或者输出物品ID和实际物品对不上,这两种情况的表现完全不一样,前者进度卡住,后者直接跳回原位。
4. 进阶:用JS动态控制配方与行为
4.1 在第三方mod基础上加入自定义逻辑
MultiCrafter当依赖用很简单,但它的方块默认是"按配方表加工"的通用逻辑,真正的灵活要写到JS里。比如我想做一个条件配方:白天产物A,晚上产物B。可以给方块自定义build类型,在update里切换。代码大致是:
Refinery.buildType = () => extendContent(GenericCrafter.GenericCrafterBuild, Refinery, { update() { this.super$update(); if (Vars.state.isDay()) { // 切到白天的配方 } else { // 切到晚上的配方 } } });super$update()是Mindustry的JavaAdapter里调用父类方法的固定写法,和Java里的super.update()一个意思。在6.0的Rhino环境里,自定义方法名如果和父类重名,必须用super$方法名来调用父类实现,否则会无限递归直接崩。这个坑我踩过一次,当时日志刷得全是StackOverflow,排查半天才发现是父类调用方式写错了。
4.2 用Map管理多配方
当你有一堆配方要管理时,别用一串if/else。JS里的Map或普通对象配得好,就能当"配方注册表"用:
const recipes = { day: { craftTime: 60, output: Items.copper, amount: 2 }, night: { craftTime: 45, output: Items.lead, amount: 3 } }; function getRecipeForBuild(build) { return Vars.state.isDay() ? recipes.day : recipes.night; }这里的思路是:配方选择逻辑和配方数据分离。后续想加新配方、改数值,只需要维护recipes对象就行。在调试时print一下getRecipeForBuild的结果,就能快速确认切换逻辑对不对。我在实际项目里一般会把配方分组,然后用一个switch对象映射条件到配方组,这样写起来比长篇if/else清爽得多。
4.3 性能:JS别做重活
Mindustry 6.0的JS跑在Rhino上,性能比Java调用差不少。如果每一帧都在update里遍历大量物品、频繁创建对象,后期方块一多,帧数会肉眼可见地掉。我给自己定了三条原则:能配置化的不写代码,普通配方全交给MultiCrafter的配置表;动态逻辑尽量用局部变量,避免在update里new数组;事件型逻辑放onBuild、onDestroy里,别每帧轮询。
比如"附近有冷却液就加速"这种判断,完全可以用build.nearby每几tick检查一次,不需要每帧遍历。我见过有些mod作者把每帧做的事情堆得又重又多,结果几十台机器一放,游戏直接变PPT,这其实不是Mindustry的问题,是JS侧该做的优化没做。
5. 常见问题与排查技巧实录
5.1 配方不生效,先查四个点
MultiCrafter的配方不生效,我遇到的10次里有9次是下面四个原因:
| 现象 | 原因 | 解决 |
|---|---|---|
| 方块建出来没有配方 | 方块ID和addRecipe第一个参数不一致 | 两边用同一个ID字符串 |
| 输入带不识别物品 | 物品ID拼写错误或未在mod中定义 | 检查是否存在该物品,用Items.xxx |
| 进度卡在99% | 输出槽满了或输出物品无法进入缓存 | 接上输出带或扩大输出缓存 |
| 游戏里读不到mod | dependencies里的name与MultiCrafter实际name不一致 | 打开MultiCrafter的mod.json核对 |
排查的时候我习惯先看游戏日志,比盲改配置快得多。日志里如果出现ScriptError相关的红字,直接把报错行号对应到main.js对应位置,基本都能定位到具体是哪个字段写错。
5.2 贴图方向错乱和特效消失
自定义方块没贴图,或者贴图方向不对,通常是region命名问题。Mindustry加载贴图时按"方块名+方向后缀"找资源,比如plasma-refinery.png是正面图,team后缀的贴图用于队伍着色。如果mod里没有对应贴图,方块会显示成紫色或直接没有模型。特效不显示则先检查craftEffect和updateEffect是否引用了存在的Fx,再换成Fx.none排除干扰,一步步缩小范围。
我只能说,贴图问题虽然不致命,但非常影响观感。我自己习惯先随便拿一张游戏内置贴图复制改名占位,确认逻辑跑通之后再请人画正式素材,这样不会因为美术资源没到位就卡住开发。
5.3 语法与加载顺序
6.0的Rhino版本较老,我在写脚本时踩过两次语法坑:一是let在某些小版本下声明不生效,二是箭头函数在部分环境里报错。稳妥做法是用var和function,所有变量尽量在函数顶层定义。另外,多脚本文件之间用require有先后顺序,如果B文件用到了A文件里定义的东西,一定要在B开头require("A"),不要依赖加载顺序的巧合,否则偶尔能跑、偶尔报错,排查起来非常折磨。
最后再分享一个我自己的使用习惯:在沙盒模式下用Vars.state.rules关掉敌人和科技锁,专心调试机器;等配方稳定了再开生存档正式拉产线,能省下大量重复读档的时间。从我用MultiCrafter写的那几个mod来看,最值钱的地方不是"多一个方块",而是把配方的复杂度从代码转移到了数据,写mod写到后面,你维护的其实是配方表和一小撮逻辑,而不是一大团if/else。
本文还有配套的精品资源,点击获取