☰
Forge-1.20.1 开发指南:从环境搭建到 DeferredRegister 注册链
2026/10/27 21:00:39 网站建设 项目流程

简介:本资源是专为Minecraft模组开发者提供的Forge 1.20.1官方MDK(Mod Development Kit)开发套件,适用于Java基础扎实、希望基于Minecraft 1.20.1版本进行模组开发的中高级学习者与实践者。包内共17个文件,涵盖构建核心(gradlew.bat、gradlew、build.gradle、settings.gradle)、环境配置(gradle.properties、.gitignore、.gitattributes)、许可证与致谢(LICENSE.txt、CREDITS.txt、changelog.txt)、源码骨架(src/main/java、src/main/resources)及元数据(mcmeta、toml),完整复现标准Forge模组工程结构。111KB轻量级压缩包便于快速下载与本地初始化,已吸引1506人学习下载。读者可直接导入IDE开展开发,获得开箱即用的Gradle构建流程、规范化的目录组织、版本兼容性保障及清晰的变更日志参考,是入门Forge模组开发、理解现代Minecraft模组工程化实践的理想起点。

1. Forge-1.20.1 是什么:不是“模组启动器”,而是 Minecraft 1.20.1 的底层开发契约与运行时沙盒

你手头刚下载了一个叫forge-1.20.1的压缩包,解压后看到一堆.jar、gradle配置和src目录,但点开installer.jar却提示“找不到 Java”或双击没反应——这不是 Forge 安装失败,而是你误把它当成了客户端工具。Forge-1.20.1 本质是一套面向 Java 开发者的 Minecraft 1.20.1 版本兼容性契约:它不直接运行游戏,而是提供一套被 Mojang 官方认可的、可预测的类加载机制、事件总线和生命周期钩子,让第三方代码能安全地“插”进原版游戏内核而不崩溃。它解决的不是“怎么装模组”,而是“为什么 A 模组和 B 模组同时启用就黑屏”“为什么我的自定义方块在服务端生成却客户端不显示”这类底层冲突问题。适合三类人:正在从 1.12.2 迁移模组到 1.20.1 的老开发者、需要调试BlockEntity同步逻辑的网络协议学习者、以及想搞清DeferredRegister和RegistryObject底层绑定关系的 Java 工程师。它不是“一键安装包”,而是一份带源码、带测试用例、带 Gradle 构建链的 SDK——你拿到的不是成品,是整条生产线的图纸和模具。


2. 环境准备与项目初始化:用官方推荐方式搭起可调试的 1.20.1 开发骨架

Forge-1.20.1 的构建体系已彻底转向 Gradle 8.x + Java 17,不再支持旧版mdk手动拷贝。必须用gradlew而非系统全局gradle命令,否则会因 Gradle Wrapper 版本不匹配导致Could not resolve org.spongepowered:minecraft-launcher-meta:1.0-SNAPSHOT类错误。以下步骤基于 Windows / Linux / macOS 通用路径,所有命令均在项目根目录(即含gradle.properties和build.gradle的目录)执行。

2.1 JDK 17 配置验证:别让 Java 版本成为第一个拦路虎

Forge-1.20.1 强制要求 JDK 17(非 JRE,非 JDK 21,非 OpenJDK 仅限 17.0.1+)。常见翻车点是系统 PATH 里残留 JDK 8 或 JDK 11,导致java -version显示正确但gradlew内部调用失败。验证方式不是看终端输出,而是执行:

./gradlew --version

提示:若报错Unsupported class file major version 61,说明 gradle wrapper 正在用 JDK 11 运行;若报Could not determine java version from '21.0.1',说明它检测到 JDK 21 但拒绝使用。必须确保JAVA_HOME指向 JDK 17 安装路径,且PATH中%JAVA_HOME%\bin(Windows)或$JAVA_HOME/bin(macOS/Linux)排在系统默认 Java 前。

2.2 初始化开发环境:运行 installer 并生成 IDE 可识别结构

Forge-1.20.1 不再提供预编译的universal.jar,而是通过installer.jar动态注入依赖。关键动作是执行:

java -jar forge-1.20.1-installer.jar --installServer

该命令实际做了三件事:(1)下载minecraft_server.1.20.1.jar并重命名为minecraft_server.jar;(2)将forge-1.20.1-universal.jar解压为libraries/下的完整依赖树;(3)生成gradle/wrapper/gradle-wrapper.properties中指定的 Gradle 分发包缓存。完成后,执行:

./gradlew genSources

此命令会反编译 Mojang 官方混淆后的minecraft_server.jar,生成src/main/java下的可读源码(含net.minecraft.*包),并创建src/main/resources/META-INF/mods.toml模板。这是后续所有@Mod注解、IEventBus注册、DeferredRegister初始化的前提——没有这一步,IDE 里所有net.minecraft.world.level.block.Block都标红,且无法跳转到方法定义。

2.3 导入 IDE:IntelliJ IDEA 的关键配置项

IntelliJ 用户常忽略两个致命设置:
(1)Project SDK 必须设为 JDK 17:File → Project Structure → Project → Project SDK → Add JDK → 选择 JDK 17 根目录;
(2)Gradle JVM 必须与 Project SDK 一致:File → Settings → Build → Build Tools → Gradle → Gradle JVM → 选同一 JDK 17。
若只改 Project SDK 而不改 Gradle JVM,./gradlew build成功但 IDEA 内代码提示全失效,因为 IDE 用 JDK 17 解析源码,却用 JDK 11 编译字节码,类型擦除后泛型信息丢失。验证方式:打开src/main/java/com/example/examplemod/ExampleMod.java,光标悬停@Mod("examplemod"),应显示@interface Mod的完整声明;若显示Cannot find declaration to go to,即配置失败。


3. 核心模块实战:从 mods.toml 到 DeferredRegister 的四层注册链

Forge-1.20.1 的模组入口不再是preInit/init/postInit三阶段,而是基于IEventBus的事件驱动模型。整个注册流程像一条流水线:mods.toml声明元数据 →@Mod触发构造 →ModConstructor绑定事件总线 →DeferredRegister延迟注册对象。漏掉任一环,你的方块不会出现在创造模式物品栏,你的音效不会播放。

3.1 mods.toml:不只是描述文件,而是 Forge 加载器的配置契约

src/main/resources/META-INF/mods.toml是 Forge-1.20.1 的强制入口配置。常见错误是复制旧版内容,忽略[[mods]]下新增的logoFile和credits字段。正确最小化配置如下:

modLoader="javafml" loaderVersion="[47,)" license="All rights reserved" issueTrackerURL="https://github.com/yourname/yourmod/issues" [[mods]] modId="examplemod" version="${file_version}" displayName="Example Mod" description=''' A simple example mod for 1.20.1. ''' authors="YourName" credits="Thanks to Forge team" logoFile="examplemod.png"

关键点:loaderVersion="[47,)"表示兼容 Forge Loader 47.x 及以上,对应 Forge-1.20.1 的正式版号(如47.2.0);logoFile必须是src/main/resources/assets/examplemod/textures/gui/下的 PNG 文件,否则启动时抛ResourceLocationException;version="${file_version}"是 Gradle 插件自动替换的占位符,由build.gradle中version = "1.0.0"决定,不可手写死值。

3.2 @Mod 注解与 ModConstructor:事件总线的唯一可信来源

@Mod("examplemod")不再只是标记类,而是触发ModLoadingContext.get()获取IEventBus的开关。必须配合无参构造函数使用:

@Mod(ExampleMod.MODID) public class ExampleMod { public static final String MODID = "examplemod"; public ExampleMod() { IEventBus modEventBus = FMLJavaModLoadingContext.get().getModEventBus(); // 所有注册必须在此 bus 上进行 DeferredRegister<Block> BLOCKS = DeferredRegister.create(ForgeRegistries.BLOCKS, MODID); BLOCKS.register(modEventBus); // 关键:必须显式调用 register() } }

玄学坑:若把BLOCKS.register(modEventBus)写在静态代码块或static final初始化中,会导致modEventBus尚未初始化就调用,抛NullPointerException。原因在于@Mod类的静态初始化早于FMLJavaModLoadingContext的上下文构建。血泪经验:所有register()调用必须包裹在构造函数内,且不能放在if条件分支外的静态变量赋值中。

3.3 DeferredRegister:延迟注册的本质是“注册请求队列”

DeferredRegister不是立即注册对象,而是将RegistryObject<Block>的创建逻辑封装为 Supplier,加入事件总线的队列。其核心是RegistryObject的懒加载特性:

public static final DeferredRegister<Block> BLOCKS = DeferredRegister.create(ForgeRegistries.BLOCKS, MODID); public static final RegistryObject<Block> EXAMPLE_BLOCK = BLOCKS.register("example_block", () -> new Block(BlockBehaviour.Properties.of().mapColor(MapColor.STONE)));

当BLOCKS.register(modEventBus)执行时,实际注册的是一个RegistryEvent.Register<Block>事件监听器;真正调用() -> new Block(...)是在RegistryEvent.Register事件被MinecraftForge.EVENT_BUS广播时。这意味着:(1)EXAMPLE_BLOCK.get()在RegistryEvent.Register之前调用会返回null;(2)若在CommonSetupEvent中调用EXAMPLE_BLOCK.get().defaultBlockState(),必须确保该事件在RegistryEvent.Register之后触发——这由 Forge 的事件优先级保证,无需手动排序。


4. 避坑指南:1.20.1 版本下五个高频崩溃与静默失败现象

Forge-1.20.1 的变更点集中在类加载隔离、资源定位和网络同步三方面。以下问题均来自某跨平台系统在迁移过程中真实复现的日志,每条均可精准复现并修复。

4.1 现象:游戏启动后控制台刷屏Unable to load class net.minecraft.world.level.block.Blocks,但游戏界面正常

原因:build.gradle中minecraft依赖版本写成"1.20.1"而非"1.20.1-rc1"(或对应 RC 版本)。Forge-1.20.1 的minecraft_server.jar实际基于 Mojang 发布的 Release Candidate 构建,其内部net.minecraft类名与正式版1.20.1存在细微差异(如BlockBehaviour.Properties.of().mapColor()在 RC 中参数为MapColor,正式版为DyeColor)。Gradle 会下载错误的minecraftjar,导致类加载器找不到符号。
解决:检查build.gradle中minecraft依赖,确认为minecraft 'net.minecraftforge:forge:1.20.1-47.2.0',且mappings channel 'official'对应1.20.1-rc1。执行./gradlew clean清理~/.gradle/caches/下的旧 minecraft jar。

4.2 现象:自定义方块在单人世界放置正常,联机时客户端显示为紫黑方块(Missing Texture)

原因:资源路径未按 1.20.1 新规组织。Forge-1.20.1 强制要求blockstateJSON 必须位于src/main/resources/assets/examplemod/blockstates/example_block.json,且其中variants的model字段必须为examplemod:block/example_block(注意斜杠/而非点.)。旧版允许model: "examplemod:block/example_block",新版解析器严格校验路径分隔符。
解决:检查blockstates文件路径和内容,确保model值为examplemod:block/example_block;同时确认src/main/resources/assets/examplemod/models/block/example_block.json存在且parent字段为"block/cube_all"。

4.3 现象:PlayerInteractEvent.RightClickBlock事件监听器完全不触发

原因:事件总线注册位置错误。PlayerInteractEvent属于MinecraftForge.EVENT_BUS,而非FMLJavaModLoadingContext.get().getModEventBus()。后者只处理模组生命周期事件(如ModConfigEvent),前者处理游戏运行时事件。
解决:在ExampleMod构造函数末尾添加:

MinecraftForge.EVENT_BUS.addListener(this::onRightClickBlock);

其中onRightClickBlock方法签名必须为public void onRightClickBlock(PlayerInteractEvent.RightClickBlock event),且类需加@Mod.EventBusSubscriber(modid = MODID, bus = Mod.EventBusSubscriber.Bus.FORGE)注解。

4.4 现象:DeferredRegister.create(ForgeRegistries.ITEMS, MODID)注册的物品在创造模式标签页不显示

原因:缺少ItemGroup绑定。1.20.1 要求每个RegistryObject<Item>必须关联一个ItemGroup,否则 Forge 默认归入Misc标签且不显示图标。
解决:在ExampleMod构造函数中添加:

public static final RegistryObject<Item> EXAMPLE_ITEM = ITEMS.register("example_item", () -> new BlockItem(EXAMPLE_BLOCK.get(), new Item.Properties().tab(CreativeModeTabs.BUILDING_BLOCKS)));

注意CreativeModeTabs.BUILDING_BLOCKS是 1.20.1 新增的标签枚举,不可用旧版ItemGroup。

4.5 现象:ServerPlayer发送PacketDistributor.TRACKING_ENTITY.with(() -> player)消息后客户端收不到

原因:PacketDistributor.TRACKING_ENTITY在 1.20.1 中已废弃,必须改用PacketDistributor.TRACKING_ENTITY_AND_SELF或PacketDistributor.ALL.noArg()。旧代码在编译期不报错,但运行时TRACKING_ENTITY返回空PacketDistributor。
解决:将PacketDistributor.TRACKING_ENTITY.with(() -> player)替换为PacketDistributor.TRACKING_ENTITY_AND_SELF.with(() -> player),并确保自定义 Packet 类实现IPacket接口(非Packet<?>)。


5. 资源加载与调试技巧:用--debug参数和ResourceLocation验证器定位硬编码路径错误

Forge-1.20.1 的资源加载器(ResourceManager)对路径大小写和扩展名极其敏感,尤其在 Windows 开发、Linux 测试的跨平台场景下。一个textures/blocks/Example_Block.png文件,若实际路径为textures/blocks/example_block.png,则ResourceLocation构造时不会报错,但Minecraft.getInstance().getTextureManager().getTexture(...)返回MissingTextureAtlasSprite,最终渲染为紫黑方块。这种错误无法通过编译或启动日志发现,必须主动验证。

5.1 启动参数注入:用--debug激活 Forge 的资源加载追踪

在runClient任务中添加 JVM 参数,使 Forge 输出所有资源加载尝试:

// build.gradle tasks.named('runClient', JavaExec) { jvmArgs = [ '-Dforge.logging.markers=SCAN,REGISTRIES', '-Dforge.logging.console.level=debug' ] }

执行./gradlew runClient后,控制台将打印类似:

[DEBUG] [ne.mi.fm.lo.FMLLoader/CORE]: Found resource pack: Mod File Resource Pack: examplemod [DEBUG] [ne.mi.re.IReloadableResourceManager/RESOURCE_PACKS]: Loading textures/blocks/example_block.png from examplemod [WARN ] [ne.mi.re.IReloadableResourceManager/RESOURCE_PACKS]: Failed to load texture: textures/blocks/example_block.png

关键线索是Failed to load texture后的路径是否与src/main/resources/assets/examplemod/下实际路径完全一致(包括大小写、下划线/驼峰、.png扩展名)。注意:Windows 文件系统不区分大小写,但 Forge 的ResourceLocation解析器区分,因此Example_Block.png在 Windows 下能加载,在 Linux Docker 容器中必失败。

5.2 ResourceLocation 静态验证:在CommonSetupEvent中强制校验

在模组初始化完成、资源加载完毕后,插入一段校验代码,避免上线后才发现纹理缺失:

@SubscribeEvent public static void onCommonSetup(CommonSetupEvent event) { ResourceLocation blockTexture = new ResourceLocation("examplemod", "block/example_block"); ResourceManager manager = Minecraft.getInstance().getResourceManager(); if (!manager.hasResource(blockTexture)) { throw new RuntimeException("Missing texture: " + blockTexture); } }

此代码在CommonSetupEvent中执行,此时ResourceManager已完成所有资源包加载。若hasResource()返回false,游戏立即崩溃并打印堆栈,比玩家反馈“方块没贴图”早三天发现问题。

5.3 模组依赖图谱可视化:用gradle dependencies查清传递依赖冲突

当引入jei或ctm等第三方库时,常因guava、gson版本不一致导致NoClassDefFoundError。执行:

./gradlew dependencies --configuration runtimeClasspath

输出中查找com.google.guava:guava行,会看到类似:

+--- net.minecraftforge:forge:1.20.1-47.2.0 | \--- com.google.guava:guava:32.1.2-jre \--- mezz.jei:jei-1.20.1:12.0.0.15 \--- com.google.guava:guava:32.0.1-jre

两个不同版本的guava共存,JVM 类加载器随机选择其一,导致Lists.newArrayList()方法在jei中不可见。解决方式是在build.gradle中强制统一版本:

configurations.all { resolutionStrategy { force 'com.google.guava:guava:32.1.2-jre' } }

从那以后我每次引入新模组依赖,都强制走一遍./gradlew dependencies --configuration runtimeClasspath | grep guava,再对比 Forge 官方文档列出的推荐版本。不是信不过作者,是信不过 Maven 传递依赖的随机性——它不报错,但会在凌晨三点让你的服务器突然拒绝加载某个音效。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询