提醒一下

本教程相当于是我在写笔记

什么是物品栏?

我们来说说什么是物品栏,好的下图就是物品栏,再见(hh

玩过一些中大型mod内容基本上都知道,创造界面在原版基础上,mod物品大多数都不是在原版物品栏,如材料,方块等中里面的

而是在原版GUI界面变化出的翻页中里面拥有,mod自己图标,名字的物品栏

我这节学的就是如何实现上图

Fabric API方法

介绍

Fabric API提供了能将我们的物品加入原版物品栏的方法,本质上使用的是Mixin。

不过,一旦你的模组里面使用了Fabric API,那么你的模组就需要这个API运行,打包之后放到真正的游戏中就要安装Fabric API。

使用方法

Fabric API提供了一个FabricItemGroupEntries类,
我们先将物品加入到这个entries中,再由ItemGroupEvents添加至原版物品栏

添加物品

1
2
3
private static void addItemToIG(FabricItemGroupEntries fabricItemGroupEntries){
fabricItemGroupEntries.add(ICE_ETHER);
}

这里我们先创建一个方法,将我们的物品加入到entries中。

这个方法目的就是给你的新建的一个物品栏取个名字

添加至原版物品栏

然后我们将之前创建的物品加入到entries中,这里我们使用的是ICE_ETHER也就是上面取的名。

1
ItemGroupEvents.modifyEntriesEvent(ItemGroups.INGREDIENTS).register(ModItems::addItemToIG);

在之前写的初始化方法registerModItems中添加这一行代码,这样我们的物品就会被加入到原版的材料物品栏中。

modifyEntriesEvent方法的参数是一个ItemGroup,这里我们使用的是ItemGroups.INGREDIENTS,这个是原版的材料物品栏。
具体其他的物品栏可以查看ItemGroups类。(注意,你需要genSource才能够正确调用ItemGroups中的字段)

后面的register方法直接引用我们之前创建的方法即可。

  1. 注册事件监听器
    • ItemGroupEvents.modifyEntriesEvent(ItemGroups.INGREDIENTS).register(ModItems::addItemToIG);
    • 这行代码注册了一个事件监听器,当“原料”物品组的内容需要被修改时,addItemToIG 方法会被调用。
  2. 事件触发
    • 当 Minecraft 游戏需要修改“原料”物品组的内容时,事件处理机制会调用所有注册的回调函数。
    • 在这种情况下,addItemToIG 方法会被调用,并传入一个 FabricItemGroupEntries 对象。
  3. 方法调用
    • addItemToIG 方法的签名是 private static void addItemToIG(FabricItemGroupEntries fabricItemGroupEntries)
    • 事件处理机制会自动传入一个 FabricItemGroupEntries 对象,这个对象包含了当前物品组的所有条目。
    • addItemToIG 方法中,你可以对这个 FabricItemGroupEntries 对象进行操作,例如添加新的物品。
    • 这个静态方法引用说简单点就是,省略了一个接受 FabricItemGroupEntries 参数的函数的基础上再直接引用了一个ModItems 类的静态addItemToIG 方法。

举一反三

那如果说我现在还有一个物品,想加入到原版的杂项(MISC)物品栏中,应该怎么做呢?

1
2
3
4
private static void addItemToIG2(FabricItemGroupEntries fabricItemGroupEntries){
fabricItemGroupEntries.add(FIRE_ETHER);
fabricItemGroupEntries.add(WATER_ETHER);
}

这里我们再创建一个方法,与之前的方法类似,将我们的物品加入到entries中。

添加多个物品直接用add方法即可。

1
ItemGroupEvents.modifyEntriesEvent(ItemGroups.MISC).register(ModItems::addItemToIG2);

那么同样的,使用不同的物品栏,只需要将ItemGroups.INGREDIENTS替换成ItemGroups.MISC即可。

然后再引用addItemToIG2方法即可。`

原版方法

介绍

使用Fabric API并不能创建自定义的物品栏,只能将物品加入到原版的物品栏中。
所以说如果想要创建自定义的物品栏,还是需要使用原版的方法。

查看源代码

我们先来看看原版是如何添加物品到物品栏的。上面也提到过了,原版物品栏的注册在ItemGroups类中。

1
2
java
public static final RegistryKey<ItemGroup> INGREDIENTS = ItemGroups.register("ingredients");

首先我们看到它的注册,这里使用的是ItemGroups.register方法,这个方法是一个静态方法,返回一个RegistryKey<ItemGroup>

1
2
3
4
java
private static RegistryKey<ItemGroup> register(String id) {
return RegistryKey.of(RegistryKeys.ITEM_GROUP, Identifier.ofVanilla(id));
}

看着这个方法的返回语句,是否和之前物品的注册有些类似呢?是的没错,同样的,Identifier要我们自行更改

那么除此之外,还有什么要注意的呢?我们可以看到registerAndGetDefault方法中一堆的entries.add(...)

那么这些东西便是将物品加入到物品栏的方法了

  • 1
    2
    3
    4
    5
    6
    7
    8
    java
    Registry.register(registry, INGREDIENTS,
    ItemGroup.create(ItemGroup.Row.BOTTOM, 3)
    .displayName(Text.translatable("itemGroup.ingredients"))
    .icon(() -> new ItemStack(Items.IRON_INGOT))
    .entries((displayContext, entries) -> {
    ...
    }).build());
  • 这里我们以INGREDIENTS为例,我们可以看到它使用的是Registry.register方法,这个方法是用来注册物品栏的
  • 但是首先我们得知道这个registry应该写什么,其实它就是Registries.ITEM_GROUP,这个是原版的物品栏注册器
  • 1
    public static final Registry<ItemGroup> ITEM_GROUP = Registries.create(RegistryKeys.ITEM_GROUP, ItemGroups::registerAndGetDefault);
    • create(RegistryKeys.ITEM_GROUP, ItemGroups::registerAndGetDefault):使用 create 方法创建一个注册表。
    • RegistryKeys.ITEM_GROUP:这是注册表的键,表示物品组注册表。
    • ItemGroups::registerAndGetDefault:这是一个方法引用,表示 ItemGroups 类中的 registerAndGetDefault 方法。当注册表需要初始化时,会调用这个方法来注册默认的物品组并返回该物品组。
  • 我们在Registries类中可以看到,ITEM_GROUP是一个Registry<ItemGroup>类型的常量,创建的时候使用的是registerAndGetDefault方法

  • 而我们自己写的时候直接使用Registries.ITEM_GROUP即可

  • 那么接下来,我们看到INGREDIENTS的第二个参数是INGREDIENTS,这个是一个RegistryKey<ItemGroup>类型的常量,这个是物品栏的ID

  • 然后我们看到ItemGroup.create方法,这个方法是用来创建物品栏的,里面有一些参数,比如ItemGroup.Row.BOTTOM,这个是指物品栏所在的位置,这里的BOTTOM表示它在GUI的下面那行中,3是指在第4个;
    那么其他的还有ItemGroup.Row.TOP,这个是指在GUI的上面那行中

  • 我们接着看displayName方法,这个是用来设置物品栏的名字的,这里使用的是Text.translatable方法,这个方法是用来设置物品栏名字的,这里使用的是itemGroup.ingredients,这个是一个翻译键,我们可以在语言文件中找到这个翻译键,然后设置物品栏的名字

  • 然后我们看icon方法,这个是用来设置物品栏的图标的,这里使用的是Items.IRON_INGOT,这个是物品栏的图标,这里使用的是铁锭

  • entries方法是用来设置物品栏的物品的,这里使用的是一个lambda表达式,这个lambda表达式有两个参数,
    一个是displayContext,一个是entries,这个displayContext是用来设置物品栏的显示的,这里没有用到,
    entries是用来设置物品栏的物品的,这里的东西省略了,我们就不展开了

创建ModItemGroups类

1
2
3
4

public class ModItemGroups {

}

注册方法

1
2
3
4

private static RegistryKey<ItemGroup> register(String id) {
return RegistryKey.of(RegistryKeys.ITEM_GROUP, Identifier.of(TutorialMod.MOD_ID, id));
}

那么Identifier同样的还是要改

注册物品栏Key

1
2

public static final RegistryKey<ItemGroup> TUTORIAL_GROUP = register("tutorial_group");

这里的都是仿照原版在编写

初始化注册方法

1
2
3
4

public static void registerModItemGroups() {

}

不要忘记到主类调用这个初始化方法

1
2
java
ModItemGroups.registerModItemGroups();

注册物品栏

这个语句直接写在初始化注册方法中就好了,这样在模组初始化时就可以注册完成

1
2
3
4
5
6
7
8
java
Registry.register(Registries.ITEM_GROUP, TUTORIAL_GROUP,
ItemGroup.create(ItemGroup.Row.TOP, 7)
.displayName(Text.translatable("itemGroup.tutorial_group"))
.icon(() -> new ItemStack(ModItems.ICE_ETHER))
.entries((displayContext, entries) -> {
entries.add(ModItems.ICE_ETHER);
}).build());

这个就是和原版一模一样的语句,不同的地方在于第一个参数我们直接用了Registries.ITEM_GROUP

ItemGroup.create方法里面的参数,不要和原版重叠(虽然我没试过会发生什么),然后具体的位置其实可以在确定有多少个物品栏之后再写。
实际情况是,如果前面空着,比如说我里面的参数写8,但7的位置没有东西,那么你新增的物品栏的位置还是7TOPBOTTOM同理,前者补完补后者

简化?YES

是不是觉得还是很复杂?能不能简化呢?当然可以(不过有个弊端,后面dataGen跑语言文件生成的时候就不能直接调写的KEY了,不过直接复制displayName也一样)

这里我们就要利用返回值为ItemGroup这个特性,还记得源代码里面registerAndGetDefault方法的返回值吗?没错,它就是ItemGroup

所以在这里,我们依旧可以选择让它修饰为和Item一样的static final,利用初始化完成注册

1
2
3
4
5
6
7
8
9
java
public static final ItemGroup TUTORIAL_GROUP = Registry.register(Registries.ITEM_GROUP, Identifier.of(TutorialMod.MOD_ID, "tutorial_group"),
ItemGroup.create(null, -1).displayName(Text.translatable("itemGroup.tutorial_group"))
.icon(() -> new ItemStack(ModItems.ICE_ETHER))
.entries((displayContext, entries) -> {
entries.add(ModItems.ICE_ETHER);
entries.add(Blocks.BRICKS);
entries.add(Items.DIAMOND);
}).build());

中间的Identifier直接写你的MOD_IDid即可

然后我这里的ItemGroup.create直接使用null-1,这个就直接让它填在最后一个位置上,如果你使用FabricItemGroup进行创建,它就是这样写的

这样就可以简化很多,另外你也可以添加原版中有的物品,比如Blocks.BRICKSItems.DIAMOND,这样就可以直接添加原版的物品了

不过,初始化方法还是要写的,也记得在主类中调用这个初始化方法

整体代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
java
public class ModItemGroups {
// public static final RegistryKey<ItemGroup> TUTORIAL_GROUP = register("tutorial_group");
// private static RegistryKey<ItemGroup> register(String id) {
// return RegistryKey.of(RegistryKeys.ITEM_GROUP, Identifier.of(TutorialMod.MOD_ID, id));
// }
// public static void registerModItemGroups() {
// Registry.register(Registries.ITEM_GROUP, TUTORIAL_GROUP,
// ItemGroup.create(ItemGroup.Row.TOP, 7)
// .displayName(Text.translatable("itemGroup.tutorial_group"))
// .icon(() -> new ItemStack(ModItems.ICE_ETHER))
// .entries((displayContext, entries) -> {
// entries.add(ModItems.ICE_ETHER);
// }).build());
// TutorialMod.LOGGER.info("Registering Item Groups");
// }

public static final ItemGroup TUTORIAL_GROUP = Registry.register(Registries.ITEM_GROUP, Identifier.of(TutorialMod.MOD_ID, "tutorial_group"),
ItemGroup.create(null, -1).displayName(Text.translatable("itemGroup.tutorial_group"))
.icon(() -> new ItemStack(ModItems.ICE_ETHER))
.entries((displayContext, entries) -> {
entries.add(ModItems.ICE_ETHER);
entries.add(Blocks.BRICKS);
entries.add(Items.DIAMOND);
}).build());

public static void registerModItemGroups() {
TutorialMod.LOGGER.info("Registering Item Groups");
}
}

语言文件

1
2
3
4
json
{
"itemGroup.tutorial_group": "Tutorial Group"
}

这个就是我们的物品栏的名字,这里的itemGroup.tutorial_group就是我们在displayName方法中设置的翻译键

测试

现在我们启动游戏,由于原版的物品栏已经填满了第一页,不过它会自动生成一个翻页符,我们点击翻页符,就可以看到我们的物品栏了