Skip to content

过渡到 JavaScript/Java ​

当你写到这里,JSON 已经能解决大部分“内容类”需求,但如果你遇到复杂逻辑、动态行为或 UI 拓展,就需要考虑升级到脚本或 Java。JSON 负责静态配置,JS/Java 负责“行为与联动”。

什么时候应该升级 ​

当你的需求涉及“运行时逻辑”,就该考虑升级。典型场景包括:自定义 AI(例如让单位按你设计的策略巡逻或撤退)、复杂反应(例如“潮湿 + 电击”触发连锁效果)、动态配方(例如根据环境实时改变消耗)、事件监听(例如波次触发特殊效果)、UI 拓展(例如自定义面板)。这些能力在 JSON 中没有对应字段,只能通过脚本或 Java 实现。

另一个明显信号是“模板失效”。当你发现自己反复复制模板,却仍然做不出想要的玩法,那就说明你需要真正的逻辑控制。这时升级反而能省时间。

如果你的需求只是“新增内容、调数值、改配方”,JSON 已经足够。过早升级到脚本或 Java,会让迭代成本上升,反而拖慢速度。把升级当作“解决新问题”的工具,而不是“越高级越好”的目标,会更省力。

行星生成器也是一个典型例子。JSON 只能解析 AsteroidGenerator,如果你需要更复杂的地形或战役结构,就必须用脚本或 Java。比如“饱和火力 3.3.0”在 scripts/planets/泰伯利亚.js 里直接创建行星与区块,绕过 JSON 的限制:

js
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 各个模块,并在同一处做全局补丁:

js
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”的状态脚本里就大量使用了这种写法:

js
const 纳米瘟疫 = extend(StatusEffect, "纳米瘟疫", {
    update(unit, time){
        this.super$update(unit, time);
        // 在这里追加传播或死亡触发逻辑
    }
});

这种写法让你在不写 Java 的情况下,实现“状态自定义逻辑”。当需求还没复杂到需要完整 Java 工程时,它是非常高效的过渡手段。

另一个常见模式是事件驱动,例如 Events.on(EventType.ContentInitEvent, () => { ... })。把内容修改放在事件里,可以确保所有 JSON 内容已经加载完毕,避免出现“对象为空”的情况。对于需要改原版内容或依赖其他模组内容的脚本,这一步尤其重要。

js
Events.on(EventType.WorldLoadEvent, () => {
    // 地图加载后触发,比如重置统计或生成特效
});

事件驱动的好处是“只在需要时运行”,避免每帧更新带来的性能压力。

如果只是想“改几项数值”,可以直接拿到内容对象再修改:

js
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 入口大致是这样:

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 就只是重构而不是重学。很多大型模组其实就是按这条路径成长起来的,选择最小可行复杂度能省下大量维护成本,也能让协作更顺滑,更利于长期维护与持续迭代,长期更省心也更可控、更踏实、更安心、更省力、更稳当一些。