本篇教程的视频

本篇教程的源代码

Github地址:TutorialMod-Headwear-1.21

介绍

前面我们写了盔甲,如果你进一步挖掘源代码,其实可以发现他们不仅仅是继承了Item,还实现了Equipment这个接口

这个接口实现的便是让我们的物品可以被穿戴,而且还可以被穿戴在不同的位置上

那么本期我们就用这个接口来实现一个头饰,让玩家可以穿戴在头部,其他部位的也可以自己去实现

本篇教程使用了BlockBench制作头饰的模型,可以自己搞一个,也可以直接使用我提供的模型文件

查看源代码

在写我们自己的头饰之前,我们先看看ArmorItemEquipment的源代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public interface Equipment {
EquipmentSlot getSlotType();

default RegistryEntry<SoundEvent> getEquipSound() {
...
}

default TypedActionResult<ItemStack> equipAndSwap(Item item, World world, PlayerEntity user, Hand hand) {
ItemStack itemStack = user.getStackInHand(hand);
EquipmentSlot equipmentSlot = user.getPreferredEquipmentSlot(itemStack);
if (!user.canUseSlot(equipmentSlot)) {
return TypedActionResult.pass(itemStack);
} else {
ItemStack itemStack2 = user.getEquippedStack(equipmentSlot);
if ((!EnchantmentHelper.hasAnyEnchantmentsWith(itemStack2, EnchantmentEffectComponentTypes.PREVENT_ARMOR_CHANGE) || user.isCreative())
&& !ItemStack.areEqual(itemStack, itemStack2)) {
if (!world.isClient()) {
user.incrementStat(Stats.USED.getOrCreateStat(item));
}

ItemStack itemStack3 = itemStack2.isEmpty() ? itemStack : itemStack2.copyAndEmpty();
ItemStack itemStack4 = user.isCreative() ? itemStack.copy() : itemStack.copyAndEmpty();
user.equipStack(equipmentSlot, itemStack4);
return TypedActionResult.success(itemStack3, world.isClient());
} else {
return TypedActionResult.fail(itemStack);
}
}
}

@Nullable
static Equipment fromStack(ItemStack stack) {
...
}
}

Equipment接口不算复杂,里面有些方法看它的方法名就知道是什么意思了

上面的方法分别是穿戴声音,穿戴并交换物品,从物品栈中获取Equipment实例

其中的equipAndSwap方法是用来穿戴物品的,我们可以看到,它会判断玩家是否可以穿戴这个物品,然后再穿戴。里面有一个附魔的判断,可以看出来是什么吗?绑定诅咒。
也就是说,如果玩家一旦穿上了这个带绑定诅咒的盔甲,就没办法换下来了,除非死亡

这个方法也在ArmorItem中实现了,我们可以看看ArmorItem的源代码

1
2
3
4
@Override
public TypedActionResult<ItemStack> use(World world, PlayerEntity user, Hand hand) {
return this.equipAndSwap(this, world, user, hand);
}

这个重写了Itemuse方法,实现了Equipment接口的equipAndSwap方法,也就是说,当我们右键点击这个物品时,会穿戴这个物品,或者将原来穿着的装备换下来(前提是没有绑定诅咒)

ArmorItem的构造函数其实我们不用去看它,里面主要是获取它的一些属性

1
2
3
public ArmorItem.Type getType() {
return this.type;
}

这个方法是用来获取ArmorItem的类型的,对应有不同的装备槽,结合接口的方法

1
2
3
4
@Override
public EquipmentSlot getSlotType() {
return this.type.getEquipmentSlot();
}

这个方法是接口里的,用来获取装备槽的,结合上面的方法,我们就可以知道这个物品是穿在哪个装备槽上的

Type是内置在ArmorItem中的一个枚举类,里面有一些属性,比如装备槽、基础耐久、名字等等

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public static enum Type implements StringIdentifiable {
HELMET(EquipmentSlot.HEAD, 11, "helmet"),
CHESTPLATE(EquipmentSlot.CHEST, 16, "chestplate"),
LEGGINGS(EquipmentSlot.LEGS, 15, "leggings"),
BOOTS(EquipmentSlot.FEET, 13, "boots"),
BODY(EquipmentSlot.BODY, 16, "body");

...
private final EquipmentSlot equipmentSlot;
private final String name;
private final int baseMaxDamage;

private Type(final EquipmentSlot equipmentSlot, final int baseMaxDamage, final String name) {
this.equipmentSlot = equipmentSlot;
this.name = name;
this.baseMaxDamage = baseMaxDamage;
}

...
}

所以Equipment这个接口主要是让物品可以放在装备槽上

其他的倒是差不多,而本次我们写的头饰,并不会涉及材料、附魔能力、盔甲韧性和击退抗性等等东西,很多东西都可以省略

创建HatItem类

那我们就开始写吧,创建一个HatItem类,这是个帽子,我们继承Item,并实现Equipment接口

1
2
3
4
5
6
7
8
9
10
11
public class HatItem extends Item implements Equipment {

public HatItem(Type type, Settings settings) {
super(settings);
}

@Override
public EquipmentSlot getSlotType() {
return null;
}
}

记得实现super函数和getSlotType方法,这个方法返回的是装备槽,我们这里先返回null,后面再改

创建内置Type枚举类

仿照ArmorItemType枚举类,我们也创建一个Type枚举类,里面有装备槽、基础耐久、名字等属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public static enum Type implements StringIdentifiable {
HAT(EquipmentSlot.HEAD, 11, "hat");

public static final Codec<ArmorItem.Type> CODEC = StringIdentifiable.createBasicCodec(ArmorItem.Type::values);
private final EquipmentSlot equipmentSlot;
private final String name;
private final int baseMaxDamage;

private Type(final EquipmentSlot equipmentSlot, final int baseMaxDamage, final String name) {
this.equipmentSlot = equipmentSlot;
this.name = name;
this.baseMaxDamage = baseMaxDamage;
}

public int getMaxDamage(int multiplier) {
return this.baseMaxDamage * multiplier;
}

public EquipmentSlot getEquipmentSlot() {
return this.equipmentSlot;
}

public String getName() {
return this.name;
}

public boolean isTrimmable() {
return false;
}

@Override
public String asString() {
return this.name;
}
}

这个枚举类里面有一个CODEC,这个是用来序列化的,用于数据传输的

这里面我们把isTrimmable()设置为false,就不让它是可锻造的,其他的就和源代码里面一样

设置Type属性

HatItem类中设置Type属性

1
2
3
4
5
protected final Type type;
public HatItem(Type type, Settings settings) {
super(settings);
this.type = type;
}

这里我们把Type属性设置为final,并在构造函数中初始化

设置getSlotType方法

我们的Type写好了,我们就可以在getSlotType方法中返回Type的装备槽

1
2
3
4
@Override
public EquipmentSlot getSlotType() {
return this.type.getEquipmentSlot();
}

这样我们的帽子就可以穿在头部了

重写use方法

我们还可以重写use方法,让玩家右键点击时穿戴这个帽子

1
2
3
4
@Override
public TypedActionResult<ItemStack> use(World world, PlayerEntity user, Hand hand) {
return this.equipAndSwap(this, world, user, hand);
}

这里就是调用Equipment接口的equipAndSwap方法,穿戴这个帽子

注册物品

我们还得注册这个物品,和之前的物品注册方法一样

1
2
public static final Item HAT = registerItems("hat", new HatItem(HatItem.Type.HAT,
new Item.Settings().maxDamage(HatItem.Type.HAT.getMaxDamage(5))));

因为我们仿写的类和盔甲类差不多,所以这里的注册方法也差不多,这里的耐久值就设置为5,还是一个乘数

写入物品栏

记得把我们的帽子写入物品栏

1
entries.add(ModItems.HAT);

数据文件

语言文件

1
translationBuilder.add(ModItems.HAT, "Hat");

模型文件

模型文件我们这里不用数据生成,因为我们用BlockBench做了这个模型文件

不过,在BlockBench做好之后,记得在显示模式里面进行调节,尤其是头部这一项。因为我们的帽子是穿在头部的,所以要调节好位置

然后如果你想制作其他部位的饰品,在调节这个显示模式的时候可能不太好搞,可能得参考原版的文件

最后导出模型文件,放到对应文件夹下

材质文件也不要忘记

随后,我们进入游戏,穿戴这个帽子,看看效果