JSON模组教程-过渡到JavaScript/Java
过渡到 JavaScript/Java
当你写到这里,JSON 已经能解决大部分“内容类”需求,但如果你遇到复杂逻辑、动态行为或 UI 拓展,就需要考虑升级到脚本或 Java。JSON 负责静态配置,JS/Java 负责“行为与联动”。
什么时候应该升级
当你的需求涉及“运行时逻辑”,就该考虑升级。典型场景包括:自定义 AI(例如让单位按你设计的策略巡逻或撤退)、复杂反应(例如“潮湿 + 电击”触发连锁效果)、动态配方(例如根据环境实时改变消耗)、事件监听(例如波次触发特殊效果)、UI 拓展(例如自定义面板)。这些能力在 JSON 中没有对应字段,只能通过脚本或 Java 实现。
另一个明显信号是“模板失效”。当你发现自己反复复制模板,却仍然做不出想要的玩法,那就说明你需要真正的逻辑控制。这时升级反而能省时间。
如果你的需求只是“新增内容、调数值、改配方”,JSON 已经足够。过早升级到脚本或 Java,会让迭代成本上升,反而拖慢速度。把升级当作“解决新问题”的工具,而不是“越高级越好”的目标,会更省力。
行星生成器也是一个典型例子。JSON 只能解析 AsteroidGenerator,如果你需要更复杂的地形或战役结构,就必须用脚本或 Java。比如“饱和火力 3.3.0”在 scripts/planets/泰伯利亚.js 里直接创建行星与区块,绕过 JSON 的限制:
const TBLY = new Planet("泰伯利亚", Planets.sun, 1, 3.3);
TBLY.generator = new SerpuloPlanetGenerator();
const map1pj = new SectorPreset("迫降区", TBLY, 1);
map1pj.difficulty = 2;
这类逻辑完全无法在 JSON 中表达,但在 JS/Java 里只需要几行代码。
JavaScript 路线
JavaScript 的优势是门槛低、无需编译、上手快。你可以保留 JSON 内容文件,只在 scripts/ 里写逻辑,常见入口是 scripts/main.js。脚本适合做小范围增强,例如监听事件、改数值、补逻辑、动态修改内容。对于中小型模组,这已经足够解决很多“缺失的行为”。
需要注意的是,Mindustry 使用 Rhino 作为 JS 引擎,ES6 语法并不完整,调试也不如 Java 方便。脚本规模一旦变大,可维护性会明显下降。因此 JS 更适合“轻量逻辑”:逻辑明确、文件不多、依赖不复杂的场景。
脚本性能也要考虑。JS 如果在 update 里做大量遍历,很容易在大型战斗中拖慢帧率。更稳妥的做法是用事件或定时器触发逻辑,把“每帧操作”降到最低。只要控制好频率,JS 在中小规模模组里完全足够。
如果你选择 JS,建议尽量保持“JSON 负责数据,JS 负责行为”的分工。这样即使以后迁移到 Java,也能保留大部分内容文件。
脚本入口通常是 scripts/main.js,你可以用 require 拆分模块,把单位、方块、状态、行星分别放到子文件,避免一堆逻辑挤在一起。“饱和火力 3.3.0”就是这样组织脚本的,它在入口里集中 require 各个模块,并在同一处做全局补丁:
require("SFitems");
require("SFliquids");
const SFStatusEffects = require("base/status");
require("planets/泰伯利亚");
StatusEffects.burning.init(() => {
// 在这里追加燃烧的联动逻辑
});
这类结构的好处是“模块化 + 可控入口”,当你需要排查问题时,只要从 main.js 追到具体模块即可。
脚本还能直接操作原版对象。例如 StatusEffects.burning 本身就是原版状态,你可以在 JS 中对它追加 init 逻辑或亲和反应。更复杂的情况下,脚本还能通过 Packages 或 Vars 调用 Java 类,这意味着 JS 并不是“独立的一套系统”,而是可以直接触达底层 API。
JS 常用模式
JS 里最常见的内容扩展方式是 extend。它允许你直接继承原版类并重写方法,例如“饱和火力 3.3.0”的状态脚本里就大量使用了这种写法:
const 纳米瘟疫 = extend(StatusEffect, "纳米瘟疫", {
update(unit, time){
this.super$update(unit, time);
// 在这里追加传播或死亡触发逻辑
}
});
这种写法让你在不写 Java 的情况下,实现“状态自定义逻辑”。当需求还没复杂到需要完整 Java 工程时,它是非常高效的过渡手段。
另一个常见模式是事件驱动,例如 Events.on(EventType.ContentInitEvent, () => { ... })。把内容修改放在事件里,可以确保所有 JSON 内容已经加载完毕,避免出现“对象为空”的情况。对于需要改原版内容或依赖其他模组内容的脚本,这一步尤其重要。
Events.on(EventType.WorldLoadEvent, () => {
// 地图加载后触发,比如重置统计或生成特效
});
事件驱动的好处是“只在需要时运行”,避免每帧更新带来的性能压力。
如果只是想“改几项数值”,可以直接拿到内容对象再修改:
const block = Vars.content.getByName(ContentType.block, "mechanical-drill"); block.drillTime = 260;
这种方式非常轻量,但也要注意兼容性,尽量避免对原版做大范围覆盖。
Java 路线
Java 是主流路线,能力完整、性能好、生态成熟。你可以在 Java 中创建自定义 AI、复杂方块逻辑、UI 界面,甚至修改原版规则。它的代价是配置复杂、编译流程更重,但当模组变大时,Java 反而更好维护。
典型结构包含 src/(Java 源码)、assets/(内容、贴图、语言文件等)以及 gradle 与构建脚本(负责构建与打包)。你可以在 assets/ 里保留原有 JSON 与贴图,再在 src/ 中编写逻辑类。这样做能最大化复用已有资源。
Java 的另一个优势是“强类型与可读性”。当逻辑变复杂时,类与接口能帮助你组织结构,避免脚本式代码的混乱。对于多人协作或长期维护的模组,Java 几乎是唯一可持续的选择。
如果你决定走 Java,mod.json 里会出现 java: true 与 main 字段,main 指向入口类(例如 sf.SF)。这意味着“逻辑在 Java 里启动”,但内容资源仍然由 assets/ 提供,JSON 文件依旧有效。很多大型模组其实就是“Java 负责行为、JSON 负责内容”的组合。
一个最小的 Java 入口大致是这样:
public class MyMod extends Mod{
@Override
public void loadContent(){
// 在这里注册自定义内容或逻辑
}
}
你可以在 loadContent() 中创建自定义方块、单位或规则,也可以在其他类中封装更复杂的行为,然后在这里统一加载。
Java 工程化细节
Java 模组通常需要完整的构建流程。你需要 JDK、Gradle 配置,以及清晰的包结构。src/ 里按包名组织代码,assets/ 里保留 JSON、贴图、语言包、音效等资源。只要资源路径不变,JSON 内容仍会像 JS 模组一样被加载。
很多团队会选择“内容仍然写 JSON,逻辑写 Java”的分工方式。这样做的优势是:数值改动不需要重新编译,而复杂行为仍然可以用 Java 实现。对于大型模组,这几乎是默认解。
构建时通常使用 gradlew jar 或类似任务生成 .jar,输出位于 build/libs。打包后的 .jar 会携带 assets/ 资源,因此内容文件的路径保持一致非常重要。构建流程是 Java 路线的门槛,但一旦搭起来,后续维护反而更稳定。
版本更新也是 Java 模组必须面对的问题。游戏版本升级后,API 可能变动,JS 与 Java 都需要调整。保持 mod.json 里的 minGameVersion 与工程依赖同步,能减少“更新后突然无法加载”的风险。
在多人协作时,Java 的优势更明显。类型系统与 IDE 支持能减少误改字段的情况,配合版本控制可以更稳地管理复杂逻辑。即使如此,也建议保留 JSON 作为“数值层”,让平衡调整不必每次都改代码。
推荐的过渡策略
最稳妥的路径是“先 JSON,后 JS,再 Java”。你可以先用 JSON 把内容搭起来,确认玩法是否成立,再用 JS 加一些轻量逻辑。只有当逻辑复杂度继续上升时,再迁移到 Java。这样既能快速验证,也能避免一开始就陷入繁重的构建流程。
过渡时可以按“系统”拆分:基础内容与数值继续放在 JSON,局部行为用 JS 补丁,核心机制再用 Java 重构。这样既保留了可调性,又能逐步建立结构。迁移过程中尽量保持内部名不变,避免存档和研究树被破坏。
迁移时可以遵循一个简单原则:不要一次性重写。先把 JS 中最核心的逻辑迁移到 Java,再逐步把其余脚本替换。这样风险最低,也便于验证每一步是否正确。
在过渡期还有一种很实用的做法:先在 JS/Java 中“拿到 JSON 内容,再做小改动”。例如使用 Vars.content.getByName(ContentType.block, "你的方块") 获取对象,然后只改一两处关键字段。这样既能保持 JSON 的可读性,又能逐步引入脚本逻辑,降低迁移成本。
调试与验证
脚本调试最直接的工具是日志。JS 中可以用 print() 或 Log.info() 打印关键参数,确认逻辑是否触发;Java 则可以用 IDE 的断点或日志。无论哪种方式,都建议先确认“事件是否触发”,再看“数值是否正确”,避免在未触发的情况下盲目改参数。
当你修改了内容或脚本,优先在小地图或空白测试地图里验证,缩小变量。对于内容字段,可以通过 Vars.content 打印对象属性,确保 JSON 的值已经写进对象。对于性能敏感的逻辑,记得在大量单位/建筑场景下测一次,避免上线后卡顿。
JS 的迭代速度通常更快,改完脚本重载模组即可看到效果;Java 则需要重新构建并重启。实际项目里常见做法是“先用 JS 验证逻辑,再移植到 Java”,这样能显著降低试错成本。
为了避免日志刷屏,建议给调试输出加上前缀或开关,只在需要时打印关键数据。这样你既能快速定位问题,也不会干扰正常游玩体验。
调试信息最好集中在少量入口文件里,避免散落在各处导致“看不出是谁在输出”。
常见误区
很多人认为“做 Java 就必须抛弃 JSON”,其实完全没必要。即使使用 Java,大量静态内容仍然适合用 JSON 管理,尤其是物品、方块、单位这类“字段为主”的内容。Java 更像是补足 JSON 的“逻辑缺口”,而不是替代。
另一个误区是“JS 一定比 Java 慢很多”。对简单逻辑来说,性能差异并不明显,真正的问题是可维护性与调试体验。JS 在复杂系统中容易失控,而 Java 的结构性更适合大型模组。
还有人会把 JS 当成“万能补丁”,但有些功能本身依赖 Java 类结构,例如自定义方块渲染器或深度改写 AI。这类需求可以先用 JS 试验思路,但最终仍需要 Java 落地。区分“能用脚本快速试错”与“必须用 Java 才稳”的边界,会让你的迭代更有效。
最后一个常见坑是“脚本覆盖原版内容”。JS 可以直接修改原版对象,这很强,但也容易引发与其他模组的冲突。除非明确要做全局改写,否则更推荐“新增内容而非覆盖”,这样兼容性会好很多。
小结
JSON 是入口,Java 是天花板。JS 可以作为过渡工具,而 Java 适合做大型模组与复杂系统。如果你已经能熟练读源码、查字段,那么转向 Java 只是时间问题。最重要的是保持分层思维:能用 JSON 就别急着上脚本,必须要逻辑时再升级,这样成本最低。保持模块化与文档习惯,过渡过程会更顺畅。当你能用脚本写出完整系统时,转向 Java 就只是重构而不是重学。很多大型模组其实就是按这条路径成长起来的,选择最小可行复杂度能省下大量维护成本,也能让协作更顺滑,更利于长期维护与持续迭代,长期更省心也更可控、更踏实、更安心、更省力、更稳当一些。
