本篇教程由作者设定未经允许禁止转载。

注:这不是一篇0基础教程,只是一篇及其片面的参考性指导。

这篇教程不会提供过多实际的代码片段(会使用“伪代码”代替实际的 Java 代码),只会提供设计思路以及基本的使用案例。

Minecraft 在愈来愈多的版本迁徙中,即使相当多部分已被数次修改,但是其内核和总体逻辑架构依然不变。

所以这篇教程所传递的思路基本能在所有版本通用,只是某些实现的细节存在差异。

注册部分

Minecraft 的大多数内容,均有被注册。

生物群系、方块、物品、实体乃至粒子,皆是需要注册才能正常使用的。

注册的方式有多种,各个版本都有不同的实现。

在查看 Modloader 提供的接口前,我们先看看原版的注册方式:

Beta 1.7.3

// 直接从数组获取注册内容
var things = new Object[<ID上限>]
things[0] = Foo()

1.7.10

var registry : Registry = Registry.make("things")
val foo = registry.register(1145, "language:foo", Foo())

1.18

val foo = Registry.register(Registry.OBJECTS, Identifier("language", "foo"), Foo())

1.19

val foo = Registry.register(Registries.OBJECTS, Identifier("language", "foo"), Foo())

然后是 Modloader 提供的接口:

Fabric

Fabric 直接调用简洁的原版注册器,并且无需考虑类的加载顺序问题。

Legacy Fabric 及以下则需要使用相关的 API,具体用例参考其文档,此处不过多赘述。

Forge

Forge 需要通过 Forge 注册器间接访问原版注册器来完成注册,而且需要注意类的加载顺序问题,所以实现上会相对复杂。

1.7.10

@Mod.EventHandler
public void doCommon(FMLInitializationEvent event) {
  GameData._register(<字串ID>, <注册项>);
}

1.12.2

// 方法一
@SubscribeEvent
public void registerThings(RegistryEvent.Register<Object> event) {
  event.getRegistry().register(new Foo().setRegistryName(<id>));
  event.getRegistry().registerAll(new Foo[]{new Bar1().setRegistryName(<id>, new Bar2().setRegistryName(<id>});
}
// 方法二(不推荐)
@Mod.EventHandler
public void init(FMLInitializationEvent event) {
  // 注册代码
}

1.20

参阅 Forge 的官方或社区文档,此处不过多赘述。

注意类的加载顺序问题,请确保 Mod 入口点被加载时时包含注册相关代码的类也被加载。

注册事件和常规类加载并不是同步的,注册初始化的动作先于常规类加载。

大致的执行顺序如下(从左到右依次进行):Mod 入口点初始化、注册事件、常规类加载。

如果你尝试在注册事件结束的时候注册,Forge 会直接抛出异常。

实在不知道如何正确地处理类加载,请参考以下代码:

public class 注册物品 {
  public static void 加载该类() {
  }
  
  // 实例化注册项
}
public class 注册类 {
  public static void 加载该类() {
    // 请确保你的注册项在将要被注册时也被加载了!
    注册物品.加载该类();
  }
  
}
@Mod
public class ModMain {
  static {
    注册类.加载该类();
  }
}

“数据”部分

“数据”,此处指不需要经过注册,但是游戏正常运行所依赖的数据。

数据包、资源包中的数据便是“数据”(data、assets),1.12.2 data 中的内容存放在 assets 中,1.7.10 则直接由代码创建。

编写数据包“数据”的部分请参阅 Minecraft Wiki,此处不过多赘述。

在1.12.2,方块的模型会根据注册键自动绑定,而物品的模型需要手动指定。

ModelLoader.setCustomModelResourceLocation(<item>, <meta>,
    new ModelResourceLocation(<id>, "inventory"));

燃料热值

1.7.10

实现 IFuelHandler 即可。

1.12.2

IFuelHandler 依然可用,但是可以直接覆盖物品基类中的 getFuelBurnTime 方法。

【未完待续】