本篇教程的视频

本篇教程的源代码

GitHub地址:TutorialMod-1.20.1-Forge-DoubleCrop

作物方块类

上一期教程我们已经写了一个单方块作物,也了解了原版作物方块的逻辑,那么本期教程我们来实现一个两格高的作物

原版的话,也有多方块作物,比如甘蔗、仙人掌这种,不过它们的生长逻辑并不是像作物方块一样的,感兴趣的同学可以自己去研究,这里我们还是基于作物方块来写

创建方块类

这里我们新建一个CornCrop,同样还是继承CropBlock

1
2
3
4
5
public class CornCrop extends CropBlock {
public CornCrop(Properties pProperties) {
super(pProperties);
}
}

常规参数

我们先把一些常规的参数和方法写了,生长逻辑后面再写

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
36
37
38
39
40
public static final int FIRST_STAGE_AGE = 7;
public static final int SECOND_STAGE_AGE = 1;
public static final IntegerProperty AGE = IntegerProperty.create("age", 0, 8);
private static final VoxelShape[] SHAPE_BY_AGE = new VoxelShape[]{
Block.box(0.0D, 0.0D, 0.0D, 16.0D, 2.0D, 16.0D),
Block.box(0.0D, 0.0D, 0.0D, 16.0D, 4.0D, 16.0D),
Block.box(0.0D, 0.0D, 0.0D, 16.0D, 6.0D, 16.0D),
Block.box(0.0D, 0.0D, 0.0D, 16.0D, 8.0D, 16.0D),
Block.box(0.0D, 0.0D, 0.0D, 16.0D, 10.0D, 16.0D),
Block.box(0.0D, 0.0D, 0.0D, 16.0D, 12.0D, 16.0D),
Block.box(0.0D, 0.0D, 0.0D, 16.0D, 14.0D, 16.0D),
Block.box(0.0D, 0.0D, 0.0D, 16.0D, 16.0D, 16.0D),
Block.box(0.0D, 0.0D, 0.0D, 16.0D, 8.0D, 16.0D)
};


@Override
public VoxelShape getShape(BlockState pState, BlockGetter pLevel, BlockPos pPos, CollisionContext pContext) {
return SHAPE_BY_AGE[pState.getValue(this.getAgeProperty())];
}

@Override
public int getMaxAge() {
return FIRST_STAGE_AGE + SECOND_STAGE_AGE;
}

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

@Override
protected void createBlockStateDefinition(StateDefinition.Builder<Block, BlockState> pBuilder) {
pBuilder.add(AGE);
}

@Override
protected ItemLike getBaseSeedId() {
return ModItems.CORN.get();
}

这里我们将作物的第一个阶段和第二个阶段分别设为7和1,这样两个阶段就会有8个阶段了

另外再重新设置一下碰撞箱,以契合我们的作物

canSurvive 方法

1
2
3
4
5
6
@Override
public boolean canSurvive(BlockState pState, LevelReader pLevel, BlockPos pPos) {
BlockState below = pLevel.getBlockState(pPos.below());
return super.canSurvive(pState, pLevel, pPos) ||
below.is(this) && below.getValue(AGE) == FIRST_STAGE_AGE;
}

这里我们再来重写canSurvive方法,这里我们需要判断下方的方块是否是CornCrop,并且下方的方块的阶段是否为7

下面的方块如果没了,或者说还没长到7,上面的方块也不能存在

randomTick 方法

接下来我们重写随机刻生长逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Override
public void randomTick(BlockState pState, ServerLevel pLevel, BlockPos pPos, RandomSource pRandom) {
if (pLevel.getRawBrightness(pPos, 0) >= 9) {
int age = this.getAge(pState);
if (age < this.getMaxAge()) {
float f = getGrowthSpeed(this, pLevel, pPos);
if (pRandom.nextInt((int) (25.0F / f) + 1) == 0) {
if (age == FIRST_STAGE_AGE) {
BlockState above = pLevel.getBlockState(pPos.above());
if (above.isAir()) {
pLevel.setBlock(pPos.above(), this.getStateForAge(age + 1), 3);
}
} else {
pLevel.setBlock(pPos, this.getStateForAge(age + 1), 3);
}
}
}
}
}

其实到最后一层if,前面的逻辑和原版的基本一致

而最后一层的逻辑,就是判断当前方块的阶段,如果为7,则判断上方方块是否为空,如果为空,则生成一个CornCrop,也就是长上去

不然就是让当前作物方块自己长

growCrops 方法

接下来我们重写growCrops方法,这是骨粉催熟执行的逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Override
public void growCrops(Level pLevel, BlockPos pPos, BlockState pState) {
int nextAge = this.getAge(pState) + this.getBonemealAgeIncrease(pLevel);
int maxAge = this.getMaxAge();
if (nextAge > maxAge) {
nextAge = maxAge;
}
BlockState above = pLevel.getBlockState(pPos.above());
if (this.getAge(pState) == FIRST_STAGE_AGE && above.isAir()) {
pLevel.setBlock(pPos.above(), this.getStateForAge(nextAge), 3);
} else {
pLevel.setBlock(pPos, this.getStateForAge(nextAge - 1), 3);
}
}

use 方法

我们再重写use方法,这里我们来实现一下类似与农夫乐事中,右键成熟作物即可收获,并让作物回到最初的生长阶段这样的逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
 @Override
public InteractionResult use(BlockState pState, Level pLevel, BlockPos pPos, Player pPlayer, InteractionHand pHand, BlockHitResult pHit) {
if (!pLevel.isClientSide()) {
if (this.getAge(pState) == this.getMaxAge()) {
pLevel.removeBlock(pPos, false);
pLevel.setBlock(pPos.below(), this.getStateForAge(0), 3);
popResource(pLevel, pPos, new ItemStack(ModItems.CORN.get()));
popExperience((ServerLevel) pLevel, pPos, 5);
return InteractionResult.SUCCESS;
}
}
return InteractionResult.PASS;
}

当然,这里的逻辑只能右键成长阶段为8的作物,也就是右键第二格的作物才能执行逻辑

这里的逻辑简单来说就是:

  • 移除方块
  • 重置第一格作物的生长阶段为0
  • 掉落作物
  • 掉落经验
  • 返回成功

注册方块

接下来我们就来注册这个作物方块

1
2
public static final RegistryObject<CornCrop> CORN_CROP =
BLOCKS.register("corn_crop", () -> new CornCrop(BlockBehaviour.Properties.copy(Blocks.WHEAT)));

和前面写的StrawberryCrop一样,这里我们注册一个CornCrop

注册种子物品

这里因为我们直接将作物的果实作为了种子,所以我们改写一下之前的CORN

1
2
public static final RegistryObject<Item> CORN =
ITEMS.register("corn", () -> new ItemNameBlockItem(ModBlocks.CORN_CROP.get(), new Item.Properties().food(ModFoods.CORN)));

数据文件

战利品列表

1
2
3
4
LootItemCondition.Builder builder2 = LootItemBlockStatePropertyCondition.hasBlockStateProperties(ModBlocks.CORN_CROP.get())
.setProperties(StatePropertiesPredicate.Builder.properties().hasProperty(CornCrop.AGE, 8));
add(ModBlocks.CORN_CROP.get(), createCropDrops(ModBlocks.CORN_CROP.get(),
ModItems.CORN.get(), ModItems.CORN.get(), builder2));

StrawberryCrop一样,我们也需要为这个作物添加LootItemCondition,让它成熟时才能掉落果实和种子

方块模型文件

这里我们再变点花样,生成一种十字交叉型的作物(甜浆果这种),所以我们还需要两个新方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public void crossCrop(CropBlock block, String name, IntegerProperty property) {
Function<BlockState, ConfiguredModel[]> function = state ->
crossStates(state, name, property);

getVariantBuilder(block).forAllStates(function);
}

private ConfiguredModel[] crossStates(BlockState state, String modelName, IntegerProperty property) {
ConfiguredModel[] models = new ConfiguredModel[1];
models[0] = new ConfiguredModel(models().cross(modelName + state.getValue(property),
ResourceLocation.fromNamespaceAndPath(TutorialMod.MOD_ID, "block/" + modelName + state.getValue(property))).renderType("cutout"));

return models;
}

这里我们使用的语句是models().cross(),而之前使用的是models().crop(),这里的cross()方法会生成一个十字交叉的作物,而crop()方法则会生成一个四面围合的作物

然后我们使用这个方法生成作物的模型

1
crossCrop(ModBlocks.CORN_CROP.get(), "corn_crop_stage", CornCrop.AGE);

贴图文件

贴图文件还是一样的,这一次我们需要准备9张贴图,corn_crop_0.pngcorn_crop_8.png