观察图片可以猜测,本节运用到的知识内容:
- 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 提供了一些我们常用的事件,这里笔者可以根据名称自行理解用途.