本篇教程的视频

本篇教程的源代码

GitHub地址:TutorialMod-ItemGroup-1.20.1

本篇教程目标

  • 理解原版物品栏的注册
  • 使用Fabric API将模组物品加入原版的创造模式物品栏
  • 创建模组的创造模式物品栏,并加入物品

查看源代码

那么在前一篇教程中,我们添加了第一个物品,但现在只能通过give来获得我们的物品,这有些麻烦

所以在这一篇教程中,我们将学习如何将物品加入的创造模式物品栏中

同样的,我们来看看原版的物品栏是怎么写的

Items一样,这一次要找的是ItemGroups这个类

那么,先问大家一个问题,就1.20.1这个版本而言,原版有多少个物品栏?也就是标签页有多少个

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public static final RegistryKey<ItemGroup> BUILDING_BLOCKS = register("building_blocks");
public static final RegistryKey<ItemGroup> COLORED_BLOCKS = register("colored_blocks");
public static final RegistryKey<ItemGroup> NATURAL = register("natural_blocks");
public static final RegistryKey<ItemGroup> FUNCTIONAL = register("functional_blocks");
public static final RegistryKey<ItemGroup> REDSTONE = register("redstone_blocks");
public static final RegistryKey<ItemGroup> HOTBAR = register("hotbar");
public static final RegistryKey<ItemGroup> SEARCH = register("search");
public static final RegistryKey<ItemGroup> TOOLS = register("tools_and_utilities");
public static final RegistryKey<ItemGroup> COMBAT = register("combat");
public static final RegistryKey<ItemGroup> FOOD_AND_DRINK = register("food_and_drinks");
public static final RegistryKey<ItemGroup> INGREDIENTS = register("ingredients");
public static final RegistryKey<ItemGroup> SPAWN_EGGS = register("spawn_eggs");
public static final RegistryKey<ItemGroup> OPERATOR = register("op_blocks");
public static final RegistryKey<ItemGroup> INVENTORY = register("inventory");

答案是14个,也就是这里罗列的14个物品栏注册键

不过,一般我们可见的只有13个物品栏,其中的OPERATOR管理员物品栏
在设置中可以将其打开,里面是放了命令方块结构方块光源方块等特殊的物品

注册键注册方法

这里我们先看看它们注册键的注册方法

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

可以看到,这里调用了RegistryKey.of方法,传入了一个RegistryKeys.ITEM_GROUPIdentifier

这与我们之前看到物品注册中的注册键类似

那么同样,如果搬到我们自己的类中,Identifier还要传入我们模组的modid

物品栏注册

上面是注册键的注册,再接下来我们看看真正的物品栏是这么注册的

挑一个BUILDING_BLOCKS,我们可以看到下面有一个registerAndGetDefault方法,
在这个方法里面定义了原版的14个创造模式物品栏,也定义了每个物品栏中有什么东西

1
2
3
4
5
6
7
8
9
10
11
12
13
Registry.register(
registry,
BUILDING_BLOCKS,
ItemGroup.create(ItemGroup.Row.TOP, 0)
.displayName(Text.translatable("itemGroup.buildingBlocks"))
.icon(() -> new ItemStack(Blocks.BRICKS))
.entries((displayContext, entries) -> {
entries.add(Items.OAK_LOG);
...
})
.build()
);
...

这里的Registry.register很熟悉吧,前面的物品注册用的就是它

第一个参数是registerAndGetDefault方法的形参,其类型为Registry<ItemGroup>,其实就是之前说过的注册表

我们可以在Registries类中找到这个注册表

1
public static final Registry<ItemGroup> ITEM_GROUP = create(RegistryKeys.ITEM_GROUP, ItemGroups::registerAndGetDefault);

这个注册表调用的也是ItemGroupsregisterAndGetDefault方法,我们自己注册物品栏时,直接用这个注册表就可以

Fabric的底层会将我们模组的物品栏塞进这个注册表中,具体的实现感兴趣的同学可以自己去研究Fabric的那些Mixin

第二个参数是注册键,也就是上面注册好的BUILDING_BLOCKS

第三个参数是物品栏的定义,从ItemGroup.create方法开始,一直到最后的build方法

物品栏定义

那么现在,我们重点来看看物品栏的定义

1
2
3
4
5
6
7
8
ItemGroup.create(ItemGroup.Row.TOP, 0)
.displayName(Text.translatable("itemGroup.buildingBlocks"))
.icon(() -> new ItemStack(Blocks.BRICKS))
.entries((displayContext, entries) -> {
entries.add(Items.OAK_LOG);
...
})
.build()

create方法传入两个参数,第一个是物品栏的标签页位置,这里的Row.Top表示在上面那行;
第二个是物品栏的标签页的索引值,从0开始

displayName方法传入一个Text对象,用于定义物品栏的标签页名称,这里使用了Text.translatable方法,
即可以用语言文件来翻译填的内容,我们可以在原版的en_us.json中找到对应的翻译

icon方法传入一个lambda表达式,用于定义物品栏的标签页图标,这里使用了new ItemStack(Blocks.BRICKS)
也就是用原版的砖块作为图标

先提一嘴,ItemStack这个概念与未来讲到的BlockEntity类似。
在游戏中,物品本身是不能存储任何数据的,而像物品图标堆叠数量耐久度等,
都是由ItemStack来存储的。所以在这里我们获取物品栏的图标,
也是实例化一个ItemStack来实现的

entries方法传入一个lambda表达式,用于定义物品栏中有什么物品,这里使用了displayContextentries两个参数。
不过绝大多数情况下,我们只会使用entries

entries.add方法传入一个物品(也可以是方块),将物品加入物品栏,这里加入的是橡木原木

build方法用于构建物品栏,返回一个ItemGroup对象

这就是其中一个物品栏的注册,剩下的13个也是类似的,不过有些物品栏不太一样,比如快捷栏玩家的生存模式物品栏等,
具体的可以大家自己研究

使用Fabric API将物品加入原版物品栏

那么现在,我们已经知道了该如何去创建物品栏,可以开始写我们模组的物品栏了

不过,在此之前,我们先讲讲如何使用Fabric API将物品加入原版物品栏

大家也看到了,上面一堆的entries.add都已经写好了,原版没有提供给我们将自己物品加入到原版物品栏的方法

这个时候,如果你想把之前添加的物品加到原版某个物品栏中时,就得使用Mixin了,将我们的物品塞进原版物品栏中

不过,Fabric已经将这个Mixin封装好了,我们拿过来用就好了,不用我们自己去写Mixin

添加方法

这里我们到ModItems中,写上一个返回值为空的方法,形参为FabricItemGroupEntries entries

1
2
3
4
private static void addItemToItemGroup(FabricItemGroupEntries entries) {
entries.add(ICE_ETHER);
entries.add(RAW_ICE_ETHER);
}

这个FabricItemGroupEntriesFabric的一个API,先将模组物品加入到Fabric的物品栏

初始化

随后,我们在之前写的,用来初始化的注册方法中写上

1
ItemGroupEvents.modifyEntriesEvent(ItemGroups.NATURAL).register(ModItems::addItemToItemGroup);

这里我们调用了ItemGroupEvents.modifyEntriesEvent方法,
传入了一个ItemGroups.NATURAL,也就是原版自然方块物品栏

后面的regsiter再调用刚刚写的方法

举一反三

我们还有一个物品对吧,我想将它加入到原版原材料物品栏,该怎么做呢?很简单,把上面步骤再重复一遍就好了

1
2
3
private static void addItemToItemGroup2(FabricItemGroupEntries entries) {
entries.add(CARDBOARD);
}

并且初始化

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

随后,我们进入游戏,再到原版的物品栏中,就可以看到我们模组的物品了

创建模组的物品栏

我们已经知道了如何使用Fabric API将物品加入原版物品栏,接下来就按照原版的方法来写我们模组的物品吧

这里我们创建一个ModItemGroups类,用于注册我们的物品栏

1
2
3
public class ModItemGroups {

}

注册键注册方法

我们先写一个注册键注册方法,把原版的搬过来改一下就可以了

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

这里我们将Identifier加上我们模组的modid

注册注册键

随后,我们注册物品栏的注册键

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

和原版的一样,一句话搞定

物品栏注册

那么最后,就是物品栏的注册

这里我们先写一个初始化注册方法,和前面物品注册里的一样

1
2
3
public static void registerGroups() {

}

随后我们在这个初始化注册方法中写上物品栏注册语句

1
2
3
4
5
6
7
8
9
10
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);
entries.add(ModItems.RAW_ICE_ETHER);
}).build());

那么,这里面的参数基本上都解释过了,这里就不再赘述

第一个参数是物品栏的注册表Registries中的直接拿过来用即可

create中的参数,注意不要和原版的重合,不然会出问题,原版已经将第一页排满了,使用我们模组的物品栏会从第二页开始

另外,如果说像这里的create写上8,在你只有这一个物品栏的情况下,它也还是在第二页的第一个,前面缺省那么后面还是会顶上去的

不过,好像Fabric的物品栏是默认以物品栏的开头字母排序的,也许与这个索引没有关系,
Fabric也没有提供像ForgeNeoForge那样可以改变物品栏顺序的方法

调用初始化方法

不要忘了在模组主类的onInitialize方法调用上面的初始化方法

1
2
3
4
5
6
7
8
@Override
public void onInitialize() {

ModItems.registerItems();
ModItemGroups.registerGroups();

LOGGER.info("Hello Fabric world!");
}

举一反三 & 简化

那么,联想到我们之前注册物品,哎?能不能用static final来像物品注册那样写呢?

能的兄弟,能的

ItemGroups中的registerAndGetDefault,其返回值是ItemGroup,返回的其实是第14个物品栏

那么现在,我们就可以直接定义一个ItemGroup类型的静态字段

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

这里也直接省去了注册键的写法,直接将第二个参数换为Identifier,也就是我们在注册物品时提到的简化写法

这里的create,直接写成null-1,交给Fabric来处理模组的物品栏(按照物品栏名称的首字母排序)

如果你按照它Wiki的方法,用FabricItemGroup来注册物品栏,那么其实这里面写的就是null和-1

不过,这倒是有一个缺点,在未来的数据生成中会讲

语言文件

最后,别忘了在语言文件中添加物品栏的翻译

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

启动游戏测试

那么,现在我们就可以启动游戏测试了

在物品栏的第二页,我们就能看到我们加入的两个物品栏了