简介:forge-1.20.1是适用于Minecraft 1.20.1版本的Forge模组开发工具包(MDK),目标用户是希望从零开始学习模组开发的玩家与程序员,可用于快速搭建标准Mod工程,解决环境配置、依赖管理、代码编译与打包发布等核心问题。压缩包共包含17个文件,整体仅111KB,内部以Gradle构建脚本、属性配置、Java源码示例、文本文档为主,同时提供跨平台启动脚本,以及版本控制辅助文件,并附有说明、变更、致谢等文本资料,基本覆盖模组开发工程的关键组成部分。其中Gradle配置定义了构建逻辑与依赖关系,属性文件用于调整构建参数,Java源码提供Mod入口示例,文本文档记录许可、变更与致谢信息。目前已有1470人学习下载,适合正在入门Minecraft模组开发并需要一份完整工程参考的爱好者。读者可以借助这份资源理解跨平台构建原理,学习通过构建脚本声明依赖、调整构建参数,参考Java示例代码了解Mod主类与资源目录组织方式,并通过变更日志掌握版本变化,结合致谢名单认识社区贡献,为独立开发自己的模组奠定坚实基础。
1. 拿到 forge-1.20.1 整合开发包:从「一堆文件」到「能跑起来的模组」
如果你下载过 MC Forge 的模组开发包,大概率经历过这种状态:解压后对着几十个文件夹和 Gradle 脚本发懵,不知道哪个是入口、哪个能删、哪个改了会炸。这个 forge-1.20.1 资源包其实是一套整理好的 Forge 1.20.1 模组开发脚手架,核心价值不是给你一个能玩的客户端,而是把「环境变量怎么配、主类怎么写、物品怎么注册、资源文件放哪」这些散落在官方文档和论坛里的信息压缩成一棵结构清晰的工程树。适合三类人:第一次接触 Forge 开发的入门者、想快速搭一个 1.20.1 模组骨架的熟手、以及被 Gradle 依赖和混淆映射折腾到想放弃的摸鱼选手。接下来我会按「环境搭起来 → 注册第一个物品 → 让客户端认你的资源 → 踩坑复盘 → 加一个配置开关」的顺序,把这套东西的每个角落都拆开讲一遍。
2. 开发环境搭建:MDK 目录结构与 Gradle 配置逐项拆解
2.1 解压之后先认目录:哪些文件是命根子,哪些可以无视
拿到 forge-1.20.1 资源包,第一件事不是双击任何脚本,而是先花十分钟把目录结构认全。常见的 MDK(Mod Development Kit)解压后大致是这样的:
forge-1.20.1/ ├── build.gradle # 构建脚本,定义依赖和插件 ├── gradle.properties # 版本号和模组元信息 ├── settings.gradle # 项目名称和仓库配置 ├── gradlew.bat # Windows 下的 Gradle 启动器 ├── gradlew # Linux/macOS 下的 Gradle 启动器 ├── gradle/ │ └── wrapper/ │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── src/main/java/ # 你的 Java 代码都在这 ├── src/main/resources/ # 资源文件、META-INF、配方、模型 └── src/test/java/ # 测试代码,通常用不到这段目录结构里最容易被忽视的是gradle/wrapper/gradle-wrapper.properties,它锁定了 Gradle 的发行版本。Forge 1.20.1 官方推荐的是 Gradle 8.x 系列,如果你本机装了别的版本,gradlew会按照这个文件里的配置自动下载对应版本。注意,这个文件里的distributionUrl指向的是 Gradle 官方服务器,国内网络环境经常下载超时,后面避坑章我会讲怎么换镜像。
gradle.properties是另一个重点,它通常长这样:
org.gradle.jvmargs=-Xmx4G -XX:MaxPermSize=1G org.gradle.daemon=true org.gradle.parallel=true minecraft_version=1.20.1 forge_version=47.1.3 mappings_channel=official mappings_version=1.20.1对于这个文件里的四个关键参数,我在实际操作中的经验和建议是:org.gradle.jvmargs里的-Xmx4G表示给 Gradle 分配最大 4G 堆内存,如果你用的是 16G 内存的机器,可以改成-Xmx6G,编译大项目时会明显减少卡顿;如果机器只有 8G 内存,建议保持 4G 不要动,否则容易撑爆。forge_version是 Forge 的构建号,47.1.3 是 1.20.1 的稳定分支版本,后期如果你想要新特性可以换更高构建号,但不要跨大版本。mappings_channel=official表示使用 Mojang 官方映射表,这意味着你在代码里看到的类名和方法名和反编译后的原版一致,比如net.minecraft.world.item.Item,而不是net.minecraft.world.item.Item的混淆名。如果你习惯用 MCP 命名,可以改成mcp并配上对应版本号,但新手建议直接用official,查资料方便。
2.2 首次构建:从命令行到 IDE 导入的完整流程
解释清楚目录结构后,接下来就是实操环节。我第一次用这个资源包时,走了弯路:直接打开 IDE 导入项目,结果等了四十分钟还在下载依赖。后来总结出正确的顺序是先命令行构建一次,再导入 IDE。
# 第一步:给 gradlew 执行权限(macOS/Linux 需要) chmod +x gradlew # 第二步:生成 IDE 运行配置,指定 IntelliJ IDEA ./gradlew genIntellijRuns # 第三步:如果你用 Eclipse,换成这句 ./gradlew genEclipseRuns # 第四步:构建并测试,确认环境没问题 ./gradlew build第二步genIntellijRuns做的事情是分析当前 Forge 版本对应的 Minecraft 客户端和服务端依赖,生成运行所需的缓存和配置文件。执行完以后,build.gradle里会自动多出minecraft块的运行配置,IDE 里也会出现runClient和runServer两个运行任务。这个步骤跑完,再用 IDEA 打开项目,导入 Gradle 模型时就会舒服很多,不再需要联网拉取大量依赖。注意,如果你跳过了命令行构建直接导入 IDE,可能会遇到「Gradle 同步失败:无法解析 forge 依赖」的报错,原因就是你还没执行过genIntellijRuns,Forge 的用户库没生成。
2.3 手动锁定运行配置:理解 runClient 与 runServer 背后的机制
构建流程跑通后,我想强调一个容易被忽略的细节:Forge 的runClient不是直接启动 Minecraft 原版客户端,而是通过 Gradle 任务把编译产物和 Forge 的二进制补丁合成在一起后启动的。具体来说,build.gradle里的minecraft块长这样:
minecraft { mappings channel: 'official', version: '1.20.1' runs { client { workingDirectory file('run') property 'forge.logging.console.level', 'debug' mods { examplemod { source sourceSets.main } } } server { workingDirectory file('run') property 'forge.logging.console.level', 'debug' mods { examplemod { source sourceSets.main } } } } }这段配置里最关键的是workingDirectory file('run'),它决定了游戏运行时的工作目录。你的存档、日志、配置文件都会生成在run/文件夹下,而不是 Minecraft 的默认用户目录。很多新手改了配置或者加了新模组,发现游戏里没有生效,跑到.minecraft里去看,结果啥也没有,就是因为工作目录不一样。解决方案很简单:每次调试前看run/logs/debug.log,那才是你的模组实际输出的日志。
property 'forge.logging.console.level', 'debug'这行的作用是把控制台日志级别调到 debug,这样你System.out.println()和LOGGER.debug()输出的内容都会显示在 IDE 控制台里。建议调试阶段保持这个设置,等上线前改成info减少日志噪音。mods块里的sourceSets.main表示这个运行配置把src/main/java目录下的代码当作模组本体加载。
3. 第一个模组骨架:主类、物品注册与事件监听的实现链
3.1 用 @Mod 注解定义模组入口
环境搭好以后,接下来就是写代码。Forge 1.20.1 的模组入口是一个标注了@Mod注解的类,这个类控制了模组的生命周期。一个最小可运行的入口类长这样:
package com.example.examplemod; import net.minecraftforge.fml.common.Mod; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @Mod(ExampleMod.MODID) public class ExampleMod { public static final String MODID = "examplemod"; public static final Logger LOGGER = LoggerFactory.getLogger(ExampleMod.class); public ExampleMod() { LOGGER.info("模组 {} 正在初始化", MODID); } }这段类的核心逻辑有两处:@Mod(ExampleMod.MODID)注解里的字符串值必须和src/main/resources/META-INF/mods.toml文件里的modId一致,如果两者对不上,Forge 会拒绝加载这个模组,并且启动日志里会直接提示「模组 ID 不匹配」。LoggerFactory.getLogger()使用的是 SLF4J 接口,Forge 底层已经集成了,不需要额外引入依赖。我习惯在构造方法里打一条初始日志,这是排查模组加载顺序问题的最快手段,如果在启动日志里看不到这条日志,说明类根本没被加载。
注意mods.toml里还有几个必填字段,新手经常漏掉:
modLoader="javafml" loaderVersion="[47,)" license="All rights reserved" [[mods]] modId="examplemod" version="1.0.0" displayName="Example Mod"loaderVersion="[47,)"表示这个模组要求 Forge 的加载器版本至少是 47,也就是对应 1.20.1 的推荐构建。如果你用的 Forge 版本是 40.x(对应 1.20 之前的版本),启动时会直接报版本不匹配。这个区间语法是标准 Maven 版本范围语法,[47,)表示大于等于 47,没有上限,不要把逗号写成别的东西。
3.2 用 DeferredRegister 注册第一个物品
注册物品的方式经历了几个版本的迭代,1.20.1 里的标准做法是用DeferredRegister加RegistryObject。可以这么理解:DeferredRegister是一个延迟注册表,它会等到 Forge 的注册事件触发时,才把你定义好的物品真正挂到游戏注册表里。用一个简单示例说明:
public class ModItems { public static final DeferredRegister<Item> ITEMS = DeferredRegister.create(ForgeRegistries.ITEMS, ExampleMod.MODID); public static final RegistryObject<Item> RUBY = ITEMS.register("ruby", () -> new Item(new Item.Properties())); public static void register(IEventBus bus) { ITEMS.register(bus); } }这段代码里做了两层事:第一行创建了一个注册表对象,泛型Item表明这个注册表管理的是物品类型,ForgeRegistries.ITEMS是原版物品注册表在 Forge 中的持有者。第二行调用register("ruby", ...)定义了一个注册 ID 为ruby的普通物品,第三个参数是一个Supplier函数式接口,返回一个Item实例。这里我用的是空属性new Item.Properties(),表示这个物品没有耐久度、没有堆叠上限的特殊设定,默认 64 个一组。register(IEventBus bus)是你手动定义的辅助方法,需要在主类构造方法里调用它,把注册表挂到 Forge 的事件总线上。
然后回到主类构造方法,你需要补一行:
public ExampleMod() { ModItems.register(FMLJavaModLoadingContext.get().getModEventBus()); LOGGER.info("模组 {} 正在初始化", MODID); }这里传进去的是FMLJavaModLoadingContext.get().getModEventBus(),这是 Forge 专门用于模组生命周期事件的独立事件总线。如果你在构造阶段没有执行register,那么RUBY这个物品永远不会出现在游戏里,因为它的注册函数没有被调用。这种延迟注册的设计是有原因的:Forge 要求所有物品必须在RegisterEvent触发时一次性注册完毕,如果你在游戏运行中途调用注册 API,会导致注册表状态不一致,可能引发存档写入错误。
3.3 用事件监听实现右键使用功能
注册了物品但什么功能都没有,那它只是一个占位符。接下来给这个物品加一个右键触发功能,让它给玩家一个速度效果。这一步需要注册事件监听器。先看代码:
@Mod.EventBusSubscriber(modid = ExampleMod.MODID) public class CommonEvents { @SubscribeEvent public static void onItemRightClick(PlayerInteractEvent.RightClickItem event) { var player = event.getEntity(); var heldItem = event.getItemStack(); if (heldItem.getItem() == ModItems.RUBY.get()) { player.addEffect(new MobEffectInstance(MobEffects.MOVEMENT_SPEED, 200, 1)); if (!player.getAbilities().instabuild) { heldItem.shrink(1); } } } }@Mod.EventBusSubscriber注解表示这个类里所有标注了@SubscribeEvent的静态方法都会被自动注册到对应的事件总线上。这里监听的是PlayerInteractEvent.RightClickItem,意思是玩家右键物品时触发。事件对象里包含了玩家实体(getEntity())和手里的物品栈(getItemStack())。我先比较手持物品是否等于ModItems.RUBY.get(),注意这里调用.get()返回的是注册表里实际的物品实例,而不是注册时的RegistryObject代理。
效果部分:new MobEffectInstance(MobEffects.MOVEMENT_SPEED, 200, 1)第一个参数 200 是效果持续时间(单位 tick,200 tick = 10 秒),第二个参数 1 是效果等级(等于 2 级速度效果,因为索引从 0 开始)。heldItem.shrink(1)表示消耗一个物品,shrink方法会修改物品栈的计数。我加了一个instabuild判断,防止创造模式玩家测试时物品被消耗掉。
实际调试时,我遇到过一个问题:监听的物品始终不触发。后来排查发现是自己忘了在mods.toml里注册eventBusSubscriber对应的条件,导致整个列表没被扫描。检查mods.toml末尾有没有这一段:
[[mods]] ... [[dependencies.examplemod]] [[dependencies.examplemod]] modId="forge" mandatory=true versionRange="[47,)" ordering="NONE" side="BOTH"这表示你的模组强依赖 Forge 47 及以上版本,如果缺失这一段,部分版本的 Forge 不会主动注册你的EventBusSubscriber类。
4. 让客户端认你的模组:资源目录、模型映射与数据生成联动
4.1 resources 目录的潜规则:一个文件放错位置,物品就是紫黑方块
代码写完只是第一步,真正的麻烦在资源文件。如果你的物品注册成功,但在游戏里显示成一个紫黑相间的方块,那说明模型或贴图缺失。Forge 1.20.1 的资源加载机制和原版 Minecraft 是一致的:它按照固定的命名空间和路径去找资源。
先看一个完整的资源目录应该长什么样:
src/main/resources/ ├── META-INF/ │ ├── mods.toml │ └── coremods.json ├── assets/examplemod/ │ ├── blockstates/ │ ├── lang/ │ │ └── en_us.json │ ├── models/ │ │ ├── item/ │ │ │ └── ruby.json │ │ └── block/ │ └── textures/ │ └── item/ │ └── ruby.png ├── data/examplemod/ │ ├── recipes/ │ └── loot_tables/ └── pack.mcmeta这里最核心的规则是路径必须严格匹配:如果你的物品注册 ID 是ruby,命名空间是examplemod,那么模型文件必须放在assets/examplemod/models/item/ruby.json,贴图文件必须放在assets/examplemod/textures/item/ruby.png。路径里任何一个单词拼错,游戏不会报错告诉你「贴图没找到」,而是直接显示紫黑纹理。这个现象是 Minecraft 的一个历史设计,被称为“missing texture”。
注意pack.mcmeta这个文件,它定义了资源包的格式版本。1.20.1 对应的格式版本是 15,如果你的pack_format写成了 13(对应 1.19.x),Forge 会警告但不强制阻断。我见过有人因为pack_format写错导致多人游戏联机时客户端和服务端资源校验不一致,直接被踢出服务器。
4.2 用 DataProvider 生成语言文件与配方
对于语言文件和配方这一类数据,Forge 1.20.1 提供了一个叫DataProvider的机制。它的逻辑是:你在 Java 代码里定义生成规则,运行一个 Gradle 任务,自动生成 JSON 文件。这么做的好处是,当你要改物品名称时,只需要改代码里的一个字符串,而不用手动同步多个语言文件。看一个例子:
public class ModDataProvider extends DataProvider { public ModDataProvider(FMLPaths paths) { super(paths); } @Override public void run(PackOutput output, CompletableFuture<HolderLookup.Provider> lookupProvider) { // 语言文件生成 output.addProvider(new SimpleAssetProvider(ExampleMod.MODID) { @Override protected void addLanguages() { add("item.examplemod.ruby", "Ruby"); add("item.examplemod.ruby", "红宝石"); } }); // 配方生成 output.addProvider(new RecipeProvider() { @Override protected void buildRecipes(RecipeOutput consumer) { ShapedRecipeBuilder.shaped(RecipeCategory.MISC, ModItems.RUBY.get()) .pattern("RR") .pattern("RR") .define('R', Items.REDSTONE) .unlockedBy("has_redstone", has(Items.REDSTONE)) .save(consumer); } }); } }这段代码做了两件事:addLanguages()定义了两个语言翻译键,item.examplemod.ruby是资源键名,后面跟着对应语言下的显示名,我同时写了英文和中文翻译。ShapedRecipeBuilder定义了一个合成配方,四个红宝石合成一个红石,这边是举个例子,实际你可以改成八个圆石围一圈合成一个石头之类。pattern("RR")和pattern("RR")两行字符组成了 2x2 的合成模板,define('R', Items.REDSTONE)把字符R对应到红石物品。
要运行数据生成,需要在 Gradle 里加一个任务配置。在build.gradle的minecraft块里,runs里增加一段:
data { workingDirectory file('run') mods { examplemod { source sourceSets.main } } }然后在命令行执行./gradlew runData,它会读取代码里所有DataProvider实现,把生成的文件刷到src/generated/resources目录下。注意这个目录需要你在build.gradle里配置为资源源目录,否则生成的 JSON 文件不会被编译进最终的 jar 包。
4.3 让模型文件生效:手动写 JSON 模型文件的两种路径
如果不想用代码生成模型,直接手写 JSON 也是一种选择。Forge 的模型文件支持两种格式:传统方块模型格式和 Forge 的 OBJ/JSON 简化格式。对于普通物品,最简单的模型文件长这样:
{ "parent": "item/generated", "textures": { "layer0": "examplemod:item/ruby" } }parent字段表示继承哪个预设模型,item/generated是原版自带的基础物品模型,它会把layer0指定的贴图绘制在物品图标上。textures里的值examplemod:item/ruby是资源路径,原始过程会自动补全.png后缀。
如果你想要做带手持 3D 模型的物品,比如一把特殊的剑,需要加上display字段,控制物品在第三人称视角、第一人称视角和 GUI 中的旋转和缩放。这个属于进阶玩法,但你要清楚一点:手写 JSON 模型文件时,如果你的 JSON 语法有错误,游戏不会弹窗告诉你,而是在日志里打印一条「Failed to load model」,然后物品继续显示紫黑方块。建议先把生成好的配方文件作为对照模板,再上手改自己的模型。
5. 避坑指南:Gradle 下载失败、映射错乱、注册冲突与客户端连不上服务端
5.1 坑一:Gradle 依赖卡在 0% 或下载超时
现象:执行./gradlew genIntellijRuns,进度条停在Downloading net.minecraftforge:forge:1.20.1-47.1.3或者Downloading https://services.gradle.org/distributions/gradle-8.1.1-bin.zip,等十几分钟没反应,最后报连接超时。
原因:Gradle 发行包和 Forge 的 Maven 仓库都在国外服务器,直接下载带宽太低。尤其是gradle-wrapper.properties里的distributionUrl指向官方地址,这是绝大多数人卡住的第一道坎。
解决:手动修改gradle/wrapper/gradle-wrapper.properties文件,把distributionUrl换成国内镜像地址:
distributionUrl=https\://mirrors.cloud.tencent.com/gradle/gradle-8.1.1-bin.zip同时在项目根目录新建或修改init.gradle文件,配置 Maven 镜像仓库:
allprojects { repositories { maven { url 'https://maven.aliyun.com/repository/public' } maven { url 'https://maven.aliyun.com/repository/forge' } mavenCentral() } }把init.gradle放到~/.gradle/目录下,这样所有项目都会自动使用这个镜像配置。改完后删除~/.gradle/caches/和项目下的build/目录,重新执行构建命令。我试过这个方案,原本四十分钟的下载时间能压缩到五分钟以内。注意,Forge 的forge模块依赖minecraft的映射文件,这个文件比较大(约 50MB),它也会走 Maven 仓库下载,镜像源如果没同步这个版本,就换个镜像源,比如https://repo1.maven.org/maven2/直连试试,运气好能跑通。
5.2 坑二:写代码时类的包名和实际映射对不上
现象:在 IDE 里写net.minecraft.world.item.Item,编译报错,提示「找不到符号 class Item」,或者你看到某个类是class_1234这种混淆名。
原因:项目的mappings_channel配置成了snapshot或mcp,而不是official。如果配置是快照映射,IDE 反编译的代码可能显示的是带数字后缀的内部类名,但你在代码里引用的是正常名称,自然对不上。
解决:把gradle.properties里的mappings_channel=official固定下来,然后重新执行./gradlew --refresh-dependencies清理旧的映射缓存。如果项目是从旧版本迁移过来的,还需要删除run/目录下的config文件夹,因为里面可能缓存了旧版的类名映射表。注意一个常见误用:有人在build.gradle里写mappings channel: 'official', version: '1.20.1',但gradle.properties里还留着一个mappings_version=1.19.4,这种情况下 Forge 会在构建时警告版本不一致,然后把 1.19.4 的映射强行用于 1.20.1,直接导致大量类缺失。
5.3 坑三:server 启动后客户端连接被拒
现象:runServer启动成功,进入游戏后runClient加入本地服务器,显示「连接被拒绝:连接超时」。
原因:Forge 1.20.1 的服务端默认监听端口是 25565,而客户端连接时会尝试解析localhost。如果run/目录下的server.properties里的server-ip被填成别的值,或者本机防火墙拦截了 Java 进程的端口访问,就会连接失败。
解决:在run/目录下找到server.properties,确认这些值:
server-ip= server-port=25565 online-mode=falseserver-ip留空表示监听所有网卡,online-mode=false是给本地联调用的,避免客户端需要正版验证。最后还要检查防火墙是否拦截了 Java,Windows 系统弹窗时要允许访问。其实更快的调试方法是不用runClient,在服务端日志里按T键输入op 你的游戏名,然后在客户端里直接使用局域网开放功能,这样能跳过很多网络层问题。
5.4 坑四:物品注册 ID 冲突,导致启动崩溃
现象:启动客户端时日志报Registry.have ID conflict,弹出的崩溃报告提示Duplicate registration或Registry entry already present。
原因:你自己的模组和另一个模组注册了同样的物品 ID,比如都是examplemod:ruby,或者你重复注册了同一个RegistryObject。还有一种可能是两个模组共用一个命名空间,然后注册了相同路径。
解决:检查所有注册代码里register("ruby", ...)的字符串是否唯一。如果你想注册一个名字有歧义的物品,加上你的模组 ID 前缀,比如把ruby改成examplemod_ruby。注意,在多人联机时,所有玩家的模组列表必须一致,如果客户端有你服务端没有的模组,或者同一个模组版本不同,也会触发注册表冲突,但那是另一类问题,可以通过加[[dependencies]]里的versionRange来控制。
5.5 坑五:数据生成任务 runData 报错,提示无法找到截图
现象:执行./gradlew runData,在生成物品模型时崩溃,报错提示Throwing RuntimeException: Asset 'missing' not found。
原因:DataProvider尝试生成一个模型,但引用的贴图资源不存在。常见情况是在模型里写了examplemod:item/ruby,但贴图文件textures/item/ruby.png还没放进去。生成任务会严格检查资源是否存在,不存在就直接抛异常。
解决:先手动建立占位贴图。用一个 16x16 像素的纯色 PNG 文件放到对应路径,比如用画图工具新建一张 16x16 的图片,命名为ruby.png,放到src/main/resources/assets/examplemod/textures/item/目录下。然后重新执行runData。等生成完再回头仔细替换成真正的贴图。这个机制其实挺有用,它相当于一个资源完整性校验器。
6. 进阶技巧:用 Forge 配置系统加功能开关,让模组适配不同服务器
摸清了基本开发流程后,最后一个值得上手的技能是给模组加一个「配置文件开关」。这样同一份模组在生存服、创造服或小游戏服务器上可以有不同的表现,不用改代码重新编译。Forge 1.20.1 的配置系统是基于 Toml 格式的,推荐用ModConfigSpec来定义配置项。
看一个示例,给之前的速度效果物品加一个「是否启用」的开关:
public class ModConfig { private static final ModConfigSpec.Builder BUILDER = new ModConfigSpec.Builder(); public static final ModConfigSpec SPEC; public static final ModConfigSpec.BooleanValue ENABLE_RUBY_EFFECT; public static final ModConfigSpec.IntValue RUBY_EFFECT_DURATION; static { BUILDER.comment("Ruby item settings").push("ruby"); ENABLE_RUBY_EFFECT = BUILDER .comment("Set to false to disable the speed effect") .define("enableEffect", true); RUBY_EFFECT_DURATION = BUILDER .comment("Duration in ticks, default 200 = 10 seconds") .defineInRange("duration", 200, 20, 6000); BUILDER.pop(); SPEC = BUILDER.build(); } }然后在主类构造函数里监听配置加载事件:
public ExampleMod() { ModConfig.register(); FMLJavaModLoadingContext.get().getModEventBus().addListener(this::loadConfig); } private void loadConfig(final ModConfigEvent event) { if (event.getConfig().getSpec() == ModConfig.SPEC) { ModConfig.SPEC.load(); } }最后修改事件监听器,让它读取配置值:
if (ModConfig.ENABLE_RUBY_EFFECT.get() && heldItem.getItem() == ModItems.RUBY.get()) { int duration = ModConfig.RUBY_EFFECT_DURATION.get(); player.addEffect(new MobEffectInstance(MobEffects.MOVEMENT_SPEED, duration, 1)); }这段流程的核心价值在于:配置值是在运行时动态读取的,服务器管理员可以在run/config/examplemod-server.toml里直接改数值,重启服务器立即生效。注意RELOAD事件也可以触发配置重新加载,但不推荐在联机中热加载,因为效果数据已经写入玩家的实体数据里,改配置只能影响后续触发的效果。
最后分享一个我自己的习惯:模组开发完成后,我一般不会直接发给别人测试,而是强制走一遍「清空run目录 → 重新运行runData→runClient启动 → 创造模式给物品 → 测试右键效果 → 切到服务端看日志」这个流程。因为run目录下的缓存经常藏着旧资源,导致你改了贴图和配方却看不到任何变化。这个习惯帮我省了无数次「咦,我明明改了怎么没生效」的抓狂时刻。希望这份详细的拆解能帮你在 MC Forge 1.20.1 的道路上少走几个来回,顺利跑起来自己的模组。
本文还有配套的精品资源,点击获取