Minecraft Modding 教程02 添加物品

前文回顾

上一篇文章中,我们讲完了一些基本概念,包括,注册,事件系统和游戏的两个端,这一篇教程我们会添加第一个自定义物品——闪电剑,要实现的效果是,左键攻击到生物实体时,会在生物的位置降下闪电,右键一个方块时,会在方块的位置降下闪电。本篇教程与前一篇跨度比较大,没有Java基础的朋友可以直接走了。

Where to start?

本教程可能在有些读者看来略显啰嗦,但是我仍然不准备像其他教程那样,直接甩一堆代码上来让大家理解,我会带大家从原版代码出发,一步步分析,模仿原版的实现,这样才能让大家对Minecraft的底层知识有更深刻的理解,并且写出更符合Minecraft本身的风格的模组。

想要实现一把剑,我们可以看看原版是怎么做的,这时候就要利用IDEA的全局搜索了,在IDEA中双击Shift键即可打开全局搜索,打开后注意勾上"Include non-project items",不然无法搜索到原版代码中的内容。我们搜索"Diamond Sword"即钻石剑:

image-20250122232959919

可以看到出来了一堆,下面的都是json文件和图片,不用管,我们直接双击第一个,跳转到源码位置,可以看到是这样写的:

1
public static final Item DIAMOND_SWORD = registerItem("diamond_sword", new SwordItem(Tiers.DIAMOND, 3, -2.4F, new Item.Properties()));

我们通过语义就能理解,这是在注册一个id为diamond_sword的的物品,上面代码中的registerItem函数的第一个参数就是这个字符串形式的id,第二个参数,明显是我们感兴趣的,这里直接new了一个名为SwordItem类的实例,根据这个类的名字,我们就能知道,这就是与剑有关的物品类,Ctrl+鼠标左键进入查看这个类,发现了许多我们在游戏中熟悉的元素,比如攻击伤害、攻击速度等等。那么我们可否依葫芦画瓢来添加我们自己的剑呢?当然是可以的!

注册物品

还记得我们在第一节介绍的如何注册物品吗

为了代码的组织结构良好,我们先在mod主类的同级目录下新建一个包,命名为registers,在该包中新建一个类ModItemRegister,新建完成后,你的目录结构应该是这样的:

1
2
3
4
5
./
├── Config.java
├── YourMod.java
└── registers
└── ModItemRegister.java

接下来,在ModItemRegister类中编写我们的注册逻辑

1
2
3
4
5
6
public ModItemRegister{
// 创建物品注册器
public final static DeferredRegister.Items ITEMS = DeferredRegister.createItems(YourMod.ModId);
// 注册物品闪电剑
public final static LIGHTNING_SWORD_ITEM = ITEMS.registerItem("lighting_sword", (pProp)->new SwordItem(Tiers.DIAMOND, 3, -2.4f, pProp));
}

在Minecraft中,剑的伤害计算方式是:剑的Tier等级对应的伤害 + SwordItem构造函数中的第二个参数 + 1,

剑的不同Tier等级伤害如下:

  • 木制(Tiers.WOOD):0.0
  • 石制(Tiers.STONE):1.0
  • 铁制(Tiers.IRON):2.0
  • 钻石(Tiers.DIAMOND):3.0
  • 黄金(Tiers.GOLD):0.0
  • 下界合金(Tiers.NETHERITE):3.0

最后还要加个一是因为Minecraft中最低伤害为1,你如果这样写SwordItem的构造函数:SwordItem(Tiers.WOOD, -1, -2.4f, pProp)那么你会得到一把攻击伤害为0的剑,用这把剑攻击敌人不会造成任何击退和伤害,也不会使敌人播放受伤动画。遗憾的是,武器攻击伤害为负时,并没有想象中的回血效果。

而剑的攻击速度的计算方式是:4 + SwordItem构造函数的第三个参数,计算出来的值越高,攻击的间隔时间越少。

然后在mod主类中注册我们的物品注册器

1
2
3
4
5
6
7
8
9
10
11
@Mod(YourMod.ModId)
public YourMod{
public final static ModId = "gtest";

public YourMod(IEventBus modEventBus){
// 注册物品注册器
ModItemRegister.ITEMS.register(modEventBus);
// 这行代码暂时不管,是用来注册mod配置的
ModLoadingContext.get().registerConfig(ModConfig.Type.COMMON, Config.SPEC);
}
}

现在进入游戏后,我们只能通过/give Dev gtest:lightning_sword来获取物品,我们还需要将物品添加到创造模式物品栏中,这里要明确两个概念,即创造模式物品栏(Creative Mode Inventory)和创造模式物品栏标签页(Creative Mode Tab),如下图:

image-20250127141910470

我们需要像你见过的其他mod一样,注册一个属于自己的创造模式物品栏标签页,或者,也可以将物品添加到现有的标签页中。

将物品添加到现有标签页

需要借助BuildCreativeModeTabContentsEvent事件,我们上一篇教程提到过,这是在Mod总线上触发的事件。你可以使用任何你喜欢的我们上一篇教程提到过的事件处理器的添加方式,为了简便起见,我这里就直接在mod主类中编写一个成员函数然后通过modEventBusaddListener方法来监听该事件:

1
2
3
4
5
6
7
8
9
10
11
12
// mod主类中
public void onBuildCreativeModeTabs(BuildCreativeModeTabContentsEvent event){
// 向战斗用品标签页中添加我们的物品
if(event.getTabKey() == CreativeModeTabs.COMBAT){
event.accept(ModItemRegister.LIGHTNING_SWORD_ITEM.get());
}
}

public YourMod(IEventBus modEventBus){
// 省略其他代码...
modEventBus.addListener(this::onBuildCreativeModeTabs);
}

创建自定义标签页

在注册标签页时,就可以决定该标签页下会显示哪些物品,具体如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// ModItemRegister中
// 创建创造模式标签页注册器
public final static DeferredRegister<CreativeModeTab> CREATIVE_MODE_TABS = DeferredRegister.create(
Registries.CREATIVE_MODE_TAB,
YourMod.ModId
);
// 注册自定义创造模式物品栏标签页
public final static DeferredHolder<CreativeModeTab, CreativeModeTab> YOUR_MOD_TAB = CREATIVE_MODE_TABS.register(
"your_mod_tab",
() -> CreativeModeTab.builder()
// 设置标签页标题
.title(Component.literal("演示mod"))
// 设置标签页所在位置,在战斗用品之后
.withTabsBefore(CreativeModeTabs.COMBAT)
// 标签页的图标,是一个ItemStack类型,创建该类型需要一个Item类型的对象,我们这里直接传入Minecraft原版的绒球葱
// 标签页将会显示一个绒球葱作为图标
.icon(() -> new ItemStack(Items.ALLIUM))
// 决定显示哪些物品,调用output对象的accept方法,然后传入刚才我们注册的物品即可(注意要调用get()方法)
.displayItems((parameters, output) -> {
output.accept(LIGHTNING_SWORD_ITEM.get());
}).build()
);

最后,别忘了在mod总线上注册CREATIVE_MODE_TABS,不然看不到任何效果哦

物品材质

现在进入游戏,你会发现物品是一坨紫黑色方块,这是因为我们还没给它设定材质

要为物品设定材质,我们需要一个贴图,贴图最好是正方形的,尺寸不要超过64 x 64,且必须是png文件。

这里推荐一个找贴图的网站:Nova Skin

我们本次使用的贴图如下,大家可以直接右键保存到本地:

lightning-sword

首先,在项目中找到resources文件夹,在里面新建文件夹,新建完成后,你的resources目录结构应该如下:

1
2
3
4
5
6
7
8
9
./
├── META-INF
│   └── mods.toml
└── assets
   └── gtest
   ├── models
   │   └── item
   └── textures
   └── item

随便查看一个Minecraft的资源包,你会发现也会有assets文件夹,没错,这就是储存Minecraft材质的地方。它的直接子目录代表了ResourceLocation的命名空间部分,models文件夹定义了模型文件,它的子目录item存放了我们所有物品的模型文件,还可以添加其他子目录,例如block,来存放方块的模型文件;textures目录存放了贴图文件,其中的item子目录含义与models中一样,表示存放的是物品的贴图。

接下来将我们的贴图文件放到assets/gtext/textures/中,并重命名为lightning_sword.png(这是因为要和代码中的注册名保持一致)

接下来,在models/item/目录中新建文件lightning_sword.json文件名也要和物品的注册名一致。那么这个文件的内容应该怎么写呢?我们仍然可以参考原版剑的写法,查看项目中的.gradle/repositories/ng_dummy_ng/net/minecraft/client/1.20.4/client-1.20.4-sources.jar包,找到里面的assets/minecraft/models/item/iron_sword.json文件,这是Minecraft原版铁剑的模型,可以看到内容非常简单:

1
2
3
4
5
6
{
"parent": "minecraft:item/handheld",
"textures": {
"layer0": "minecraft:item/iron_sword"
}
}

解释一下这其中字段的含义

  • parent:继承的模型文件路径,有兴趣可以去查看一下handheld.json这个文件。除了item/handheld,还有一种常用的模型文件是item/generated,就是最普通的物品模型,例如骨粉、粗铁等都使用的该模型。
  • textures:物品的贴图文件,其中的layer0表示最底层的材质,除了layer0,我们还能填写layer1layer2等等,每一层都会叠加在上一层的上方,如果图片有透明度(Alpha值),那么多个图片的Alpha值会进行叠加。

这些字段值的开头都有一个minecraft:命名空间,我们其实可以省略这个命名空间,缺省值就是minecraft:

我们也仿造原版铁剑编写lightning_sword.json

1
2
3
4
5
6
{
"parent": "minecraft:item/handheld",
"textures": {
"layer0": "gtest:item/lightning_sword"
}
}

注意,我们要使用的模型来自Minecraft,所以parent的命名空间填写minecraft:,但是贴图是我们自己提供的,所以需要指明命名空间和路径,Minecraft通过如下方式读取文件并加载模型:

  • 第一步:textures表示读取assets/<namespace>/textures/目录
  • 第二步:"layer0": "gtest:item/lightning_sword"中的gtest:确定了上一步中的<namespace>gtest
  • 第三步:"layer0": "gtest:item/lightning_sword"中的item/lightning_sword在上一步得出的路径基础上添加item/lightning_sword.png

最终,得出的路径为:assets/gtest/textures/item/lightning_sword.png

现在进入游戏,打开创造模式物品栏即可找到我们新增的闪电剑:

image-20250129152455459

物品的名字显示得似乎有点奇怪,这是因为我们还没有给物品翻译名。回到项目文件中,在resources/assets/gtest/下新建lang目录,你的目录结构应该如下所示:

1
2
3
4
5
6
7
8
9
10
11
./
├── META-INF
│   └── mods.toml
└── assets
   └── gtest
   ├── lang
   ├── models
   │   └── item
   └── textures
   └── item
  

lang目录中新建文件zh_cn.json,这是中文语言的翻译文件,当Minecraft的语言设置为中文时,会应用我们这个文件里面的翻译规则。

作为一名合格的mod开发者,你至少要提供一个英文翻译文件(en_us.json)和一个中文翻译文件(zh_cn.json),更多语言的文件应该如何命名请查看:Minecraft Wiki Language

语言文件的格式是:游戏内路径全名: 翻译名,例如,上述闪电剑应该填写:

1
2
3
{
"item.gtest.lightning_sword": "闪电剑"
}

再次打开游戏,应当能看到物品名字显示正常了

下一篇教程中,我们会实现攻击生物以及右键方块降下闪电的功能。