跳转至

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)
 ├── 实体更新
 ├── 方块更新
 ├── 事件触发
 └── 渲染(客户端)

规则:

  1. 模组代码默认在主线程执行(事件、tick 方法都是)
  2. 别在主线程做耗时操作(读大文件、网络请求、复杂计算)→ 卡服/掉帧
  3. 别在非主线程碰游戏数据(世界、实体、方块)→ 崩溃
  4. 需要跨线程操作时,用 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 基础已经足够开始真正的模组开发。接下来按这个顺序走:

  1. 读本站的 Forge 开发文档:从 01 章节开始(模组文件结构 → 物品 → 方块 → 注册 → 事件……)
  2. 抄一个完整小模组:照着做一遍,比如自定义物品+合成配方
  3. 改需求:把抄的代码改成自己的东西(物品属性、配方、事件效果)
  4. 独立实现一个功能:比如"右键物品生成闪电"(你已经有能力拆解它:物品 use 事件 + 实体生成 API)
  5. 进阶主题:GUI(第 6 章)、网络(第 10 章)、实体(第 12 章)、能力(第 7 章)

23.7 小练习

  1. 说出你学的 Java 知识里,对应"事件监听""物品属性配置""注册表"的分别是哪个设计模式/语法
  2. 写一个事件:玩家右键方块时(PlayerInteractEvent.RightClickBlock)发送一条消息
  3. 注册一个自定义方块(参照第 22 章的物品注册方式,注册表换成 BLOCKS)
  4. 判断以下代码会不会崩:Minecraft.getInstance() 在服务端事件里调用?→ 会!

本章小结

  • Java 基础全部映射到模组开发:继承/接口/注解/泛型/设计模式
  • 主线程铁律:游戏数据操作在主线程,耗时操作别在主线程
  • 三大机制:事件总线、注册表、能力系统
  • Dist.CLIENT 区分客户端代码
  • 下一步:跟着 Forge 开发文档 实战

下一章(最后一章):24 学习路线与资源

🏠 返回总站