zh_cn:tutorial:trees
Differences
This shows you the differences between two versions of the page.
| Both sides previous revisionPrevious revisionNext revision | Previous revision | ||
| zh_cn:tutorial:trees [2021/07/25 10:45] – [Creating a BlockStateProvider] breakice | zh_cn:tutorial:trees [2022/08/18 03:40] (current) – external edit 127.0.0.1 | ||
|---|---|---|---|
| Line 1: | Line 1: | ||
| + | FIXME 本文有一段时间没有更新了,可能对未来版本不起作用。请参考[[tutorial: | ||
| + | |||
| + | ===== 添加树木 [1.17](高级) ===== | ||
| + | 阅读本文之前,建议先学习如何创建一个特征地形。\\ | ||
| + | 参见 [[zh_cn: | ||
| + | |||
| + | 树木是在你的 mod 中拓展原版世界生成的一个好方法。\\ | ||
| + | 注意本话题较为高级,因此开始之前,最好要有关于修改世界生成的丰富经验。 | ||
| + | |||
| + | ===== 创建简单的树木 ===== | ||
| + | |||
| + | ==== 结构 ==== | ||
| + | 原版的树的结构分为不同的种类,方便你做出复杂并且漂亮的树。\\ | ||
| + | 概览如下: | ||
| + | |||
| + | - '' | ||
| + | - '' | ||
| + | - '' | ||
| + | - '' | ||
| + | - '' | ||
| + | |||
| + | 如果想让树木长得不那么像是原版的树木,可以选择创建自定义的实现。但是实际上原版的实现通常足够模组的开发了。 | ||
| + | |||
| + | ==== 创建 ConfiguredFeature ==== | ||
| + | 不需要创建新的 '' | ||
| + | |||
| + | 把这个添加到你的 '' | ||
| + | |||
| + | <code java> | ||
| + | public static final ConfiguredFeature<?, | ||
| + | // 使用builder配置特征地形 | ||
| + | .configure(new TreeFeatureConfig.Builder( | ||
| + | new SimpleBlockStateProvider(Blocks.NETHERITE_BLOCK.getDefaultState()), | ||
| + | new StraightTrunkPlacer(8, | ||
| + | new SimpleBlockStateProvider(Blocks.DIAMOND_BLOCK.getDefaultState()), | ||
| + | new SimpleBlockStateProvider(RICH_SAPLING.getDefaultState()), | ||
| + | new BlobFoliagePlacer(ConstantIntProvider.create(5), | ||
| + | new TwoLayersFeatureSize(1, | ||
| + | ).build()) | ||
| + | .spreadHorizontally() | ||
| + | .applyChance(3); | ||
| + | </ | ||
| + | |||
| + | 现在只需要像往常那样向游戏注册 '' | ||
| + | |||
| + | <code java> | ||
| + | @Override | ||
| + | public void onInitialize() { | ||
| + | RegistryKey< | ||
| + | |||
| + | Registry.register(BuiltinRegistries.CONFIGURED_FEATURE, | ||
| + | |||
| + | // 应该为树木使用 VEGETAL_DECORATION 生成步骤 | ||
| + | BiomeModifications.addFeature(BiomeSelectors.foundInOverworld(), | ||
| + | } | ||
| + | </ | ||
| + | |||
| + | ==== 创建树苗 ==== | ||
| + | 树苗是生长树木的一类特殊方块,需要 '' | ||
| + | |||
| + | === 创建 SaplingGenerator === | ||
| + | 简单的生成器接收树木的 '' | ||
| + | |||
| + | <code java> | ||
| + | public class RichSaplingGenerator extends SaplingGenerator { | ||
| + | private final ConfiguredFeature< | ||
| + | |||
| + | public RichSaplingGenerator(ConfiguredFeature<?, | ||
| + | this.feature = (ConfiguredFeature< | ||
| + | } | ||
| + | |||
| + | @Nullable | ||
| + | @Override | ||
| + | protected ConfiguredFeature< | ||
| + | return feature; | ||
| + | } | ||
| + | } | ||
| + | </ | ||
| + | |||
| + | 后面会展示高级的'' | ||
| + | |||
| + | === 创建 SaplingBlock === | ||
| + | 创建方块本身需要继承'' | ||
| + | |||
| + | <code java> | ||
| + | public class RichSaplingBlock extends SaplingBlock { | ||
| + | public RichSaplingBlock(SaplingGenerator generator, Settings settings) { | ||
| + | super(generator, | ||
| + | } | ||
| + | } | ||
| + | </ | ||
| + | |||
| + | === 注册SaplingBlock === | ||
| + | 要注册树苗,按照注册方块的以下步骤(参见[[zh_cn: | ||
| + | |||
| + | 把这个放在用于你的树苗方块的类中: | ||
| + | |||
| + | <code java> | ||
| + | public static final RICH_SAPLING = new RichSaplingBlock(new RichSaplingGenerator(TREE_RICH), | ||
| + | |||
| + | public static void register() { | ||
| + | Registry.register(Registry.BLOCK, | ||
| + | Registry.register(Registry.ITEM, | ||
| + | } | ||
| + | |||
| + | </ | ||
| + | |||
| + | ===== 创建 TrunkPlacer ===== | ||
| + | '' | ||
| + | |||
| + | ==== 原版 TrunkPlacers ==== | ||
| + | 在创建 '' | ||
| + | |||
| + | * '' | ||
| + | * '' | ||
| + | * '' | ||
| + | * '' | ||
| + | |||
| + | ==== 创建TrunkPlacerType ==== | ||
| + | 往游戏注册 '' | ||
| + | |||
| + | 可惜 FabricAPI 目前没有用于创建和注册'' | ||
| + | |||
| + | 我们准备创建一个调用器(invoker)(见[[https:// | ||
| + | |||
| + | 以下是我们的 mixin ,不要忘记加到 mixin 配置中: | ||
| + | |||
| + | <code java> | ||
| + | |||
| + | @Mixin(TrunkPlacerType.class) | ||
| + | public interface TrunkPlacerTypeInvoker { | ||
| + | @Invoker | ||
| + | static <P extends TrunkPlacer> | ||
| + | throw new IllegalStateException(); | ||
| + | } | ||
| + | } | ||
| + | </ | ||
| + | |||
| + | ==== 创建 TrunkPlacer ==== | ||
| + | '' | ||
| + | |||
| + | * 用于序列化的编码解码器。编码解码器(codec)是其自己的话题(topic),这里我们只需要使用 '' | ||
| + | * 获取器(getter),返回 '' | ||
| + | * '' | ||
| + | |||
| + | '' | ||
| + | |||
| + | <code java> | ||
| + | public class RichTrunkPlacer extends TrunkPlacer { | ||
| + | // 使用fillTrunkPlacerFields来创建编码解码器 | ||
| + | public static final Codec< | ||
| + | fillTrunkPlacerFields(instance).apply(instance, | ||
| + | |||
| + | public RichTrunkPlacer(int baseHeight, int firstRandomHeight, | ||
| + | super(baseHeight, | ||
| + | } | ||
| + | |||
| + | @Override | ||
| + | protected TrunkPlacerType<?> | ||
| + | return Tutorial.RICH_TRUNK_PLACER; | ||
| + | } | ||
| + | |||
| + | @Override | ||
| + | public List< | ||
| + | // 将树干下的方块设为泥土 | ||
| + | this.setToDirt(world, | ||
| + | | ||
| + | // 迭代到树干高度限制,并使用 TrunkPlacer 中的 getAndSetState 方法放置两个方块 | ||
| + | for (int i = 0; i < height; i++) { | ||
| + | this.getAndSetState(world, | ||
| + | this.getAndSetState(world, | ||
| + | } | ||
| + | |||
| + | // 创建两个树木节点——一个用于第一个树干,另一个用于第二个 | ||
| + | // 将树干中最高的方块设为中心坐标给 FoliagePlacer 使用 | ||
| + | return ImmutableList.of(new FoliagePlacer.TreeNode(startPos.up(height), | ||
| + | new FoliagePlacer.TreeNode(startPos.east().north().up(height), | ||
| + | } | ||
| + | } | ||
| + | </ | ||
| + | |||
| + | ==== 注册并使用TrunkPlacer ==== | ||
| + | 使用你的调用器,为你的 '' | ||
| + | |||
| + | <code java> | ||
| + | public static final TrunkPlacerType< | ||
| + | </ | ||
| + | |||
| + | 现在将你的 '' | ||
| + | <code java> | ||
| + | [...] | ||
| + | new RichTrunkPlacer(8, | ||
| + | [...] | ||
| + | </ | ||
| + | |||
| + | ===== 创建FoliagePlacer ===== | ||
| + | '' | ||
| + | |||
| + | ==== 原版FoliagePlacer ==== | ||
| + | 创建 '' | ||
| + | |||
| + | * '' | ||
| + | * '' | ||
| + | * '' | ||
| + | |||
| + | ==== 创建 FoliagePlacerType ==== | ||
| + | 往游戏中注册 '' | ||
| + | |||
| + | 和 '' | ||
| + | |||
| + | <code java> | ||
| + | @Mixin(FoliagePlacerType.class) | ||
| + | public interface FoliagePlacerTypeInvoker { | ||
| + | @Invoker | ||
| + | static <P extends FoliagePlacer> | ||
| + | throw new IllegalStateException(); | ||
| + | } | ||
| + | } | ||
| + | </ | ||
| + | |||
| + | ==== 创建 FoliagePlacer ==== | ||
| + | '' | ||
| + | |||
| + | * 用于序列化的编码解码器。在此例中我们展示了如何往编码解码器中添加一个额外的 IntProvider。 | ||
| + | * 用于获取 '' | ||
| + | * '' | ||
| + | * '' | ||
| + | * '' | ||
| + | |||
| + | 我们的 '' | ||
| + | |||
| + | <code java> | ||
| + | |||
| + | public class RichFoliagePlacer extends FoliagePlacer { | ||
| + | // 对于foliageHeight我们使用由 IntProvider.createValidatingCodec 生成的编码解码器 | ||
| + | // 方法参数,我们传入 IntProvider 的最小值和最大值 | ||
| + | // 为了向你的 TrunkPlacer/ | ||
| + | // | ||
| + | // 如果想创建属于我们自己的编码解码器类型,可以参考 IntProvider.createValidatingCodec 方法的源代码。 | ||
| + | public static final Codec< | ||
| + | fillFoliagePlacerFields(instance) | ||
| + | .and(IntProvider.createValidatingCodec(1, | ||
| + | .apply(instance, | ||
| + | |||
| + | private final IntProvider foliageHeight; | ||
| + | |||
| + | public RichFoliagePlacer(IntProvider radius, IntProvider offset, IntProvider foliageHeight) { | ||
| + | super(radius, | ||
| + | |||
| + | this.foliageHeight = foliageHeight; | ||
| + | } | ||
| + | |||
| + | public IntProvider getFoliageHeight() { | ||
| + | return this.foliageHeight; | ||
| + | } | ||
| + | |||
| + | @Override | ||
| + | protected FoliagePlacerType<?> | ||
| + | return Tutorial.RICH_FOLIAGE_PLACER; | ||
| + | } | ||
| + | |||
| + | @Override | ||
| + | protected void generate(TestableWorld world, BiConsumer< | ||
| + | BlockPos.Mutable center = treeNode.getCenter().mutableCopy(); | ||
| + | |||
| + | for ( | ||
| + | // 从 X 开始:中心 - 半径 | ||
| + | Vec3i vec = center.subtract(new Vec3i(radius, | ||
| + | // 在 X 结束:中心 + 半径 | ||
| + | vec.compareTo(center.add(new Vec3i(radius, | ||
| + | // 每次移动1 | ||
| + | vec.add(1, 0, 0)) { | ||
| + | this.placeFoliageBlock(world, | ||
| + | } | ||
| + | |||
| + | for (Vec3i vec = center.subtract(new Vec3i(0, radius, 0)); vec.compareTo(center.add(new Vec3i(0, radius, 0))) == 0; vec.add(0, 1, 0)) { | ||
| + | this.placeFoliageBlock(world, | ||
| + | } | ||
| + | } | ||
| + | |||
| + | @Override | ||
| + | public int getRandomHeight(Random random, int trunkHeight, | ||
| + | // 使用 IntProvider 挑选随机高度 | ||
| + | return foliageHeight.get(random); | ||
| + | } | ||
| + | |||
| + | @Override | ||
| + | protected boolean isInvalidForLeaves(Random random, int dx, int y, int dz, int radius, boolean giantTrunk) { | ||
| + | // 我们的 FoliagePlacer 不为树叶设置限制 | ||
| + | return false; | ||
| + | } | ||
| + | } | ||
| + | |||
| + | </ | ||
| + | ==== 注册并使用你的 FoliagePlacer ==== | ||
| + | 该过程几乎相同,只需要使用你的调用器(invoker)创建并注册 '' | ||
| + | |||
| + | <code java> | ||
| + | public static final FoliagePlacerType< | ||
| + | </ | ||
| + | |||
| + | 并将旧的 '' | ||
| + | |||
| + | <code java> | ||
| + | [...] | ||
| + | new RichFoliagePlacer(ConstantIntProvider.create(5), | ||
| + | [...] | ||
| + | </ | ||
| + | |||
| + | ===== 创建一个 TreeDecorator ===== | ||
| + | '' | ||
| + | |||
| + | ==== 原版的 TreeDecorators ==== | ||
| + | 原版的 '' | ||
| + | 和 '' | ||
| + | |||
| + | 虽然这是一件非常繁琐的事情,但是你还是需要创建你自己的 '' | ||
| + | |||
| + | ==== 创建一个 TreeDecoratorType ==== | ||
| + | 一个 '' | ||
| + | |||
| + | FabricAPI 没有提供任何工具用于创建 '' | ||
| + | |||
| + | 我们的 mixin 大概会看起来非常像是以下内容,同时不要忘记把他们添加到你自己的 mixin 配置文件当中: | ||
| + | |||
| + | <code java> | ||
| + | @Mixin(TreeDecoratorType.class) | ||
| + | public interface TreeDecoratorTypeInvoker { | ||
| + | @Invoker | ||
| + | static <P extends TreeDecorator> | ||
| + | throw new IllegalStateException(); | ||
| + | } | ||
| + | } | ||
| + | </ | ||
| + | |||
| + | ==== 创建 TreeDecorator ==== | ||
| + | '' | ||
| + | |||
| + | * 一个可用于序列化的编码解码器。但默认情况下为空,因为构造函数是没有参数的。如果需要,你可以随时扩展(expand)它。 | ||
| + | * 你的 '' | ||
| + | * 为修饰树而存在的 '' | ||
| + | |||
| + | 我们的 '' | ||
| + | |||
| + | <code java> | ||
| + | public class RichTreeDecorator extends TreeDecorator { | ||
| + | public static final RichTreeDecorator INSTANCE = new RichTreeDecorator(); | ||
| + | // 我们的构造函数没有任何参数,所以我们创建一个单元编解码器,让他返回一个单例对象。 | ||
| + | public static final Codec< | ||
| + | |||
| + | @Override | ||
| + | protected TreeDecoratorType<?> | ||
| + | return Tutorial.RICH_TREE_DECORATOR; | ||
| + | } | ||
| + | |||
| + | @Override | ||
| + | public void generate(TestableWorld world, BiConsumer< | ||
| + | // 遍历方块位置 | ||
| + | for (BlockPos logPosition : logPositions) { | ||
| + | // 选择一个从 0(含)到 4(不含)的值,如果是 0,则继续 | ||
| + | // 这是一个让树生成金块从而让我们走向富裕的机会,太爽了。 | ||
| + | if (random.nextInt(4) == 0) { | ||
| + | // 选择一个从 0 到 4 的随机值,并使用它确定将放置金块到树的一侧 | ||
| + | int sideRaw = random.nextInt(4); | ||
| + | Direction side = switch (sideRaw) { | ||
| + | case 0 -> Direction.NORTH; | ||
| + | case 1 -> Direction.SOUTH; | ||
| + | case 2 -> Direction.EAST; | ||
| + | case 3 -> Direction.WEST; | ||
| + | default -> throw new ArithmeticException(" | ||
| + | }; | ||
| + | |||
| + | // 通过结果边偏移树木位置 | ||
| + | BlockPos targetPosition = logPosition.offset(side, | ||
| + | |||
| + | // 使用 BiConsumer replacer 放置金块! | ||
| + | // 这是在 TrunkPlacers、FoliagePlacers 和 TreeDecorators 中放置方块的标准方法。 | ||
| + | replacer.accept(targetPosition, | ||
| + | } | ||
| + | } | ||
| + | } | ||
| + | } | ||
| + | </ | ||
| + | |||
| + | ==== 注册和使用你的 TreeDecorator ==== | ||
| + | 首先,使用调用器(invoker)创建你的 '' | ||
| + | |||
| + | <code java> | ||
| + | public static final TreeDecoratorType< | ||
| + | </ | ||
| + | |||
| + | 然后,在创建你的 '' | ||
| + | |||
| + | <code java> | ||
| + | [...] | ||
| + | .decorators(Collections.singletonList(RichTreeDecorator.INSTANCE)) | ||
| + | [...] | ||
| + | </ | ||
| + | |||
| + | ===== 创建一个高级的 SaplingGenerator ===== | ||
| + | 所以,还记得我告诉过你 '' | ||
| + | 这是一个例子 - 我们这次来创建几棵原版的树木而不是实际的树: | ||
| + | |||
| + | <code java> | ||
| + | public class RichSaplingGenerator extends SaplingGenerator { | ||
| + | private final ConfiguredFeature< | ||
| + | |||
| + | public RichSaplingGenerator(ConfiguredFeature<?, | ||
| + | this.feature = (ConfiguredFeature< | ||
| + | } | ||
| + | |||
| + | @Nullable | ||
| + | @Override | ||
| + | protected ConfiguredFeature< | ||
| + | int chance = random.nextInt(100); | ||
| + | | ||
| + | // 每棵树都有 10% 的几率 | ||
| + | if (chance < 10) { | ||
| + | return ConfiguredFeatures.OAK; | ||
| + | } else if (chance < 20) { | ||
| + | return ConfiguredFeatures.BIRCH; | ||
| + | } else if (chance < 60) { | ||
| + | return ConfiguredFeatures.SPRUCE; | ||
| + | } else if (chance < 40) { | ||
| + | return ConfiguredFeatures.MEGA_SPRUCE; | ||
| + | } else if (chance < 50) { | ||
| + | return ConfiguredFeatures.PINE; | ||
| + | } else if (chance < 60) { | ||
| + | return ConfiguredFeatures.MEGA_PINE; | ||
| + | } else if (chance < 70) { | ||
| + | return ConfiguredFeatures.MEGA_JUNGLE_TREE; | ||
| + | } | ||
| + | | ||
| + | // 如果这些都没有发生(随机值在 70 到 100 之间),则创建实际的树 | ||
| + | return feature; | ||
| + | } | ||
| + | } | ||
| + | </ | ||
| + | |||
| + | 其实这没啥练手的,但是他给你展示了 '' | ||
| + | |||
| + | ===== 给你的树整点额外逻辑! ===== | ||
| + | 使用额外的 '' | ||
| + | |||
| + | ==== dirtProvider ==== | ||
| + | 还记得巨型云杉木下面的灰化土吗,它就是用这个做到的。 | ||
| + | |||
| + | 设置一下 '' | ||
| + | |||
| + | 例子: | ||
| + | <code java> | ||
| + | [...] | ||
| + | .dirtProvider(new SimpleBlockStateProvider(Blocks.IRON_BLOCK.getDefaultState())) | ||
| + | [...] | ||
| + | </ | ||
| + | |||
| + | ==== decorators ==== | ||
| + | 用来在你的树上添加 '' | ||
| + | 本教程的 '' | ||
| + | 如果你想,你可以使用像 '' | ||
| + | |||
| + | 例子 | ||
| + | |||
| + | <code java> | ||
| + | [...] | ||
| + | .decorators(Arrays.asList( | ||
| + | FirstTreeDecorator.INSTANCE, | ||
| + | SecondTreeDecorator.INSTANCE, | ||
| + | ThirdTreeDecorator.INSTANCE | ||
| + | )) | ||
| + | [...] | ||
| + | </ | ||
| + | |||
| + | ==== ignoreVines ==== | ||
| + | 使树生长时无视藤蔓。 | ||
| + | |||
| + | 例子 | ||
| + | |||
| + | <code java> | ||
| + | [...] | ||
| + | .ignoreVines() | ||
| + | [...] | ||
| + | </ | ||
| + | |||
| + | ==== forceDirt ==== | ||
| + | 强制 '' | ||
| + | |||
| + | 例子: | ||
| + | |||
| + | <code java> | ||
| + | [...] | ||
| + | .forceDirt() | ||
| + | [...] | ||
| + | </ | ||
| + | |||
| ===== 创建一个 BlockStateProvider ===== | ===== 创建一个 BlockStateProvider ===== | ||
| 敬请期待! | 敬请期待! | ||
zh_cn/tutorial/trees.1627209950.txt.gz · Last modified: 2021/07/25 10:45 by breakice