『模组开发日记』之关于如何生成挂在树上的果子

本文最后更新于 2024年12月1日 凌晨

如何生成挂在树上的果子


前言

在我的世界中有一些树在生成的时候会附带例如藤蔓,可可果,蜂巢等一起生成


这是由 TreeDecorator 树木装饰器类来实现的。

树木装饰器可以为树木添加额外的块状物,如藤蔓或蜂巢。

这不是教程,仅是笔记,如果当作教程来看则默认你会模组开发的基本物品添加和注册功能的实现


具体实现

创建芒果果实

下面以长在树上的芒果为例

首先需要创建一个芒果果实方块,该方块以 MangoBlock 类来实现其基本功能,并且复制于我先前所写的ModBlocks.MANGO_LEAVES_HARVESTED


1
2
3
public static final Block MANGO_BLOCK = registerBlocks("mango_block",
new MangoBlock(FabricBlockSettings.copyOf(ModBlocks.MANGO_LEAVES_HARVESTED)
.nonOpaque().pistonBehavior(PistonBehavior.DESTROY)));

然后创建 MangoBlock 类并继承 PlantBlock 类,在该类里面实现芒果果实基本的放置前提条件

在该类中创建 VoxelShape SHAPE 字段用于创建芒果的模型体积大小

并使用 Stream.of(...) 来创建该形状的几个组成部分,并通过 reduce 方法来组合它们。

1
2
3
4
public static final VoxelShape SHAPE = Stream.of(
//在这里面自行设计你想要的形状大小,例如
Block.createCuboidShape(7, -0.5, 11, 8, 0.5, 12)
).reduce((v1, v2) -> VoxelShapes.combineAndSimplify(v1, v2, BooleanBiFunction.OR)).get();

接下来创建 MangoBlock 类构造函数并接受类型为 Settings 的参数,在方法内调用父类的构造函数,传入 settings 参数

1
2
3
4
public MangoBlock(Settings settings)
{
super(settings);
}

接下来分别重写 onUse,canPlaceAt,getOutlineShape,getCollisionShape,getPlacementState,getStateForNeighborUpdate和canPathfindThrough方法


1
2
3
4
5
6
7
8
9
10
@Override
public ActionResult onUse(BlockState state, World world, BlockPos pos, PlayerEntity player, Hand hand, BlockHitResult hit)
{
// 掉落一个指定的果子
MangoBlock.dropStack(world, pos, new ItemStack(ModItems.MANGO, 1));
world.playSound(player, pos, SoundEvents.BLOCK_SWEET_BERRY_BUSH_PICK_BERRIES, SoundCategory.BLOCKS, 1.0F, 1.0F);
// 移除方块
world.removeBlock(pos, false);
return ActionResult.SUCCESS;
}

上面方法是当玩家右键点击芒果果实时会掉落一个芒果


1
2
3
4
5
6
7
8
9
10
11
@Override
public boolean canPlaceAt(BlockState state, WorldView world, BlockPos pos)
{
BlockState blockState = world.getBlockState(pos.up(1));
BlockState blockStateBelow = world.getBlockState(pos.down());
BlockState blockStateAbove = world.getBlockState(pos.up(1)); //目标方块上方方块不是空气

return blockState.isOf(ModBlocks.MANGO_LEAVES)
&& blockStateBelow.isAir()
&& !blockStateAbove.isAir(); //确保上方一格不是空气
}

上面代码是芒果果实放置的条件,通过检测当放置方块的上方为 ModBlocks.MANGO_LEAVES 且下方一格为空气和上方一格不为空气是才能放置芒果果实


1
2
3
4
5
6
7
8
9
10
11
@Override
public VoxelShape getOutlineShape(BlockState state, BlockView world, BlockPos pos, ShapeContext context)
{
return SHAPE;
}

@Override
public VoxelShape getCollisionShape(BlockState state, BlockView world, BlockPos pos, ShapeContext context)
{
return SHAPE;
}

重写上面两个方法来实现芒果果实的形状和碰撞箱体积,其中 SHAPE 传入的为上面自己设计的方块形状


1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Override
@Nullable
public BlockState getPlacementState(ItemPlacementContext ctx)
{
BlockState blockState = this.getDefaultState();
World worldView = ctx.getWorld();
BlockPos blockPos = ctx.getBlockPos();
// 仅检查是否可以放置
if (blockState.canPlaceAt(worldView, blockPos))
{
return blockState;
}
return null;
}

上面代码用于检查芒果果实是否可以放置,若无法放置则返回空


1
2
3
4
5
6
7
8
9
@Override
public BlockState getStateForNeighborUpdate(BlockState state, Direction direction, BlockState neighborState, WorldAccess world, BlockPos pos, BlockPos neighborPos)
{
if (direction == Direction.UP && !state.canPlaceAt(world, pos))
{
return Blocks.AIR.getDefaultState();
}
return super.getStateForNeighborUpdate(state, direction, neighborState, world, pos, neighborPos);
}

判断芒果果实上方是否为空气,如果是空气则移除芒果果实


1
2
3
4
5
@Override
public boolean canPathfindThrough(BlockState state, BlockView world, BlockPos pos, NavigationType type)
{
return false;
}

默认如何实体无法穿过该方块


创建树木装饰器

首先创建 ModTreeDecoratorType 类来记录你有哪些树木装饰器

1
2
3
4
5
6
7
8
9
10
11
public record ModTreeDecoratorType<P extends TreeDecorator>(Codec<P> codec)
{
//声明自定义植物装饰生成器
public static final TreeDecoratorType<MangoTreeDecorator> MANGO_TREE_DECORATOR_TYPE = new TreeDecoratorType<>(MangoTreeDecorator.CODEC);

public static void modTreeDecoratorTypeRegistry()
{
// 使用命名空间和路径创建Identifier并注册植物装饰生成器
Registry.register(Registries.TREE_DECORATOR_TYPE, new Identifier("test-mod", "mango_tree_decorator_type"), MANGO_TREE_DECORATOR_TYPE);
}
}

然后在模组主类中的初始化方法中调用 modTreeDecoratorTypeRegistry 方法


再创建一个 MangoTreeDecorator 芒果树装饰器(名字可以随便取),它继承于 TreeDecorator

在该类里定义 Codec,用于序列化和反序列化 MangoTreeDecorator 对象

1
2
3
4
5
public static final Codec<MangoTreeDecorator> CODEC = RecordCodecBuilder.create(instance ->
instance.group(
Codec.FLOAT.fieldOf("probability").forGetter(decorator -> decorator.probability)
).apply(instance, MangoTreeDecorator::new)
);

定义 probability 属性并在构造函数中接受该属性

1
2
3
4
5
6
private final float probability;

public MangoTreeDecorator(float probability)
{
this.probability = probability;
}

接着重写 getType 方法并返回返回在 ModTreeDecoratorType 类中定义的自定义的 mango 树装饰器类型。

1
2
3
4
5
@Override
protected TreeDecoratorType<?> getType()
{
return ModTreeDecoratorType.MANGO_TREE_DECORATOR_TYPE;
}

然后重写 generate 用来实现芒果树装饰器的具体实现效果

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
@Override
public void generate(Generator generator)
{
Random random = generator.getRandom();
if (random.nextFloat() >= this.probability)
{
return;
}

ObjectArrayList<BlockPos> list = generator.getLeavesPositions();
int i = list.get(0).getY(); // 获取第一个叶子块的 Y 值

list.stream().filter(pos -> Math.abs(pos.getY() - i) <= 1).forEach(pos ->
{
for (Direction direction : Direction.Type.HORIZONTAL)
{
Direction oppositeDirection = direction.getOpposite();
BlockPos blockPos = pos.add(oppositeDirection.getOffsetX(), 0, oppositeDirection.getOffsetZ());

// 检查上方是否是指定的树叶
boolean isAboveLeaves = generator.getWorld().testBlockState(pos.up(), state -> state.isOf(ModBlocks.MANGO_LEAVES));
//检查下方的方块是否为空气
boolean isBelowAir = generator.getWorld().testBlockState(pos.down(), AbstractBlock.AbstractBlockState::isAir);

//检查生成条件
if (!(random.nextFloat() <= 0.25f) || !generator.isAir(blockPos) || !isAboveLeaves || !isBelowAir)
{
continue; //如果不满足条件则跳过
}
generator.replace(blockPos, ModBlocks.MANGO_BLOCK.getDefaultState());
}
});
}

做完上面步骤之后就可以在树木生成时创建 MangoTreeDecorator 实例来实现果子生成的效果了

在树生成时创建 MangoTreeDecorator 示例

上面通过创建一个 MangoTreeDecorator(0.75f) 的实例并作为装饰器添加到树的特征配置中

原版中还有其他很多的树装饰器可以用,也能根据设定的概率和逻辑添加额外的装饰效果


总结

树上长果子的效果就是通过树木装饰器来实现。

可以通过创建各种不同的树木装饰器来实现果子,藤蔓等伴随树木生成而生成的效果

以上代码用于 Fabric-1.20 版本模组开发

仅供参考,若想摘抄请在下方留言



“一花一世界,一叶一菩提”


版权所有 © 2024 云梦泽
欢迎访问我的个人网站:https://hgt12.github.io/


『模组开发日记』之关于如何生成挂在树上的果子
http://example.com/2024/11/30/『模组开发日记』之关于如何生成挂在树上的果子/
作者
云梦泽
发布于
2024年11月30日
更新于
2024年12月1日
许可协议