23 Forge 核心机制与 Java 对照¶
本章目标¶
- 把前面学的 Java 知识映射到 Forge 模组开发
- 掌握主线程概念与调度
- 了解 Forge 常用 API 套路
- 衔接本站的 Forge 详细教程
23.1 Java 知识 → 模组开发对照表¶
| 你学的 Java 知识 | 在模组开发里用在哪 |
|---|---|
| 类与继承 | 自定义物品 extends Item、方块 extends Block、实体 extends Entity |
| 接口 | Forge 扩展接口(IItemExtension 等) |
| 抽象类 | Forge 提供的抽象基类(AbstractContainerMenu 等) |
| 重写 @Override | 重写 Item.use()、Block.getStateForPlacement() |
| 注解 | @Mod、@SubscribeEvent、@OnlyIn、@ObjectHolder |
| 泛型 | RegistryObject<T>、DeferredRegister<T>、Codec<T> |
| Lambda | 事件监听、ITickable、谓词 |
| 枚举 | Rarity、GameType、Direction |
| 集合 | Map<ResourceLocation, Item> 等注册表相关结构 |
| Optional | Optional.of(...) 链式处理可能为空的值 |
| 反射 | Forge 内部注册机制、模组兼容检测 |
| 设计模式 | 观察者(事件)、Builder(Properties)、工厂(注册) |
| Stream | 批量处理物品/实体列表 |
| 异常 | 模组加载失败抛异常、容错处理 |
23.2 主线程:模组开发第一铁律¶
Minecraft 的核心循环跑在主线程(Server thread / Client thread)上:
游戏循环(每 tick ≈ 50ms)
├── 实体更新
├── 方块更新
├── 事件触发
└── 渲染(客户端)
规则:
- 模组代码默认在主线程执行(事件、tick 方法都是)
- 别在主线程做耗时操作(读大文件、网络请求、复杂计算)→ 卡服/掉帧
- 别在非主线程碰游戏数据(世界、实体、方块)→ 崩溃
- 需要跨线程操作时,用 Forge 提供的调度回到主线程
// 从任意线程安全地回到主线程执行
Minecraft.getInstance().execute(() -> {
// 这里可以安全操作游戏数据(客户端)
});
// 服务端(在 ServerLevel 上)
serverLevel.getServer().execute(() -> {
// 主线程执行
});
最常见的崩溃原因
"Thread X has no stack trace / Accessing game data from wrong thread"——新手模组崩溃有一半是这个原因。涉及世界、实体、玩家的操作,一律确保在主线程。
23.3 Forge 三大核心机制¶
1. 事件总线(Event Bus)¶
// Forge 事件(游戏世界相关):MinecraftForge.EVENT_BUS
@SubscribeEvent
public static void onLivingDeath(LivingDeathEvent event) { ... }
// Mod 事件(加载生命周期相关):modEventBus
@SubscribeEvent
public static void onRegisterItems(RegisterEvent event) { ... }
| 总线 | 场景 | 例子 |
|---|---|---|
MinecraftForge.EVENT_BUS |
游戏运行时 | 玩家登录、实体死亡、方块放置 |
| modEventBus | 模组加载阶段 | 注册物品/方块、加载配置 |
2. 注册表(Registries)¶
所有内容注册进注册表(第 22 章讲过):
public static final DeferredRegister<Block> BLOCKS =
DeferredRegister.create(ForgeRegistries.BLOCKS, FfsMod.MODID);
public static final RegistryObject<Block> MAGIC_BLOCK =
BLOCKS.register("magic_block", () -> new Block(BlockBehaviour.Properties.copy(Blocks.STONE)));
3. 能力系统(Capabilities)¶
跨模组的数据共享机制(容器、能量、流体):
// 给实体/方块挂"能力"(简化示意)
capability.getCapability(Capabilities.ItemStackHandler.ITEM_HANDLER)
.ifPresent(handler -> handler.insertItem(0, stack, false));
23.4 客户端与服务端(Dist 区分)¶
一个模组代码会在客户端(渲染、输入)和服务端(逻辑)分别加载:
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
@OnlyIn(Dist.CLIENT) // 只在客户端执行
public static void clientOnlyInit() { ... }
// 或运行时判断
if (event.getSide() == LogicalSide.CLIENT) { ... }
别在服务端调用客户端 API
调用 Minecraft.getInstance()(客户端专用)要在服务端保护,否则服务端直接崩。新手常见坑。
23.5 数据生成与 Codec¶
Forge 支持数据生成器(DataGen)自动生成配方、模型、语言文件,避免手写:
// 配方(DataGen 生成 recipes)
ShapedRecipeBuilder.shaped(RecipeCategory.MISC, ModItems.MAGIC_INGOT.get())
.pattern("###").pattern("#X#").pattern("###")
.define('#', Items.DIAMOND).define('X', Items.NETHER_STAR)
.save(consumer);
Codec 是 Minecraft 现在的数据序列化方式(泛型 + 函数式,Java 知识全用上了):
// 自定义结构的 Codec
public static final Codec<MyData> CODEC = RecordCodecBuilder.create(inst -> inst.group(
Codec.STRING.fieldOf("name").forGetter(MyData::getName),
Codec.INT.fieldOf("value").forGetter(MyData::getValue)
).apply(inst, MyData::new));
23.6 从本教程到实战¶
到这里,你的 Java 基础已经足够开始真正的模组开发。接下来按这个顺序走:
- 读本站的 Forge 开发文档:从 01 章节开始(模组文件结构 → 物品 → 方块 → 注册 → 事件……)
- 抄一个完整小模组:照着做一遍,比如自定义物品+合成配方
- 改需求:把抄的代码改成自己的东西(物品属性、配方、事件效果)
- 独立实现一个功能:比如"右键物品生成闪电"(你已经有能力拆解它:物品 use 事件 + 实体生成 API)
- 进阶主题:GUI(第 6 章)、网络(第 10 章)、实体(第 12 章)、能力(第 7 章)
23.7 小练习¶
- 说出你学的 Java 知识里,对应"事件监听""物品属性配置""注册表"的分别是哪个设计模式/语法
- 写一个事件:玩家右键方块时(
PlayerInteractEvent.RightClickBlock)发送一条消息 - 注册一个自定义方块(参照第 22 章的物品注册方式,注册表换成 BLOCKS)
- 判断以下代码会不会崩:
Minecraft.getInstance()在服务端事件里调用?→ 会!
本章小结¶
- Java 基础全部映射到模组开发:继承/接口/注解/泛型/设计模式
- 主线程铁律:游戏数据操作在主线程,耗时操作别在主线程
- 三大机制:事件总线、注册表、能力系统
- Dist.CLIENT 区分客户端代码
- 下一步:跟着 Forge 开发文档 实战
下一章(最后一章):24 学习路线与资源