本文最后更新于 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() { 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();
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/