本篇教程的视频

本篇教程的源代码

GitHub地址:TutorialMod-CustomTools-1.20.1

本篇教程目标

  • 理解原版的采集工具类中的各个方法
  • 学会利用原版的类来编写自定义的物品类

查看源代码

1.21中,有人提到过,能不能做一个既能实现又能实现功能的物品

那么,当然可以,甚至你将5种工具的功能都结合起来也是可以的

这里我们就得来看看原版的工具类是怎么写的了

PickaxeItem

我们先看看这个物品类

1
2
3
4
5
public class PickaxeItem extends MiningToolItem {
public PickaxeItem(ToolMaterial material, int attackDamage, float attackSpeed, Item.Settings settings) {
super((float)attackDamage, attackSpeed, material, BlockTags.PICKAXE_MINEABLE, settings);
}
}

很短,不过我们也能获取到一些信息

它继承了MiningToolItem这个类,这个也是原版除了以外的4种工具都继承的类

而在它的super函数中,有个BlockTags.PICKAXE_MINEABLE

那么这个标签我们再熟悉不过了,这个是限定使用镐才能采集的方块

MiningToolItem

那么接下来,我们看看它的父类MiningToolItem

当然,它是继承自ToolItemToolItem再继承自Item,这两个我们就不看了,因为不是今天的重点

我们直接来看MiningToolItem中的一些方法

getMiningSpeedMultiplier

1
2
3
4
@Override
public float getMiningSpeedMultiplier(ItemStack stack, BlockState state) {
return state.isIn(this.effectiveBlocks) ? this.miningSpeed : 1.0F;
}

这个是getMiningSpeedMultiplier方法,即获取采集速度修饰方法

它会判断挖掘的方块是否是该工具能够挖掘的方块,也就是通过Tag来判断,
如果是则返回正常的挖掘速度,不是则直接返回1.0

比如说我们拿石镐去挖钻石,挖得会很,就是因为这个方法

postHit和postMine

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Override
public boolean postHit(ItemStack stack, LivingEntity target, LivingEntity attacker) {
stack.damage(2, attacker, e -> e.sendEquipmentBreakStatus(EquipmentSlot.MAINHAND));
return true;
}

@Override
public boolean postMine(ItemStack stack, World world, BlockState state, BlockPos pos, LivingEntity miner) {
if (!world.isClient && state.getHardness(world, pos) != 0.0F) {
stack.damage(1, miner, e -> e.sendEquipmentBreakStatus(EquipmentSlot.MAINHAND));
}

return true;
}

接下来两个方法可以连起来看,一个是攻击时的方法,另一个是挖掘时的方法

能够看到它们的区别在于stack.damage的第一个参数不同

这个方法是减少物品耐久度的,对于这两个方法,我们不难理解,当采集用的工具去攻击其他实体时,会掉2个耐久,
但如果是正常挖掘,则会掉1

我也是到了这里才发现还有这样的特性,以往是不知道的

getAttributeModifiers

1
2
3
4
@Override
public Multimap<EntityAttribute, EntityAttributeModifier> getAttributeModifiers(EquipmentSlot slot) {
return slot == EquipmentSlot.MAINHAND ? this.attributeModifiers : super.getAttributeModifiers(slot);
}

这个getAttributeModifiers是获取属性修饰的方法

我们在游戏中,能够看到那些工具会有在主手时的提示信息,下面还有两行攻击伤害攻击速度

提示信息中的额外加成是仅在主手时生效的,如果你放在副手,则没有这些效果,只有工具材料提供的基础值

isSuitableFor

1
2
3
4
5
6
7
8
9
10
11
@Override
public boolean isSuitableFor(BlockState state) {
int i = this.getMaterial().getMiningLevel();
if (i < MiningLevels.DIAMOND && state.isIn(BlockTags.NEEDS_DIAMOND_TOOL)) {
return false;
} else if (i < MiningLevels.IRON && state.isIn(BlockTags.NEEDS_IRON_TOOL)) {
return false;
} else {
return i < MiningLevels.STONE && state.isIn(BlockTags.NEEDS_STONE_TOOL) ? false : state.isIn(this.effectiveBlocks);
}
}

最后一个要看的方法是isSuitableFor,显然易见,这个是判断不同等级的工具能否挖掘某个方块的

如果方块所需的采集等级高于当前的工具,那么就会挖得很,也就没掉落物,相应的方法可以在Item类中找到

AxeItem

上面的类看完了,我们来看看斧头这个物品类

1
2
3
protected static final Map<Block, Block> STRIPPED_BLOCKS = new Builder<Block, Block>()
.put(Blocks.OAK_WOOD, Blocks.STRIPPED_OAK_WOOD)
...

开头就有一串Map,看名字就知道了,这个是没削皮的原木(木头)与削皮的原木(木头)组成的Map类型的变量

因为斧头右键没削皮的原木时,原木可以被削皮

在它下面重写的useOnBlock方法中就可以看到它的使用

源代码我这里就不放了,useOnBlock方法是使用物品右键某一个方块时会调用

在斧的这个方法中,不仅有处理原木、木头削皮的逻辑,还有给氧化铜块抛光给上蜡的铜块去蜡的逻辑

编写PickaxeAxeItem

看了一堆类和方法,有没有晕乎乎的?哈~其实原版很多东西都是值得研究的

就像我第一期教程讲的那样,慢慢看,慢慢研究,总有些东西我们是可以借鉴的

那么接下来我们就来实现自定义的工具类,创建一PickaxeAxeItem

这里的话,我们直接继承AxeItem好了,不然斧头相关的方法我们还得重新写

再创建一个构造函数

1
2
3
4
5
public class PickaxeAxeItem extends AxeItem {
public PickaxeAxeItem(ToolMaterial material, float attackDamage, float attackSpeed, Settings settings) {
super(material, attackDamage, attackSpeed, settings);
}
}

然后我们就要重写一些方法

首先是采集速度修饰的方法,因为现在我们要实现不仅仅能挖木头,也能挖石头,这个方法判断的方块肯定得改

不过,原版没有现成的标签,所以我们还得自己创建一个

创建标签

那么我们先到ModBlockTags中创建一个方块的标签,里面的内容在写数据生成的时候再写好了

1
public static final TagKey<Block> PICKAXE_AXE = of("pickaxe_axe");

getMiningSpeedMultiplier

随后我们就来重写getMiningSpeedMultiplier方法

1
2
3
4
@Override
public float getMiningSpeedMultiplier(ItemStack stack, BlockState state) {
return state.isIn(ModBlockTags.PICKAXE_AXE) ? this.miningSpeed : 1.0F;
}

这里判断的就是我们自己写的标签

isSuitableFor

同样的道理,要有正确的掉落物,isSuitableFor方法也得改写

1
2
3
4
@Override
public boolean isSuitableFor(BlockState state) {
return state.isIn(ModBlockTags.PICKAXE_AXE);
}

好了,那么现在,我们的类就完成了,是不是比预想中的要简单?

物品注册

接下来就是物品的注册

1
2
public static final Item PICKAXE_AXE = registerItems("pickaxe_axe", new PickaxeAxeItem(
ModToolMaterials.FIRE_ETHER, 6.0F, -2.8F, new Item.Settings().fireproof()));

注意,实例化的类是我们自定义的类,不要写错了

工具材料延用上一期写的就好了

不过在它这里的方块设置,我加上了一个fireproof()方法,这个是给工具的抗火保护

原版游戏中,下界合金类的工具都是有这个的,也就是如果你把这个工具扔到或者岩浆里,正常的掉落物都是会被烧毁的(掉落物也是实体)

但如果有fireproof(),那它们就不会被烧毁

加入物品栏

物品栏别忘了

1
entries.add(ModItems.PICKAXE_AXE);

数据生成

方块标签

接下来就是为我们自定义的方块标签添加内容

其实也是很简单的,就把原版能挖的方块与能挖的方块结合起来就行

1
2
3
getOrCreateTagBuilder(ModBlockTags.PICKAXE_AXE)
.forceAddTag(BlockTags.AXE_MINEABLE)
.forceAddTag(BlockTags.PICKAXE_MINEABLE);

语言文件

1
translationBuilder.add(ModItems.PICKAXE_AXE, "Pickaxe Axe");

模型文件

1
itemModelGenerator.register(ModItems.PICKAXE_AXE, Models.HANDHELD);

同样的,父模型还是使用HANDHELD

测试

数据生成跑一下,材质文件放一下,游戏测试一下就好了