本篇教程的视频

本篇教程的源代码

GitHub地址:TutorialMod-1.21.1-NeoForge-Item

注册物品

那么事不宜迟,我们来讲第一个物品

总体上来说,NeoForge的写法和Forge差不多,毕竟是本自同根生

注册

这里首先我们创建一个ModItems类,这个类用于物品的注册

1
2
3
public class ModItems {

}

然后,我们要创建一个延迟注册器,用于管理注册的物品

1
2
public static final DeferredRegister.Items ITEMS =
DeferredRegister.createItems(TutorialMod.MOD_ID);

这里的类型是DeferredRegister.Items

我们可以看到NeoForge已经对一些原本Forge中的内容进行封装了

后面使用DeferredRegister.createItems来创建这个延迟注册器

里面传入TutorialMod.MOD_ID,也就是我们模组的命名空间

接下来我们就可以注册物品了

1
2
public static final DeferredItem<Item> ICE_ETHER =
ITEMS.register("ice_ether", () -> new Item(new Item.Properties()));

这里我们注册了一个名为ICE_ETHER的物品,类型是DeferredItem<Item>

然后我们用上面写的注册器,使用register方法注册物品,第一个参数是物品的名称,第二个参数是一个Supplier,用于提供物品设置

这里我们使用new Item(new Item.Properties())来创建一个物品,Item.Properties是物品的属性,这里我们使用默认的属性

模组的案例呢,也同样是Fabric长线教程计划中的案例,我就不单独开其他的了
(PS:目前开行的长线教程计划都会共用案例)

ResourceLocation

这里我们提一下关于物品的名称,这个也是物品的注册名,那么这里我们就要引出一个类,ResourceLocation

这个是原版的类

FabricYarn映射下,它叫Identifier,这两个是同一个东西,都是用于表示资源位置的类

当然我们这里注册物品并没有用上什么ResourceLocation,但是在register方法中用了(当然这里的register是另一个重载方法)

1
2
3
4
5
public <I extends T> DeferredHolder<T, I> register(final String name, final Function<ResourceLocation, ? extends I> func) {
...
final ResourceLocation key = ResourceLocation.fromNamespaceAndPath(namespace, name);
...
}

所以我还是来提一嘴这个东西,我将其称之为模组开发道路上的第二块绊脚石

第一块呢,就是前面一期的Gradle构建,那玩意确实得用点魔法

那么这里,什么崩溃了,报错了,黑紫块了等等,往往是因为ResourceLocation的问题

这里我们看看这个类中的注释,虽然这是其中一个方法的注释,但对整个类都是适用的

ResourceLocation

哎?有人就要说了,为什么这一个方法的注释就适用于整个类了?

说实话,NeoForge在这里注释写得没有Fabric那样详细,甚至还比的Forge还要简略

FabricIdentifier类中类似的注释是写在开头的(感兴趣可以去翻一下)

而我对此的判断,一部分也是源自我已经有的开发经验,知道通其一,便可通其二怎么来的了吧

当然,对于第一次接触模组开发的同学来说,这个藏在里面的东西自然是不知道的

我们就看其中返回值的提示

1
2
the parsed resource location; 
otherwise null if there is a non [a-z0-9_.-] character in the decomposed namespace or a non [a-z0-9/._-] character in the decomposed path

翻译翻译,啥意思?字面意思是如果命名空间路径存在不是[a-z0-9_.-]之中的字符,那么就会返回null,继而会抛出异常

再翻译翻译,我们的命名空间路径
它只能包含a-z小写字母、0-9数字、/斜杠、.点、_下划线、-短横这些字符

值得注意的,理论上-短横可以使用在命名空间中,也就是modid,但NeoForgeForge中都不行,也不知道有什么东西限制了它们

斜杠是用来分级的,就像文件路径里面的斜杠,同一个意思,用在路径中

而不在这个范围内的,都会在运行时抛出ResourceLocationException异常(这里没写明,不过你试一下就有了)

所以什么大写字母中文字符空格等等都是非法字符,都会造成游戏崩溃,绝对不能使用

对于另外的四个特殊字符,一般下划线是代替空格的,斜杠用来分路径,另外两个不怎么用

举一反三

我们也可以来举一反三

1
2
3
4
public static final DeferredItem<Item> RAW_ICE_ETHER =
ITEMS.register("raw_ice_ether", () -> new Item(new Item.Properties()));
public static final DeferredItem<Item> CARDBOARD =
ITEMS.register("material/cardboard", () -> new Item(new Item.Properties()));

对于这里的CARDBOARD,我们使用了material/cardboard作为物品的名称,实际上我们是将它归到了material文件夹下,
在后面写物品模型的时候,你就会发现它于其他物品的区别

斜杠是常用来进行分类的,这是在文件层级上的分类

然而,在后面的数据生成中,确实存在一个问题,不过到时候我们再说

原版物品注册类

Item.Properties()只是最简单的物品属性,在Items类中我们还能看到许许多多的属性

1
public static final Item APPLE = registerItem("apple", new Item(new Item.Properties().food(Foods.APPLE)));

比如原版的苹果,它就是用food方法来设置食物属性的

物品的属性我们会在后面的教程中展开讲讲

Items类是原版的物品注册类,感兴趣的同学可以先研究起来了

注册事件

接下来我们还要写一个方法,用于初始化注册物品这个事件

1
2
3
public static void register(IEventBus eventBus){
ITEMS.register(eventBus);
}

而后,这个方法要在模组主类构造函数中调用

1
ModItems.register(modEventBus);

这个和Forge中是一样的

数据文件

现在我们已经完成了物品的注册,虽然没写创造模式物品栏,但可以通过give指令获取物品了

但如果你现在获取,你会发现你的物品是一个大大的黑紫块,名字还老长一串,这显然不是我们想要的

我们还没写它们的数据文件,所以接下来我们就来补齐它们

不过在此之前,我们现在resources文件夹下创建一个assets文件夹,这个文件夹是用于存放资源文件的

再在它下面创建一个与我们模组MOD_ID相同名字的文件夹,这里就是tutorial_mod

原版文件

这里我提一下,如果说还从来没写过这些数据文件,可以去查看原版的那些数据文件

这个项目菜单中的外部库找到neoforge-21.1.193-client-extra-aka-minecraft-resources

这个是游戏客户端放资源文件和数据文件的jar

这个库下面有个assetsdata文件夹,
前者是放资源文件的,像语言文件物品模型文件材质文件等等,
后者是放数据文件的,像战利品列表配方等等

语言文件

tutorial_mod文件夹下已经有一个lang文件夹了,这个是在模板文件中生成的,这个文件夹用于存放语言文件

下面有一个en_us.json文件,这个文件是英文语言文件,当其他语言文件缺省时,游戏会默认采用这个语言文件

当然,我们也可以创建一个zh_cn.json文件,这个是简体中文的语言文件

原本的文件中有示例物品、方块的翻译,我们就去掉好了,而另外的则是模组配置页面的翻译,可以先留着

这里我们来创建刚才写的那些物品的翻译

1
2
3
4
5
{
"item.tutorial_mod.ice_ether": "Ice Ether",
"item.tutorial_mod.raw_ice_ether": "Raw Ice Ether",
"item.tutorial_mod.material.cardboard": "Cardboard"
}

item.tutorial_mod.ice_ether是物品实际注册的名字,当我们没有写语言文件时,在游戏中显示的就说这一串

另外两个也是一样的,不过,CARDBOARD我们在注册时多加了一个material/,而/在语言文件中会被替换为.,所以这里就是material.cardboard

所以语言文件的作用是翻译这一长串东西

物品注册的名字是item + . + MOD_ID + . + 物品注册名

同理,方块其实你也可以推测出来了,当然,我们在后面的教程中再说

物品模型

tutorial_mod文件夹下创建一个models(注意复数)文件夹

再创建一个item(注意单数)文件夹,用来存放物品的模型文件

我们再创建一个ice_ether.json文件,这个文件就是ICE_ETHER物品的模型文件

1
2
3
4
5
6
{
"parent": "item/generated",
"textures": {
"layer0": "tutorial_mod:item/ice_ether"
}
}

这里写的也是最简单的物品模型文件

parent是父模型,这里我们使用的是item/generated,也就是原一般版物品的模型

textures是纹理,这里我们使用的是tutorial_mod:item/ice_ether,命名空间 + : + 文件夹 + / + 文件名,
其对应的是textures/item文件夹下的ice_ether.png文件

另外两个也一样

RAW ICE ETHER

1
2
3
4
5
6
{
"parent": "item/generated",
"textures": {
"layer0": "tutorial_mod:item/raw_ice_ether"
}
}

但是,对于CARDBOARD而言,我们还得再item下创建一个material文件夹,然后创建一个cardboard.json文件

1
2
3
4
5
6
{
"parent": "item/generated",
"textures": {
"layer0": "tutorial_mod:item/material/cardboard"
}
}

所以,cardboard.json文件的层级是resources/assets/tutorial_mod/models/item/material/cardboard.json

这就是为什么我说/可以用来分类,这样就相当于CARDBOARD被我们归在了material下,当你有很多很多物品时,
如果全扔在一个文件夹下,后期维护起来肯定是很麻烦的,而归类则方便维护

至于这里的材质文件,你其实可以写成tutorial_mod:item/cardboard
但同样的道理,还是建议将material/加上

材质文件

tutorial_mod文件夹下创建一个textures文件夹,再创建一个item文件夹,用来存放物品的材质文件

ICE ETHERRAW ICE ETHER的材质文件就直接放item下就行

CARDBOARD的材质文件则要放到item/material

材质文件的绘制?能画画的软件都可以,我用的Aseprite,一个专业的像素画软件,
Steam上可以购买(70块),你也可以到它的源代码仓库下载源代码自行编译,那是免费的

另外的Blockbench也是不错的选择,一个轻量级的用于Minecraft建模的三维软件,
但也可以画画,免费开源

后面我们也会用到Blockbench制作自定义模型的物品和方块,感兴趣的同学可以先学

测试

那么在此之后,我们就可以启动游戏进行测试了

当然,因为没写创造模式物品栏,所以我们使用give来获取物品

1
2
3
/give @a tutorial_mod:ice_ether
/give @a tutorial_mod:raw_ice_ether
/give @a tutorial_mod:material/cardboard

那么当我们获得物品且名字和材质都正确时,就说明物品已经添加成功了