物品组

前面我们成功向 MC 中添加了一个 Mod 物品,但是,它不像原版物品,还不存在于任何物品组中,你不能在创造模式轻易获取!有两种方式实现:

  • 将物品添加到已存在的物品组
  • 创建你自己的物品组并添加物品

不论是哪种,添加到任何物品组的物品都可以在创造模式物品栏中搜索到。

添加到已存在的物品组

首先,我们需要决定要添加到哪个物品组。那我怎么知道 MC 有哪几个物品组呢?我们可以通过查看源码来确定。双击 shift 唤出搜索面板,搜索 ItemGroups 类(搜索位置为所有位置,以后将不再赘述):
image-20250727104440097

这第一个来源为 net.minecraft.item 的(查看源码都是基于来源 minecraft,以后将不再赘述)就是我们需要查看的源码了:
image-20250727104651148

对照表如下:

物品组
BUILDING_BLOCKS 建筑方块
COLORED_BLOCKS 染色方块
NATURAL 自然方块
FUNCTIONAL 功能方块
REDSTONE 红石方块
HOTBAR 已保存的快捷栏
SEARCH 搜索物品
TOOLS 工具与实用物品
COMBAT 战斗用品
FOOD_AND_DRINK 食物与饮品
INGREDIENTS 原材料
SPAWN_EGGS 刷怪蛋
OPERATOR 管理员物品
INVENTORY 生存模式物品栏

原版总共 14 种物品组。其中,OPERATOR 在按键控制中的管理员用品标签页设置启用。

知道物品组之后,我们尝试将前面创建的紫色粉末和蓝色粉末加到 INGREDIENTS 原材料物品组中。

参考 Fabric 提供的 API(底层是 Mixin),我们使用 ItemGroupEvents.modifyEntriesEvent 为修改物品组创建事件处理器(ModItems.java):

1
2
3
4
5
6
7
8
9
10
11
12
// 添加物品到原有的物品组中
public static void registerToVanillaItemGroups() {
    ItemGroupEvents.modifyEntriesEvent(ItemGroups.INGREDIENTS).register(content -> {
        content.add(PURPLE_POWDER);
        content.add(BLUE_POWDER);
    });
    TutorialMod.LOGGER.info("fk-ItemGroups: items registered to vanilla item groups");
}

public static void registerItems() {
    registerToVanillaItemGroups();
}

别忘记在主类 TutorialMod.java 中,在 onInitialize()registerItems() 进行调用。效果如下:
image-20250727111725765

如果觉得默认添加到物品组的底部比较麻烦,也可以精细化地控制添加位置。比如,将蓝色粉末加在原材料物品组的木炭后面:

1
2
3
4
5
6
7
8
// 添加物品到原有的物品组中
public static void registerToVanillaItemGroups() {
    ItemGroupEvents.modifyEntriesEvent(ItemGroups.INGREDIENTS).register(content -> {
        content.add(PURPLE_POWDER);
        content.addAfter(Items.CHARCOAL, BLUE_POWDER); // 将蓝色粉末加在木炭之后
    });
    TutorialMod.LOGGER.info("fk-ItemGroups: items registered to vanilla item groups");
}

效果如下:
image-20250727112240461

创建自定义物品组

创建自定义的物品组会被放置在单独的标签页中,这是一般 Mod 都会做的操作。

写法一(使用 Fabric API)

java/com/frozen/tutorialmod/item/ 中,创建 ModItemGroups.java 类。

参考 Fabric 提供的 api,我们使用 FabricItemGroup.builder 方法来创建物品组的构建器,并调用 build 方法完成构建(ModItemGroups.java):

1
2
3
4
5
6
7
8
9
public class ModItemGroups {
    public static final ItemGroup TUTORIAL_GROUP = FabricItemGroup.builder()
            .icon(() -> new ItemStack(ModItems.BLUE_POWDER)) // 标签页图标
            .displayName(Text.translatable("itemGroup.tutorial-mod.tutorial_group")) // 必须显式指定显示名称,否则会报错
            .entries((context, entries) -> {
                entries.add(ModItems.BLUE_POWDER);
                entries.add(ModItems.PURPLE_POWDER);
            }).build();
}

紧接着,完成物品组的注册:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class ModItemGroups {
    public static final ItemGroup TUTORIAL_GROUP = FabricItemGroup.builder()
            .icon(() -> new ItemStack(ModItems.BLUE_POWDER)) // 标签页图标
            .displayName(Text.translatable("itemGroup.tutorial-mod.tutorial_group")) // 必须显式指定显示名称,否则会报错
            .entries((context, entries) -> {
                entries.add(ModItems.BLUE_POWDER);
                entries.add(ModItems.PURPLE_POWDER);
            }).build();

    public static void registerMyItemGroups() {
        TutorialMod.LOGGER.info("fk-ItemGroups: created new item group and registered items to it");
        Registry.register(Registries.ITEM_GROUP, new Identifier("tutorial-mod", "tutorial_group"), TUTORIAL_GROUP);
    }
}

别忘记在主类 TutorialMod.java 中对 registerMyItemGroups() 方法进行调用。

写法二(原生写法)

上面的方法一是 Fabric 提供的 API 实现的物品组注册,没什么好说的,参照官方写法就行。
值得注意的是,这种写法,Mod 的物品组默认位置为 TOP 且索引为 714 种物品组分为上下各 7 组,且索引均从 0 开始),或者说索引为 -1,是不能控制物品组的位置的。

那么原生写法如何写呢?其实,在 MC 源码的 ItemGroups.java 中就已经写了:
image-20250727170323242

第一个 register 方法主要是在

那么参照原生写法,我们再来写一遍 ModItemGroups.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 写法二:使用Vanilla API 写法
public static final RegistryKey<ItemGroup> TUTORIAL_GROUP = register("tutorial_group");

private static RegistryKey<ItemGroup> register(String id) {
    return RegistryKey.of(RegistryKeys.ITEM_GROUP, new Identifier(TutorialMod.MOD_ID, id));
}
public static void registerMyItemGroups() {
    Registry.register(Registries.ITEM_GROUP,
            TUTORIAL_GROUP,
            ItemGroup.create(ItemGroup.Row.TOP, 7)
                    .displayName(Text.translatable("itemGroup.tutorial-mod.tutorial_group"))
                    .icon(() -> new ItemStack(ModItems.BLUE_POWDER))
                    .entries((context, entries) -> {
                        entries.add(ModItems.BLUE_POWDER);
                        entries.add(ModItems.PURPLE_POWDER);
                    }).build());
}

好像也挺不错?但是似乎又稍微麻烦了一点,需要写三个部分。

写法三(简化原生写法)

那么,可以尝试将上面的原生写法进行简化:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 写法三: 简化Vanilla API 写法
public static final ItemGroup TUTORIAL_GROUP = Registry.register(
        Registries.ITEM_GROUP,
        new Identifier("tutorial-mod", "tutorial_group"),
        ItemGroup.create(ItemGroup.Row.TOP, 7) // 可选位置参数
                .icon(() -> new ItemStack(ModItems.BLUE_POWDER))
                .displayName(Text.translatable("itemGroup.tutorial-mod.tutorial_group"))
                .entries((context, entries) -> {
                    entries.add(ModItems.BLUE_POWDER);
                    entries.add(ModItems.PURPLE_POWDER);
                })
                .build()
);

public static void registerMyItemGroups() {
    TutorialMod.LOGGER.info("fk-ItemGroups: registered new item group using vanilla builder");
}

这种写法将方法二的构造、注册、返回链式的串在一起,声明即注册。

三种写法的优劣

其实优劣谈不上,这主要看构建 Mod 的目的是什么,是快速得到一个简单的 mod?还是它长期更新维护且可能涉及多人协作?我的考虑基于后者,会更推荐第二种写法:原生写法加上显式声明 RegistryKey。这使得它更容易维护,并且显式声明 RegistryKey 也支持数据生成,风格标准。参考表如下:

写法 优点 缺点 适用场景
写法一(Fabric API) 简洁;API 现代;链式风格 不支持数据生成;不原生;依赖 Fabric API 快速开发、小型项目
写法二(Vanilla + RegistryKey) 支持数据生成;结构清晰;可扩展性强 稍微繁琐一点 中大型项目、多人协作、需要数据生成
写法三(简化 Vanilla) 语法最短;直观;快速上手 不适配数据生成;不易重用 学习阶段、测试用途

PS:以后就不提版本控制和运行测试了,大家自己操作就行。