本篇教程的视频:

本篇教程源代码:

GitHub地址:TutorialMod-ItemGroup-1.21

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方法直接引用我们之前创建的方法即可。

举一反三

那如果说我现在还有一个物品,想加入到原版的杂项(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
public static final RegistryKey<ItemGroup> INGREDIENTS = ItemGroups.register("ingredients");

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

1
2
3
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
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);

我们在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
public class ModItemGroups {

}

注册方法

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

那么Identifier同样的还是要改

注册物品栏Key

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

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

初始化注册方法

1
2
3
public static void registerModItemGroups() {

}

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

1
ModItemGroups.registerModItemGroups();

注册物品栏

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

1
2
3
4
5
6
7
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
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
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
{
"itemGroup.tutorial_group": "Tutorial Group"
}

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

测试

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