这是适用于Minecraft Java版1.21.4的Fabric模组开发系列教程专栏第二章——创建物品。想要阅读其他内容,请查看或订阅上面的专栏。
想要在Minecraft中添加自己的物品,通常需要完成下面的步骤:
- 创建物品类
- 在游戏注册表中注册物品
- 为物品创建模型文件
- 为物品创建模型描述文件
- 在翻译文件中添加物品翻译字段
除此之外,还可以对物品进行其他设置,在下文也会提到。
写在前面
从本章开始,将正式开始学习Fabric模组开发相关知识。本节将展示部分需要使用到的链接和一些需要提前告知的信息。
链接
- 开发者指南 | Fabric文档
最新版的Fabric开发文档。 - Fabric 模组教程 [Fabric Wiki]
适用于旧版游戏的Fabric开发文档。 - fabric-docs/reference/latest/ at main · FabricMC/fabric-docs
Fabric官方模组代码示例。
说明
- 本系列教程按照最新版Fabric开发文档的节奏编写,部分内容相同,但是要比官方文档的讲解更加详细,同时规避了官方文档中的错误。
- 即使是最新版的Fabric开发文档,部分文章的中文版本依然缺乏更新,部分代码无法显示。代码写法仍需参考github上的示例代码;
- 请勿直接在网页上保存游戏纹理文件,因为网页上的图片并不符合纹理文件的大小要求,下载纹理文件请参考最新版的Fabric开发文档或联系作者;
- 使用AI解决问题时,不能完全相信AI提供的代码。Fabric和Minecraft API更新速度很快,AI给出的部分写法可能已经过时;
- 在网络上搜索问题的解决方案时,要注意模组加载器和游戏版本是否与本文相一致,同一功能不同版本的代码写法有很大出入,旧版本代码写法和旧版本的项目结构在新版本环境中不生效。
创建物品类
在本章中,我们以狗粑粑(dog_poo) 为例,详细讲解创建这个物品的过程。
1.首先,在main模块的com.example.test
包中创建item
目录,用于存放所有物品类。右键点击com.example.test
目录,选择“新建→软件包”,输入“item”然后按下回车创建目录;
2.右键item
目录,选择“新建→Java类”,输入“DogPoo”然后按下回车创建Java类;
public class DogPoo {}
在本章和接下来的章节中,所有代码中的package
和import
声明都将省略。
物品类Item
Item
是Minecraft原生API中的核心类之一,其对象指的是所有玩家和其他实体可以使用的物品。通常,对于没有自定义行为的物品,继承Item
类后可以直接调用其构造方法;对于需要有自定义行为的物品,此时要继承Item
的子类,如ArmorItem
等,这可以给对应的物品类提供更多的功能。
3.使DogPoo
继承Item
类,并在其中调用Item
类的构造方法;
public class DogPoo extends Item {
public DogPoo(Settings settings) {
super(settings);//调用父类的构造方法
}
}
这样,一个物品类就创建完成了。
在游戏注册表中注册物品
现在,我们可以在游戏物品注册表中注册自己的物品。不过在开始前,需要了解一些概念。
注册表接口Registry
注册表接口Registry
用于注册各种游戏内组件。游戏内几乎所有内容——方块、物品和实体——都会在注册表中注册。注册表会分配一个唯一的标识符给每一个注册表项。因此,注册表在游戏中起着非常重要的作用,某些应该注册的对象但并没有在注册表中注册通常会导致错误甚至崩溃。
使用注册表接口Registry
中的Registry.register()
方法将物品注册到注册表;
static <V, T extends V> T register(Registry<V> registry, RegistryKey<V> key, T entry) {
((MutableRegistry)registry).add(key, (V)entry, RegistryEntryInfo.DEFAULT);
return entry;
}
其中的参数为:
Registry<V> registry
:目标注册表类型。一般传递Registries.ITEM
(物品)或Registries.Block
(方块)等;RegistryKey<V> key
:唯一标识符。此处传递一个由Identifier
类中静态方法生成的注册键;T entry
:任意类型对象,一般指的是要注册物品的实例。
按照官方相关文档所述,在创建普通物品时,不会直接调用这个方法。因此,这个方法即将被封装。
标识符类Identifier
Identifier
类用来为物品添加标识符,也被称为命名空间ID。标识符使用命名空间和路径的组合来标识物品。标识符的格式为<namespace>:<path>
,如果省略命名空间和冒号,则命名空间默认为“minecraft”。
标识符有着非常严格的格式规范,除了创建的标识符不能和已经存在的标识符重复以外,其中的字符:
- 只能包含ASCII小写字母([a-z]);
- 只能包含ASCII数字([0-9]);
- 仅接受的几个特殊字符:
_
(下划线),.
(点),-
(连字符); - 不能使用大写字母;
通常,可以使用Identifier
类中的静态方法of()
来创建一个标识符;
public static Identifier of(String namespace, String path) {
return ofValidated(namespace, path);
}
其中的两个参数分别为“命名空间(namespce)”和“路径(path)”,类型均为字符串。当传入无效值时,将抛出InvalidIdentifierException
。
4.在开始封装Registry.register()
方法前,需要创建一个用于存放静态常量的类,方便后续开发。首先在com.example.test
目录中创建一个名为utils
软件包,作为工具类的存放位置。在utils
包中创建FabricDocsReference
类(可以使用其他自己喜欢的名字);
public class FabricDocsReference {
public final static String MOD_ID ="test";
}
在其中声明当前模组Id为静态常量,在将来会有更多静态内容添加到这个类中。
5.按照官方文档的写法,需要封装Registry.register()
方法。在utils
包中创建ModItems
类,在类中创建register()
方法,封装Registry.register()
;
public static Item register(String name, Function<Item.Settings, Item> itemFactory, Item.Settings settings) {
// Create the item key.
RegistryKey<Item> itemKey = RegistryKey.of(RegistryKeys.ITEM, Identifier.of(FabricDocsReference.MOD_ID, name));
// Create the item instance.
Item item = itemFactory.apply(settings.registryKey(itemKey));
// Register the item.
Registry.register(Registries.ITEM, itemKey, item);
return item;
}
代码摘自官方文档。其中的参数如下:
String name
:物品名,即标识符中的路径;Function<Item.Settings, Item> itemFactory
:物品对象与物品默认配置对象的函数式;Item.Settings settings
:配置对象。
首先使用模组Id和name
(物品路径)创建物品注册键RegistyKey
,然后创建默认配置对象Item.settings
,接着确定注册物品的类型是物品Registries.ITEM
或其他类型,最后使用注册键RegistyKey
和默认配置对象Item.settings
在游戏注册表中注册物品;
在这个项目中,所有Item
对象的命名空间都是FabricDocReference.MOD_ID
,即模组Id。
物品配置内部类Item.Settings
Item.Settings
类在Item
类内部,它可以为需要注册的物品配置诸多属性和行为,例如物品堆叠的最大计数、物品是否可以食用和物品的最大伤害等,是创建物品过程中必不可少的参数之一。
6.在入口点类Test
中注册物品dog_poo
;
入口点
通常,一个模组通常包含至少一个入口点类,类名与模组Id一致。在main模块中,这个类实现了 ModInitializer
接口。此外,入口点类中必须实现onInitialize()
方法。当游戏启动时,onInitialize()
方法将被执行,同时在类中的静态常量也将被初始化。
打开Test
类,在onInitialize()
方法前使用封装后的register()
方法声明并初始化物品dog_poo
为静态常量;
public static final Item DOG_POO = ModItems.register("dog_poo"
, DogPoo::new
, new Item.Settings().maxCount(63));
Item.Settings
类对象提供了maxCount()
方法以设置物品的最大堆叠数量,默认值为64;
现在,将物品在游戏注册表中注册的代码逻辑已经完成。
为自定义物品创建纹理、模型文件和模型描述文件
现在,可以开始准备物品的纹理与其对应的模型JSON文件了。
创建纹理
我们使用这个图片作为dog_poo
的纹理;
纹理图片也有一定的格式要求,包括:
- 大小必须为16x16或32x32;
- 文件格式必须为.png;
- 文件名必须与物品路径一致。
7.在项目中的assets.test
目录中创建textures
目录,用于存放项目中的所有纹理文件,然后在其中创建item
目录,用于存放物品的纹理文件,最后将纹理图片移动到items
目录中。文件名与物品路径一致。
创建模型描述文件
模型描述文件也称之为模型映射文件,是一个在项目中指向物品模型文件位置与类型的JSON文件。自1.21.4版本后,自定义物品的过程新增了创建此文件的操作;
8.在assets.test
目录中创建items
目录,用于存放项目中所有的模型描述文件,然后在items
目录中创建dog_poo.json
;
以dog_poo
为例,模型描述文件的写法如下:
{
"model":{
"type": "minecraft:model",
"model": "test:item/dog_poo"
}
}
其中:
model
:代表对模型的引用属性,其中包含:type
:模型的类型。 对于大多数物品,应该为minecraft:model
;model
:模型的标识符。其中test
为模组Id,item/dog_poo
指向模型文件的位置。
创建模型文件
模型文件用于指向物品纹理图的所在位置,与模型映射文件类似,文件类型为JSON。
9.在assets.test
目录中创建models
目录,用于存放项目中所有的模型文件,然后再创建item目录,用于存放物品的模型文件,最后在item
目录中创建dog_poo.json
;
以dog_poo
为例,模型文件的写法如下;
{
"parent": "minecraft:item/generated",
"textures": {
"layer0": "test:item/dog_poo"
}
}
其中:
parent
:模型要继承的父模型。 在这个例子中,是item/generated
模型;textures
:指定模型对应物品的纹理文件位置。layer0
键是模型使用的纹理。
现在,物品的纹理、模型文件和模型描述文件已经创建完毕。
创建语言文件
为了防止自定义物品在游戏中直接显示标识符,如test:dog_poo
,可以创建语言文件,根据不同的语言为标识符提供对应的文本,这就是对应一般程序开发项目中的i18n(internationalization) 功能,即国际化;
当游戏的语言为中文时,模组项目中的语言文件名应当为zh-cn.json
;如果为英文,则文件名为en-us.json
。
10.在assets.test
目录中创建lang
目录,用于存放语言文件。在lang
目录中创建zh-cn.json
文件;
在语言文件中,添加一对键值;
{
"item.test.dog_poo":"狗粑粑"
}
将test:dog_poo
翻译为“狗粑粑”。其中item
是物品类型,test.dog_poo
是物品的标识符(此时使用.
而不是:
隔开),物品类型与标识符两者之间使用.
分割;
至此,一个基本的自定义物品已经创建完成。在游戏中,可以使用/give
命令获得该物品。
将自定义物品添加到物品组
在创造模式下,为了避免始终使用命令获取物品,我们可以将自定义物品加入到指定物品组中。
物品组事件类ItemGroupEvents
ItemGroupEvents
一个事件系统,专门用于为即将修改的物品组创建修改事件。一般,使用ItemGroupEvents.modifyEntiesEvent()
方法来确定物品组;
public static Event<ModifyEntries> modifyEntriesEvent(RegistryKey<ItemGroup> registryKey) {
return ItemGroupEventsImpl.getOrCreateModifyEntriesEvent(registryKey);
}
方法需要传递一个物品组注册键。例如ItemGroups.INGREDIENTS
,指的是创造模式中“原材料”物品组;
ItemGroupEvents.modifyEntiesEvent()
方法返回一个Event
对象,即物品组修改事件对象。还需要调用Event
对象的register()
方法,为该事件注册监听器;
public abstract void register(T listener);
方法传递一个事件监听器。此处传递的监听器类型为ItemGroupEvents.ModifyEntries
接口。
物品修改内部函数式接口ItemGroupEvents.ModifyEntries
这个接口在ItemGroupEvents
类内部声明,是注册物品组修改事件监听器时所使用的事件回调接口。用于修改特定物品组中的物品,其中只有一个modifyEntries()
方法。接口外部带有@FunctionalInterface
注解,这代表调用此接口时可以简写为Lambda表达式;
public final class ItemGroupEvents {
//...
@FunctionalInterface
public interface ModifyEntries {
void modifyEntries(FabricItemGroupEntries entries);
}
}
modifyEntries()
方法中提供了当前物品组的物品对象FabricItemGroupEntries
。
Fabric物品组内容类FabricItemGroupEntries
FabricItemGroupEntries
是 Fabric API 提供的一个关键类,它作为物品组内容修改的接口,允许ItemGroupEvents
中的事件修改物品组的物品;
FabricItemGroupEntries
类实现了ItemGroup.Entries
接口,并重写了其内部方法add()
;
default void add(ItemConvertible item) {
this.add(new ItemStack(item), ItemGroup.StackVisibility.PARENT_AND_SEARCH_TABS);
}
其中传递的参数是ItemConvertible
对象。此处可以直接理解为传递的是自定义物品的实例。
11.在入口点类中的onInitialize()
方法中,将dog_poo
添加到“原材料”物品组中;
ItemGroupEvents.modifyEntriesEvent(ItemGroups.INGREDIENTS)
.register((itemGroup) -> itemGroup.add(DOG_POO));
此处使用了Lambda表达式,也可以使用匿名内部接口new ItemGroupEvents.ModifyEntries(){...}
;
ItemGroupEvents.modifyEntriesEvent(ItemGroups.INGREDIENTS)
.register(new ItemGroupEvents.ModifyEntries() {
@Override
public void modifyEntries(FabricItemGroupEntries entries) {
entries.add(DOG_POO);
}
});
如果是初学者,这个写法更易于理解。
12.打开游戏,创建一个新世界,模式为创造模式。进入世界后,按E打开物品栏,在“原材料”物品组最后的位置,可以看到新加入的物品。
到此为止,一个基本的物品就创建完成了。对于一个没有复杂的自定义行为和属性的物品,可以采用以上的步骤来创建;对于其他带有复杂行为的物品,还请参考下文与后面的数据组件等章节中的相关内容来完成创建。
为物品添加合成配方
在此小节,我们会为“狗粑粑”添加一套合成配方。默认使用工作台制作,定义合成配方为:
如上图,三个“橡木栅栏”居中排列。
一般,合成配方的定义需要创建一个JSON文件。而根据官方文档所述,可以使用“配方 JSON 生成器”网站来生成这个JSON文件,请访问:
Crafting
1.首先,在右侧找到“橡木栅栏”,然后拖动到左侧工作台九宫格中;
2.完成后,找到左侧的“options”部分;
其中
- Minecraft Version:Minecraft版本,选择“Java 1.21.4”;
- shapeless? :是否不使用形状限制?大概的意思是是否完全按照摆放的形状来生成对应的物品。如果勾选则代表不需要对配方添加形状限制,换言之,三个“橡木栅栏”以任意形状在工作台中摆放都可以合成“狗粑粑”,即使是2x2的玩家工作台。此处不勾选。
- 2x2 Grid:确定配方是否为2x2网格的配方。此处不勾选。
- Exactly where placed?:是否限制物品摆放的位置?如果勾选,则必须将三个“橡木栅栏”放在中间的一列才能制作出“狗粑粑”;反之如果不勾选,则代表在任意一列摆放三个“橡木栅栏”都能制作出“狗粑粑”。此处不勾选。
- Output Recipe:输出文件的文件名,填写“dog_poo”;
- Group:物品在工作台中所属物品组,暂时可以为空。
3.此时下方已经生成JSON,点击“Download JSON file”,下载JSON文件。
4.在main模块下,创建data/test/recipe
目录,将下载好的JSON文件复制进去。
5.打开dog_poo.json
,在配置对象result
中添加一条数据,键为id
,值为物品的标识符。代表配方可以制作出的物品;
...
"result": {
"id": "test:dog_poo"
}
值得注意的是,Crafting官方文档(https://github.com/skylinerw/guides/blob/master/java/recipes.md)的result
配置对象中,键写成了item
。这是旧版本的写法,在1.21.4版本中,必须改为id
。
6.由于配方JSON属于数据生成式文件,需要启用项目的数据生成设置。找到build.gradle
配置文件,在文件结尾添加数据生成配置;
fabricApi {
configureDataGeneration() {
client = true
}
}
7.打开“构建”窗格,点击“执行Gradle任务”。执行/gradlew runDatagen
命令,可以看到main模块中生成了generated
目录;
8.将data
目录移动到generated
目录中;
9.打开游戏测试,配方已经生效。
自定义物品提示
创建完物品后,可以给物品添加物品提示。当鼠标移动到物品上时,会显示物品的名称和物品提示,如下图。
通常,我们在继承了Item
类的物品类中重写appendTooltip()
方法,来为物品添加工具提示;
public void appendTooltip(ItemStack stack, TooltipContext context, List<Text> tooltip, TooltipType type) {}
这个方法默认为空。添加物品提示要使用List<Text> tooltip
参数的add()
方法,将文字加入到工具提示列表中。add()
方法传递一个Text
对象。
可变文本类MutableText
MutableText
是Text
的子类,用于处理游戏内的文本渲染与本地化。使用其静态方法literal()
可以直接将普通字符串字面量转换为可变文本并添加到游戏当中;
static MutableText literal(String string) {
return MutableText.of(PlainTextContent.of(string));
}
方法传递一个字符串,即需要转换的字符串。此外,还可以使用formatted()
方法设置文字颜色;
public MutableText formatted(Formatting formatting) {
this.setStyle(this.getStyle().withFormatting(formatting));
return this;
}
方法传递一个Formatting
枚举类中的枚举常量,例如Formatting.GOLD
指的是金色,Formatting.BLUE
指的是蓝色。
打开DogPoo.java
,重写appendTooltip()
方法;
public void appendTooltip(ItemStack stack, TooltipContext context, List<Text> tooltip, TooltipType type) {
tooltip.add(Text.literal("这可是狗粑粑啊!").formatted(Formatting.GOLD));
}
使用literal()
方法将字符串字面量转换为可变文本,然后使用formatted()
方法设置文字颜色为金色。打开游戏,可以在物品下面看到金色的工具提示文本。
配置物品可堆肥或作燃料
还可以将“狗粑粑”作为堆肥物品或燃料,这与“狗粑粑”在现实世界对应的功能相符合。
物品堆肥列表及其概率注册接口CompostingChanceRegistry
CompostingChanceRegistry
是Fabric API提供的工具类,用于注册可堆肥物品及其堆肥成功率,可以将自定义物品添加到堆肥桶的可堆肥物品列表中,使物品能够像原版材料一样被转化为骨粉。需要使用接口CompostingChanceRegistry
的实例CompostingChanceRegistry.INSTANCE
的add()
方法,将物品添加到可堆肥列表中并设置其成功概率。
public void add(ItemConvertible item, Float value) {
ComposterBlock.ITEM_TO_LEVEL_INCREASE_CHANCE.put(item.asItem(), value);
}
CompostingChanceRegistry.INSTANCE
由接口CompostingChanceRegistry
的实现类CompostingChanceRegistryImpl
中的构造方法实例化。其中的add()
方法参数有:
ItemConvertible item
:要加入可堆肥列表的物品实例;Float value
:堆肥成功的概率,取值范围在0.0-1.0之间。例如,0.3代表堆肥成功率为30%。
在入口点类中的onInitialize
方法中,为物品添加可堆肥功能;
@Override
public void onInitialize() {
//...
CompostingChanceRegistry.INSTANCE.add(DOG_POO,0.5f);
//...
}
设置其成功概率为50%。完成后,启动游戏测试功能已经实现。
燃料注册事件接口FuelRegistryEvents
FuelRegistryEvents
是一个事件系统,用于创建一个修改或扩展燃料注册表的事件。通常,使用接口中的BUILD
属性创建一个事件。按照源代码Javadoc所述,该事件在游戏燃料注册表创建之后和EXCLUSION
事件执行之前调用。EXCLUSION
事件用于排除燃料注册表中的特定物品。事件BUILD的类型为FuelRegistryEvents.BuildCallback
。
处理燃料注册事件BUILD
的内部函数式接口FuelRegistryEvents.BuildCallback
这个接口在FuelRegistryEvents
内部声明,用于处理BUILD
事件。接口外部带有@FunctionalInterface
注解,这代表调用此接口时可以简写为Lambda表达式,接口内部中只有一个方法build()
;
public interface FuelRegistryEvents {
//...
@FunctionalInterface
interface BuildCallback {
void build(FuelRegistry.Builder builder, Context context);
}
}
其中提供了两个参数:
FuelRegistry.Builder builder
:燃料注册表项构造器,用于在燃料注册表中创建新的注册项;Context context
:BUILD
事件上下文。
燃料注册表构造器内部类FuelRegistry.Builder
FuelRegistry.Builder
声明在FuelRegistry
类内部,用于构造自定义燃料物品的注册项并将其在燃料注册表中注册。通常,调用其中的add()
方法来完成燃料物品的注册;
public Builder add(TagKey<Item> tag, int value) {...}
方法体已省略,其中的两个参数为:
TagKey<Item> tag
:自定义物品的实例对象;int value
:燃烧时长,单位:游戏刻,1秒=20游戏刻。例如,30秒为600(30 * 20)游戏刻。
在入口点类的onInitialize
方法中,将物品dog_poo
添加到燃料注册表中,配置燃烧时长为30秒;
@Override
public void onInitialize() {
//...
FuelRegistryEvents.BUILD.register((builder, context) -> builder.add(DOG_POO, 30 * 20));
//...
}
此处使用了Lambda表达式,也可以使用匿名内部接口new FuelRegistryEvents.BuildCallback(){...}
;
FuelRegistryEvents.BUILD.register(new FuelRegistryEvents.BuildCallback() {
@Override
public void build(FuelRegistry.Builder builder, FuelRegistryEvents.Context context) {
builder.add(DOG_POO,30 * 20);
}
});
打开游戏,功能已经实现。
本章小结
本章阐述了创建物品的基本步骤与其他相关功能的写法。若想添加更多类型的物品,如食物、武器和盔甲等,请参考后续章节。感谢各位的阅读,有兴趣可以订阅此专栏!