第一个物品 1.20 Fabric 长线教程计划
本篇教程的视频
本篇教程的源代码
GitHub地址:TutorialMod-Item-1.20.1
前述
视频教程里稍微提了一下,本系列教程为长线教程计划
教程模式和1.21的一样,我将从源代码的层面来讲模组开发
这里的话,顺便浅谈一下Fabric和Forge及NeoForge区别
它们两个的话,你就只能用它们的那个注册系统,而不能像Fabric那样直接用原版的注册系统
不知道大家有没有像我一样去尝试过,如果你按照原版的方法去注册物品、方块等东西,
在游戏加载的时候,会直接抛出类似于注册表已冻结这样的信息,游戏崩溃
所以,我猜测(对,只是猜测,因为没去完全了解过他们具体的工作逻辑),Forge和NeoForge在加载的时候,会先加载Minecraft的主程序,然后再加载模组
但Fabric其实在它自己的Wiki上也说了,Fabric是完全通过Mixin来工作的,
它本身也就是个Mixin,我们的模组也是通过它直接注入游戏中的
所以Fabric很轻,加载速度比那两个快得多,但看起来没那么稳定
以前确实是有大型模组在Forge开发,而轻量级的模组在Fabric开发这样的说法
不过现在,其实已经差不多了,你在哪个端开发都没关系。
只是,Forge和NeoForge有更多的轮子(API),而Fabric的话,
相对欠缺一点,但也可以自己造
而且在Fabric,Mixin也会用得多一点,不过,Mixin使用前提是你对Minecraft的源码有一定了解,
否则很容易把正常的游戏给改崩
本篇教程目标
- 理解物品注册
- 理解模组开发之路的第二块绊脚石——Identifier类
- 理解物品的基本属性
- 编写物品模型的json文件(贴图就自己学吧)
查看源代码
那么现在,我们正式开始讲解第一个物品
假设说,你现在什么都不会,不知道物品该怎么添加,然后其他的教程也没看过
那么,很简单,看源代码
我们利用之前讲过的IDEA的随处搜索,快捷键double shift,搜索item
item是物品的英文,玩Minecraft的应该都知道吧
注意,这里搜索的范围不只是我们的项目文件,我们要将范围拓展到所有位置
所有位置的话,它就包括了外部库里的那些东西
而后,会冒出来一堆类
但要注意分辨,我们要找的是Minecraft的源代码,不是其他的库
它是net.minecraft.item下的类,注意看各个类后面的包的名字

不过,我们要找的其实并不是Item这个类,而要找Items
这两个类都存在,也都是Minecraft的类,随着我们教程的开展,你还会发现另外的,
例如Block和Blocks、ItemGroup和ItemGroups等等
感兴趣的同学可以先自己去研究一下,我这里的话也简单说明一下
一般来说,带s的是Minecraft的注册类,而不带s的是定义各个类的基本属性
比如Item类,你可以在这个类中发现物品的一些基本属性,像默认最大堆叠数量64、默认最大使用次数32、稀有度等等
还有各种各样的方法,到时候我们讲解自定义物品类的时候可以重写它们,来实现自己的一些逻辑
而Items类,这是物品的注册类,这里注册了所有的Minecraft物品,你会发现清一色的public static final字段,以及一系列的register方法
源代码中的注册方法
那么register翻译翻译,就是注册的意思
所以现在,我们就来看看源代码中的注册方法
不过先得挑一个,因为在这里不只有单纯的物品,还有像方块物品这样的,这个东西我们在后面讲方块的时候会讲到
还有像一些物品实例化的并不是Item类,而是实例化它们自己的类,像我们之后会讲到的盔甲、武器等
所以这里的话,我们挑一个简单的,几乎没什么特别的属性物品,DIAMOND,也就是钻石
搜索的快捷键是CTRL + F,搜索diamond,可以再加一个空格,就能定位到钻石这个物品了
1 | public static final Item DIAMOND = register("diamond", new Item(new Item.Settings())); |
register里面的东西我们先不用看,先看这个方法本身
1 | public static Item register(String id, Item item) { |
DIAMOND使用的方法是第一个register,同名的重载方法有一堆,也层层叠叠调用了一堆方法
最终调用了最后一个register方法,也就是我们真正要看的那个方法
1 | Registry.register(Registries.ITEM, key, item); |
这里的Registry是Minecraft的注册类,在这个类的注释中,也有提示,告诉我们物品该怎么注册
不过,我们先不管他,还是回过头来看这个方法
这个方法接受三个参数,第一个是注册表,第二个是注册键,第三个是注册的物品
Registries是Minecraft的注册表类,它里面定义了所有Minecraft的注册表,
之后我们注册方块、物品栏都会用到其中的字段
至于第二个注册键,我们可以根据上面三个方法进行整合
1 | Registry.register(Registries.ITEM, RegistryKey.of(Registries.ITEM.getKey(), new Identifier(id)), item); |
这里传入的id是String类型的,然后,实例化Identifier类,传入id
Identifier类
那么在这里,我们就要重点讲讲Identifier类了
这是定义Minecraft中所有资源文件路径的类,我们可以看看它的注释
1 | An identifier used to identify things. |
它这里也说了,这个类也被称为资源位置、命名空间ID、位置或者ID
1 | Identifiers are formatted as <namespace>:<path>. |
它的格式也在注释这有说明,就是<命名空间>:<路径>,命名空间缺省的话,默认为minecraft
在之后的教程中,我提到的所有关于命名空间的事情,都是从这个类里来的
而我们模组的命名空间,则是我们的modid
1 | The namespace and path must contain only |
敲黑板,划重点了
为什么说命名空间能成为模组开发之路上的第二块绊脚石(第一块是Gradle)?
因为不少人在定义物品、方块的名字,其他的一些命名空间等东西的时候,会写非法字符
这里的注释说的很清楚了,命名空间和路径只能包含ASCII小写字母[a-z]、ASCII数字[0-9]、下划线[_]、点[.]和短横线[-]
而路径还可以包含标准路径分隔符/
其余的全是非法字符,包括大写字母
所有,定义命名空间及路径的时候看清楚,不要写非法字符,再写我可要嘲笑你了
物品的基本属性
好,看完Identifier类,我们再回过头来看物品具体注册时候实例化的方法
1 | public static final Item DIAMOND = register("diamond", new Item(new Item.Settings())); |
还是DIAMOND,第一个参数是它的名字,后面实例化了一个Item类,并传入了一个Item.Settings()
在这个Settings()中就是物品的基本属性的类,嵌套在Item类中
1 | public static class Settings { |
可以看到,默认定义好的最大堆叠数量64,稀有度为普通,另外的属性可以通过下面的方法加入,也可以更改默认的这两个
不过,具体的我们到后面再说,因为现在我们只需要注册一个最简单的物品即可
注册第一个物品
好了,前面铺垫工作基本完成了
你看,要讲的东西其实很多很多,要换种教程模式的话,我就直接从这里开始讲了
创建注册物品类
现在我们来真正开始写第一个物品,和原版一样,我们也先创建一个用于注册物品的类ModItems
1 | public class ModItems { |
然后我们把原版的注册方法搬过来
1 | public static Item register(String id, Item item) { |
当然,这里的命名空间改成了我们模组的MOD ID,不然到后面找你资源文件的时候就跑到Minecraft命名空间下去了
优化注册方法
不过,上面我也提了一下整合方法,这四个方法其实是可以整合为一个方法的
1 | public static Item registerItems(String id, Item item) { |
什么?还能优化?还有高手?
是的,Registry.register其实还有一个简化的重载方法,我们可以先看一下
1 | static <V, T extends V> T register(Registry<V> registry, Identifier id, T entry) { |
这里的第二个是我们目前使用的方法,而第一个调用的也是第二个方法,唯一的区别就是第一个方法的第二个参数为Identifier类型,而第二个方法的第二个参数为RegistryKey类型的
我们可以直接利用第一个方法来简化我们的代码,顺带一提,它也是我们在后面讲方块时,原版采用的注册方法
1 | public static Item registerItem(String id, Item item) { |
注册物品
好,现在我们就可以开始注册我们的第一个物品了
和DIAMOND的写法一样,写一个最简单的物品
1 | public static final Item ICE_ETHER = registerItem("ice_ether", new Item(new Item.Settings())); |
还是我们的老朋友ICE ETHER
那么,我们举一反三,再写一点
1 | public static final Item RAW_ICE_ETHER = registerItem("raw_ice_ether", new Item(new Item.Settings())); |
我们再加入RAW ICE ETHER和CARDBOARD,
其中CARDBOARD我加了一个路径material,我们在后面写模型文件的时候会看到它和另外两个的区别
初始化注册方法
我们的物品已经写好了,但还没用,你到游戏中还是找不到添加的物品
因为我们这个类没有初始化
这里我们再写一个无返回值的空方法
1 | public static void registerItems() { |
随后,到模组主类的onInitialize方法中去调用它
1 |
|
onInitialize方法会在模组加载的时候调用,而此时我们的方法也会被调用
那么为什么加了这个方法就可以完成初始化了呢?
因为你会发现,我们写的这三个物品注册,都是static final修饰的,这个类被调用了,那么它们就必须初始化
初始化的结果呢,就是调用注册方法,从而让物品注册到游戏中
资源文件
物品注册到这里就好了,不过现在进入游戏,
虽然可以获得你的物品,但你会发现它是一个大大的黑紫块,名字还老长一串
因为现在,我们的物品没有模型,也没有材质
原版的资源文件都可以在外部库中找到,在其assets文件夹下,另外一边的data则为数据文件
那么我们的模组也有一个assets文件夹,这里是存放我们模组的资源文件
语言文件
这里我们先来写语言文件,在assets/<modid>下新建一个lang文件夹,然后新建一个en_us.json文件
注意文件路径,Minecraft的文件读取是有严格的规范的,你不能自己乱写
这里的en_us.json文件是英文语言文件,这是默认的语言文件,在其他语言文件缺省时,会默认使用它
当然你也可以新建一个zh_cn.json文件,用于简体中文的语言文件
1 | { |
这里我们定义了三个物品的名称,其中item.tutorial-mod.ice_ether是实际注册在Minecraft中的名字
它是由你注册的类型.你的MOD ID.你的物品名字组成的
也就是你不写语言文件则会显示这么长一串,而后面的值则会翻译这一串东西
中文的语言文件同样如此,这里就不再赘述
物品模型
语言文件写好了,现在我们再来看模型文件
在assets/<modid>/models/item下新建一个ice_ether.json文件,用于存放ICE ETHER的模型文件
1 | { |
这个模型继承自minecraft:item/generated,也就是原版物品的模型
它是原版最基本的物品模型,也就是在你贴图基础上加厚一层
其中layer0是贴图,我们这里贴图路径是tutorial-mod:item/ice_ether
那么RAW ICE ETHER的模型文件也差不多
1 | { |
但CARDBOARD呢?前面我们多加了一个路径
所以,在这里我们就要在assets/<modid>/models/item/material下新建一个cardboard.json文件
注意文件路径多了一个material
1 | { |
我在这里加这么一个路径的原因呢很简单,一方面让大家再感受一下Identifier的用法,
另一方面呢,也是方便管理
虽然我们现在的东西不多,但你自己的开发过程中,或许会有成百上千个东西要写,不给它分类的话,如果要修改某些文件,找就得找半天
方舟家具模组全面重写之后,也就采用了这种方法,毕竟成百上千个模型,后面要改的话,光是找就得要命了
不过,这里贴图路径的material可以不写,为了方便管理也可以写
物品材质
物品材质的绘制的话,Photoshop就可以画,也可以拿专业的像素画软件来画,如Aseprite
我们要将贴图文件放在assets/<modid>/textures/item下
另外,如果上面cardboard的贴图文件路径不加material的话,则和另外两个放一起就行
如果写上,则得将贴图放在assets/<modid>/textures/item/material下
启动游戏测试
那么现在,我们就可以启动我们的游戏去测试一下
由于我们现在没有将物品加入到创造模式物品栏中,只能通过give命令来获得物品
give输入我们的物品时,命名空间改为我们模组的modid,而不是minecraft
另外,以后当你找不到你新加的物品时,也可以用这种方法来检测一下
成功注册的物品都可以用give命令来获得











