本篇教程的视频

本篇教程的源代码

GitHub地址:TutorialMod-Crop-1.20.1

本篇教程目标

  • 理解原版的作物方块
  • 理解原版的作物方块和作物种子注册
  • 学会创建自定义的作物方块
  • 学会注册作物方块和相关种子
  • 学会作物类方块状态文件的编写

查看源代码

民以食为天对吧,那么现在咱们来种田,不过,当然得现有作物

作物方块注册

这里我们先来看看小麦相关的注册,这里到Blocks中找到WHEAT

1
2
3
4
5
6
7
8
9
10
11
12
public static final Block WHEAT = register(
"wheat",
new CropBlock(
AbstractBlock.Settings.create()
.mapColor(MapColor.DARK_GREEN)
.noCollision()
.ticksRandomly()
.breakInstantly()
.sounds(BlockSoundGroup.CROP)
.pistonBehavior(PistonBehavior.DESTROY)
)
);

这里我们可以看到,它实例化的是CropBlock,这个类是原版作物方块类

它的参数就一个方块设置,但方块设置中可以做很多文章

noCollision()表示无碰撞箱,作物方块是没有碰撞箱的,我们可以自由穿过
不过当我们的十字光标移上去后,也还是会显示它的外轮廓线

ticksRandomly()表示随机刻,也就是随机更新,作物类方块都会有这个,决定作物的生长

breakInstantly()表示立刻破坏,也就是破坏速度很快,我们破坏作物并不是像挖其他方块那样,要挖好一会,看着它被破坏,相反,作物可以立即破坏

pistonBehavior(PistonBehavior.DESTROY)表示活塞行为,也就是活塞推动作物方块时,作物方块会被破坏

作物方块类

接下来我们来看看CropBlock,当然我们这里就看一部分,因为在后面一篇教程中,我们还会再好好了解

1
2
3
public class CropBlock extends PlantBlock implements Fertilizable {
...
}

CropBlock继承了PlantBlock,也就是植物方块,它里面是有一些生长逻辑的

然后又实现了Fertilizable接口,表示可施肥,也就是可以施肥的方块,也就是拿骨粉催熟作物

1
2
3
4
5
6
7
8
9
10
11
12
public static final int MAX_AGE = 7;
public static final IntProperty AGE = Properties.AGE_7;
private static final VoxelShape[] AGE_TO_SHAPE = new VoxelShape[]{
Block.createCuboidShape(0.0, 0.0, 0.0, 16.0, 2.0, 16.0),
Block.createCuboidShape(0.0, 0.0, 0.0, 16.0, 4.0, 16.0),
Block.createCuboidShape(0.0, 0.0, 0.0, 16.0, 6.0, 16.0),
Block.createCuboidShape(0.0, 0.0, 0.0, 16.0, 8.0, 16.0),
Block.createCuboidShape(0.0, 0.0, 0.0, 16.0, 10.0, 16.0),
Block.createCuboidShape(0.0, 0.0, 0.0, 16.0, 12.0, 16.0),
Block.createCuboidShape(0.0, 0.0, 0.0, 16.0, 14.0, 16.0),
Block.createCuboidShape(0.0, 0.0, 0.0, 16.0, 16.0, 16.0)
};

这里的三个变量,分别定义了作物的最大生长阶段生长阶段属性(用在方块状态上),还有每个阶段不同的外框形状(轮廓线)

生长阶段要注意,它是从0开始算的,所以这里会有8个生长阶段,对应的轮廓线也有8

1
2
3
4
@Override
public VoxelShape getOutlineShape(BlockState state, BlockView world, BlockPos pos, ShapeContext context) {
return AGE_TO_SHAPE[this.getAge(state)];
}

这里就是获取作物的轮廓线,根据当前的生长阶段来获取对应的轮廓线

1
2
3
4
@Override
protected boolean canPlantOnTop(BlockState floor, BlockView world, BlockPos pos) {
return floor.isOf(Blocks.FARMLAND);
}

这里就是判断作物方块是否可以种植在当前方块上,默认它是只能种在耕地方块上的,但我们可以改一下,让它可以像甜浆果那样直接种在地上

1
2
3
4
5
6
7
8
9
10
11
protected IntProperty getAgeProperty() {
return AGE;
}

public int getMaxAge() {
return 7;
}

public int getAge(BlockState state) {
return (Integer)state.get(this.getAgeProperty());
}

这里就是获取作物的生长阶段属性最大生长阶段当前生长阶段

不过,我不知道Mojang为什么定义了一个最大生长阶段,结果这里的getMaxAge还是直接返回一个7,怪

那么我们在这期教程中会涉及的就大概这一些,后面还有很多方法,这个我们放到下一期的多方块作物里来讲

作物种子注册

作物的种子这里我们也来看一下,我们在Items中找到WHEAT_SEEDS

1
public static final Item WHEAT_SEEDS = register("wheat_seeds", new AliasedBlockItem(Blocks.WHEAT, new Item.Settings()));

这里实例化的是AliasedBlockItem,其实它也是方块物品,但它有自己的翻译键,而不是和方块共用翻译键

不过注意一下,我们这里是以小麦为例,其他的作物像胡萝卜马铃薯这样的,作物和种子都是同一个东西,与小麦这种还是有点区别的

注册作物

创建作物方块

我们创建一个StrawberryCropBlock,让它继承CropBlock,然后创建一个构造函数

1
2
3
4
5
6
7
public class StrawberryCropBlock extends CropBlock {

public StrawberryCropBlock(Settings settings) {
super(settings);
}

}

对于这个草莓作物,我们可以重新设置一下最大生长阶段和它是生长阶段属性

1
2
public static final int MAX_AGE = 5;
public static final IntProperty AGE = Properties.AGE_5;

原版是正好定义了6个生长阶段属性的变量,所以我们直接拿过来用就好了

那么再接下来我们重写一些和它有关的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Override
public int getMaxAge() {
return MAX_AGE;
}

@Override
protected IntProperty getAgeProperty() {
return AGE;
}

@Override
public int getAge(BlockState state) {
return state.get(this.getAgeProperty());
}

@Override
protected void appendProperties(StateManager.Builder<Block, BlockState> builder) {
builder.add(AGE);
}

这里重写最大生长阶段生长阶段属性当前生长阶段,还有方块状态

然后,我们要返回作物的种子

1
2
3
4
@Override
protected ItemConvertible getSeedsItem() {
return ModItems.STRAWBERRY_SEEDS;
}

这里我们返回的是ModItems.STRAWBERRY_SEEDS,也就是我们后面要注册的草莓种子

我们再改写一下这个作物可以种植的位置

1
2
3
4
@Override
protected boolean canPlantOnTop(BlockState floor, BlockView world, BlockPos pos) {
return floor.isOf(Blocks.FARMLAND) || floor.isIn(BlockTags.DIRT);
}

这里我们让草莓作物既可以种植在耕地方块上,也可以种在泥土类方块上(包括泥土、草方块、砂土)

那么这样,我们完成了自定义作物的编写,接下来就是注册这个作物方块

注册作物方块

1
2
public static final Block STRAWBERRY_CROP = Registry.register(Registries.BLOCK, new Identifier(TutorialMod.MOD_ID, "strawberry_crop"),
new StrawberryCropBlock(AbstractBlock.Settings.create().noCollision().ticksRandomly().breakInstantly().pistonBehavior(PistonBehavior.DESTROY)));

注册作物方块我们得直接用Registry.register,因为我们不需要对应的方块物品,毕竟作物方块是种在地上的,
拿在手里的只是种植或者成熟的果实,而不是植株

这里实例化的是StrawberryCropBlock,也就是我们前面创建的草莓作物方块,后面的设置我们按照小麦的写就好了

注册作物种子

1
2
public static final Item STRAWBERRY_SEEDS = registerItems("strawberry_seeds",
new AliasedBlockItem(ModBlocks.STRAWBERRY_CROP, new Item.Settings()));

这里我们注册的是草莓种子,实例化的是AliasedBlockItem,也就是我们前面提到的

数据生成

语言文件

1
translationBuilder.add(ModItems.STRAWBERRY_SEEDS, "Strawberry Seeds");

战利品列表

作物的战利品列表稍微有点复杂,因为它是分成熟未成熟两种状态来决定最终的掉落物的

成熟的时候,会掉落最终的果实和种子,未成熟的时候,只会掉落种子

这里我们要写一个LootCondition

1
2
3
LootCondition.Builder builder =
BlockStatePropertyLootCondition.builder(ModBlocks.STRAWBERRY_CROP)
.properties(StatePredicate.Builder.create().exactMatch(StrawberryCropBlock.AGE, 5));

这个可以参考原版的BlockLootTableGenerator中的内容

BlockStatePropertyLootCondition.builder中写上作物

properties中写上属性,这里我们写的是StrawberryCropBlock.AGE,也就是草莓作物的生长阶段属性,根据被破坏的作物当前的生长阶段来决定掉落物

然后就可以写战利品列表了

1
addDrop(ModBlocks.STRAWBERRY_CROP, cropDrops(ModBlocks.STRAWBERRY_CROP, ModItems.STRAWBERRY, ModItems.STRAWBERRY_SEEDS, builder));

作物的战利品列表有cropDrops方法,里面的参数是作物方块、作物种子和上面写的条件

方块模型

作物模型的数据生成同样有单独的方法

1
blockStateModelGenerator.registerCrop(ModBlocks.STRAWBERRY_CROP, StrawberryCropBlock.AGE, 0, 1, 2, 3, 4, 5);

registerCrop来生成作物的模型文件、方块状态文件

第一个参数是作物方块,第二个参数是生长阶段属性,后面的参数是每个生长阶段的值,一一罗列出来

种子物品同样也不用我们写了,它也会自己生成的

材质文件

那么这里的草莓作物一共6个生长阶段,所以我们需要6个不同的材质文件

注意它的命名方法是作物方块注册名 + _stage + 生长阶段数值 + .png

stage数值之间是没有其他字符的,这里我们要从0写到5,一共6

测试

跑好数据生成之后,我们就可以进入游戏进行测试了