效果图

观察图片可以猜测,本节运用到的知识内容:

  • DeferredRegister - 创建并注册游戏元素
  • I18n - 为元素添加本地化和材质
  • CompoundTag - 为物品添加功能
  • ClientTooltipEvent - 为物品添加 Tooltips (使用封装好的事件)

下面我们来基于 Architectury API 来编写我们的第一个物品 —— 经验储存器.

# 创建并注册物品

trou.arch.item 包下创建 ItemExpContainer 类,使其继承自原版的 Item 类

位于common: trou/arch/item/ItemExpContainer.java
public class ItemExpContainer extends Item {
    public ItemExpContainer() {
        super(new Properties().stacksTo(1).tab(CreativeModeTab.TAB_TOOLS));
    }
}

熟悉 Forge 开发的读者在这里可能产生疑问,读者可能尝试设置 RegistryName 和 TranslationKey, 结果发现没有这样的方法。

实际上,Forge 为原版的 Item 进行了许多层封装,读者可以尝试查阅 forge 模块中的 Item 类,会发现它实现了 IForgeItem, 继承自 ForgeRegistryEntry. Forge 为了易用性舍弃了轻量的结构. Forge 将注册名的设置封装在了物品内部,这与原版的逻辑不同.

而 Fabric 中并没有额外的封装,而是基于 Minecraft 原本的样子。读者可能意识到,实际上 Architectury 是更倾向于原版,或者说 Fabric 的。所以我们在开发中,RegistryName 实际上是在注册的时候提供的。

Architectury 这里采用原版 (Fabric) 的架构,而为 Forge 进行兼容是合理的。在跨平台开发中,往往要基于低适配高,这也是可理解的。正如 DisplayPort 转 HDMI 容易,而 HDMI 转 DisplayPort 困难的道理。

下面我们要注册这个物品,我们采用 Architectury 封装的 DeferredRegister 进行注册。相同的,如果读者要注册其他的方块、附魔、BlockEntityType 等元素,也可以使用相同的方法。读者可以查阅 Registry 中的常量

trou.arch.object 包下创建 ModItems

位于common: trou/arch/object/ModItems.java
public class ModItems {
    private static final DeferredRegister<Item> ITEMS = DeferredRegister.create(Arch.MOD_ID, Registry.ITEM_REGISTRY);
    public static final RegistrySupplier<Item> EXP_CONTAINER_ITEM = ITEMS.register("exp_container_item", ItemExpContainer::new);
    public static void register() {
        ITEMS.register(); //DeferredRegister 的 register 方法需要在 init 阶段被调用
    }
}

为了注册物品,我们调用 DeferredRegister.create 来创建一个注册器

针对每一个物品,我们需要调用注册器的 register 方法来注册,需要传入注册名和构造方法

之后,我们在模组的 init 阶段调用 ModItems.register() 方法

<mark style="color:red;"> 因为 Forge 的某些限制,我们需要在 forge 模块中手动注册一次 Architectury 的事件总线 </mark>

位于forge: trou/arch/forge/ArchForge.java
@Mod(Arch.MOD_ID)
public class ArchForge {
    public ArchForge() {
        // 这里一定要注册上 Architectury 的 EventBus
        EventBuses.registerModEventBus(Arch.MOD_ID, FMLJavaModLoadingContext.get().getModEventBus());
        Arch.init();
    }
}

之后分别进入 fabric 和 forge 客户端,应该都能看到我们的紫黑块物品了。

笔者这里储存了 ITEMS.register 返回的 RegistrySupplier, 如果在代码的别处需要用到 ItemExpContainer 的实例时,可以调用 RegistrySupplier 的 get 方法.

不难理解,其实 RegistrySupplier 充当了保存对象和它的注册名的功能

# 为物品添加本地化和材质

common 模块中的 assets 在编译的时候会被合并进 forge 和 fabric 模块中,所以我们只需按照熟悉的方式将本地化文件和材质放在 common 模块中就可以了.

位于common: assets/arch/lang/zh_cn.json
{
  "item.arch.exp_container_item": "经验储存器",
  "tooltip.exp_container": "已储存经验: %s exp"
}
位于common: assets/arch/lang/en_us.json
{
  "item.arch.exp_container_item": "Experience Container",
  "tooltip.exp_container": "Stored experience: %s exp"
}
位于common: assets/arch/models/item/exp_container_item.json
{
  "parent": "item/generated",
  "textures": {
    "layer0": "arch:item/exp_container_item"
  }
}
位于common: assets/arch/textures/item/exp_container_item.png
// 在这里放置物品的材质

# 为物品添加功能

根据我们的需求,需要覆盖物品的 use 方法,并编写我们的逻辑,这里相信有经验的读者可以完成

位于common: trou/arch/item/ItemExpContainer.java
@Override
public InteractionResultHolder<ItemStack> use(@NotNull Level level, @NotNull Player player, @NotNull InteractionHand usedHand) {
    ItemStack stack = player.getItemInHand(usedHand);
    if (level.isClientSide || usedHand == InteractionHand.OFF_HAND) return InteractionResultHolder.fail(stack);
    CompoundTag tag = stack.hasTag() ? stack.getTag() : new CompoundTag();
    assert tag != null;
    if (player.isShiftKeyDown()) {
        player.giveExperiencePoints(tag.getInt("exp"));
        tag.putInt("exp", 0);
    } else {
        int exp = player.totalExperience;
        player.giveExperiencePoints(-exp);
        tag.putInt("exp", tag.getInt("exp") + exp);
    }
    stack.setTag(tag);
    return InteractionResultHolder.sidedSuccess(stack, level.isClientSide());
}

# 监听 Tooltip 事件


ArchtecturyAPI 建立了一套位于 Forge 和 Fabric 之上的事件封装层。在 Forge 中,我们创建方法并添加 @SubscribeEvent 注解来让 Forge 注册我们的事件。而在 ArchtecturyAPI 中,我们使用一种更为 "静态" 的方式

ArchitecturyAPI 为我们提供了一些常用的事件,在上一章节可以找到.

这里我们需要监听 ClientTooltipEvent 事件,首先创建一个类来注册所有的事件

位于common: trou/arch/object/ModEvents.java
public class ModEvents {
    public static void register() {
        // 需要传入的是事件处理器的方法
        ClientTooltipEvent.ITEM.register(ItemExpContainer::append);
    }
}
位于common: trou/arch/Arch.java
public class Arch {
    public static void init() {
        ModEvents.register(); // 在 init 阶段注册事件
    }
}

然后我们按照代码提示,在 ItemExpContainer 类中补全 append 方法来处理事件

位于common: trou/arch/item/ItemExpContainer.java
public static void append(ItemStack stack, List<Component> lines, TooltipFlag flag) {
    if (stack.getItem() instanceof ItemExpContainer) {
        CompoundTag tag = stack.hasTag() ? stack.getTag() : new CompoundTag();
        assert tag != null;
        lines.add(new TranslatableComponent("tooltip.exp_container", tag.getInt("exp")));
    }
}

ClientTooltipEvent.ITEM.register 背后实际上是利用 @ExpectPlatform 来处理的

针对两个不同的平台,API 分别写了相应的事件处理器的实现,因此简化了我们对于事件的注册

对于一个特定的需求,首先我们要检查 API 是否已经为我们提供了相应的方法

如果没有提供相应的方法,我们再去手动针对两个平台进行实现

最后进入游戏,我们的第一个物品已经成功被加入了

# 再次开始

首先我们先从官方文档出发,看看 Architectury API 都为我们封装了哪些易于方法的方法和类。

读者可以暂时跳过这部分,进入下文的实例环节,在以后的开发中如果有需要再来查阅.

注:某些类的描述添加了笔者自己的理解,并且附加了可能的应用方法,以便于读者自行摸索下文的例子中没有提到的使用方法。

# 实用的抽象层

类名 描述
Platform 提供了获取当前加载器的一些规范的类 (比如配置文件位置,Mod 列表等方法)
Registries 提供了注册物品,方块等元素的一系列方法和接口的类 (比如 RegistryProvider)
KeyBindings 提供了一些关于按键绑定的方法
CreativeTabs 提供了创建 CreativeTab 的方法
MenuRegistry 提供了关于 Menu 界面 (例如统计信息,设置) 的一些注册方法
RenderTypes 统一了两个加载器的 RenderTypes
ReloadListeners 重载 Listeners 的注册器,现在已经改成了 ReloadListenerRegistry, 但是官方文档还没有更新
CriteriaTriggersRegistry 提供了一些关于成就触发器的方法,现在这个类已经弃用,如果读者要使用,请调用 CriteriaTriggers
ColorHandlers 提供了一些关于颜色的方法
BlockEntityRenderers 提供了一些绑定 BlockEntity 渲染器的方法
BiomeModifications 提供了一些关于生物群系的方法
PackRepositoryHooks 提供了一些关于高版本行为包的钩子方法

# 网络和网络包的抽象层

类名 描述
NetworkManager 提供了一个类似于 Fabric 的网络包管理系统
NetworkChannel 提供了一个类似 Forge Channel 的包收发系统

# 抽象出来的一些钩子方法

类名 描述
BiomeHooks 提供了一套重新封装的生物群系属性。需要获取生物群系属性的可以调用这个类中的方法
BlockEntityHooks 将 BlockEntity 的数据变化推送至客户端的方法,会调用区块的 blockChanged 方法来推送
DyeColorHooks 提供了将 DyeColor 转换成颜色的方法
EntityHooks 提供了关于 Entity 的 Collision 相关方法,源文档中提供的已弃用
ExplosionHooks 提供了从爆炸 (Explosion) 获取爆炸位置的方法
ItemEntityHooks 可以获取一个物品实体的 lifespan
PlayerHooks 可以获取玩家是否是 fakePlayer
ScreenHooks 提供了一些关于在屏幕上渲染的方法

# 抽象出来的一些事件

事件

Architectury API 提供了一些我们常用的事件,这里笔者可以根据名称自行理解用途.