Skip to main content

1.1 第一个物品

Part A. 实战

本节了解,创建一个物品就 3 步【所有物品都是这样】:

  1. 创建自己的物品类,一般继承于原版的物品的类 Item
  2. 在合适的位置实例化这个物品;
  3. 将此物品对象注册进游戏;

我们先以一个原版游戏中没有 “黑曜石锭” 为例:

public class ObsidianIngot extends Item {
public ObsdianIngot() {
super(new Properties().group(ItemGroup.MATERIALS));
}
}

这里我们就写了一个构造函数,向父类的构造函数传递了一个 Properties 类的对象,而这个 Properties 对象调用了 group()表示设置物品所在组为 Materials 组(杂项栏)

传给父类的 Properties 对象的作用是规定了物品的一些属性,比如:是不是食物,或者这个物品在创造模式的哪一个物品栏。这里的 group() 方法就是设置了物品在创造模式的杂项栏。

如果不调用 group() 方法设置,那么这个物品就不会出现在创造模式物品栏,只能用 /give 获取;

然后是实例化 + 注册。现在的 Forge 引入了 DeferredRegister 类:

public class ItemRegistry {
public static final DeferredRegister<Item> item_reg =
DeferredRegister.create(ForgeRegistires.ITEMS, Utils.MOD_ID);
public static final RegistryObject<Item> obsdianIngot =
item_reg.register("obsidian_ingot", ObsidianIngot::new);
}

先使用 DeferredRegister 泛型类的静态方法 create(<ForgeRegistries.TYPE>, <mod_id>)mod_id 的 mod 创建了一个 DeferredRegister<Type> 对象,表示这是专门注册 Type 类型实体的类

紧接着告诉这个对象要注册 “注册名为 "obsdian_ingot"(不能大写)、其对象构造方法为 ObsidianIngot::new” 的一个类的 supplier

最后,由于这个 DeferredRegister 泛型类帮助我们注册物品需要依赖注入事件。显然注册是一种初始化的事件,所以将 DeferredRegister 对象注入 Mod 总线(我们以 0.9 中的项目为例):

@Mod(Utils.MOD_ID)
public class HelloMC {
public HelloMC() {
// DeferredRegister 的 register 方法如果参数是总线,就将自己注入总线中
ItemRegistry.item_reg.register(
// 这里前面说过,获取 Mod 总线
FMLJavaModLoadingContext.get().getModEventBus()
);
}
}

运行后会发现新的、没有材质的物品出现在创造模式的物品栏中。

Part B. 理论:Item & Register

上面是实战,我们从理论层面归纳一下,方便记忆:

我们想要创建一个新的物品,它必然需要继承于原版物品的类型,以方便对物品属性的添加。一般来说:

  • 新的物品继承于 Item 类型,目前其构造函数的参数是 Properties 类型,用于配置物品基本属性;

    • Properties 类型可以使用 setter group() 来设置属性内容:“物品应放在创造模式物品栏的哪里”;
  • 将一个自定义的物品类型(Item),或者其他对象注册进程序时,需要找到合适的时机(也就是 Mod 加载时,这个事件我们说过在 Mod 总线上)。但是直接使用事件的方法会增大 mod 的维护成本,因此 forge 提供了一个委托注册的类:DeferredRegistry<T>

怎么使用 deferred register 呢?为什么要使用 deferred register?官方文档说:

DeferredRegister is the recommended way to register objects.

  • It allows the use and convenience of static initializers while avoiding the issues associated with it.
  • It simply maintains a list of suppliers for entries and registers the objects from those suppliers during RegisterEvent.

所以,使用延迟注册器(deferred register),可以委托它进行对象的注册,需要告诉它要注册的对象名称,以及实例创建方法(supplier),以便延迟注册器在合适的时机帮你注册这些对象。

创建一个 deferred register 的工厂方法:

/* 第一参数指明推迟注册器要向什么类型的注册表注册对象。
* Forge 中内置了一些注册表对象的常量供我们选择: ForgeRegistries.*
*
* 第二参数指明注册器是为哪个 mod 实例注册对象。这就是我们在 @Mod 中给 mod 起的唯一标识 */
DeferredRegister.create(IForgeRegistryEntry<T> reg, String modid);
/* 返回一个 DeferredRegister<T> 对象 */

我们可以委托它注册对象,就要告诉它映射关系、创建实例的方法(对象什么名称、怎么实例化):

DeferredRegister.register(String name, Supplier<T> sup);
/* 返回一个 RegisterObject<T>,即包裹了被注册对象的对象 */

注意,在 mod 之后的部分中,如果想要以已注册对象的方式来访问这个对象,请使用这里的返回值 RegisterObject<T>,其中的 get() 方法能获得已注册对象的完整信息(还是 Item 类,但是一些信息填写完整了):

RegisterObject.get() -> Item;

在上面的 register 语句被执行后,deferred register 会记住这条关系,等待时机(延迟)进行注册。那么什么时候注册?

我们知道,mod 的内容的初始化的时机是交给 mod 总线的,所以,我们还需要把 deferred register 注册到 mod 总线中,以便 mod 总线在 mod 初始化的合适时机提醒 deferred register 开始注册。

DeferredRegister.register(IEventBus bus);

这条重载方法就是让 deferred register 监听总线,等到程序准备好了,就会触发 mod 总线的事件,从而 deferred register 开始注册之前委托的内容。

显然,这个注册监听的步骤应该写在 mod 总的构造函数中,在 mod 一启动时就要监听。

记住这个 DeferredRegister,它不仅可以注册 Item,还可以注册其他类型的对象。