User Tools

Site Tools


tutorial:trees

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revisionPrevious revision
Next revision
Previous revision
tutorial:trees [2021/07/19 14:32] – Remove unnecessary section redgrapefruittutorial:trees [2022/12/21 01:40] (current) – 1.19.3 haykam
Line 1: Line 1:
-===== Adding Trees [1.17] (Advanced) ===== +===== Adding Trees [1.19.2] (Advanced) ===== 
-It is recommended that you learn how to create a ''Feature'' in Minecraft first before reading this tutorial.\\ +It is recommended that you learn how to create a [[tutorial:features|Feature]] in Minecraft first before reading this tutorial.\\
-See [[https://fabricmc.net/wiki/tutorial:features]]+
  
 Trees are a great way to expand Minecraft's world generation in your mod.\\ Trees are a great way to expand Minecraft's world generation in your mod.\\
 Beware that this topic is advanced and preferably you should have decent experience with modding world generation in Minecraft before starting. Beware that this topic is advanced and preferably you should have decent experience with modding world generation in Minecraft before starting.
 +
 +Firstly, you need to understand that a ''PlacedFeature'' is what gets placed in the world. There are a few steps to retrieve a tree placed feature:
 +You need a feature, in our case ''Feature.TREE'', we configure it with ''TreeFeatureConfig'', make a placed feature, make a configured feature and finally a placed feature (This is the placed feature that gets placed in the world)
  
 ===== Creating a Simple Tree ===== ===== Creating a Simple Tree =====
  
 ==== Architecture ==== ==== Architecture ====
-Minecraft's tree architecture is split into different classes to allow for very complex and beautiful trees.\\+Minecraft's tree configuration architecture is split into different classes to allow for very complex and beautiful trees.\\
 Here's an overview: Here's an overview:
  
Line 22: Line 24:
 ==== Creating the ConfiguredFeature ==== ==== Creating the ConfiguredFeature ====
 We don't need to create a new ''Feature'', as the vanilla ''TreeFeature'' is configurable. We don't need to create a new ''Feature'', as the vanilla ''TreeFeature'' is configurable.
-Add this into your ''ModInitializer'''s body:+Add this into your ''ModInitializer'''(This could be your content initializer if you prefer) body:
  
 <code java> <code java>
-public static final ConfiguredFeature<?, ?> TREE_RICH = Feature.TREE+public static final RegistryEntry<ConfiguredFeature<TreeFeatureConfig, ?>> TREE_RICH = ConfiguredFeatures.register("tutorial:tree_rich", Feature.TREE
   // Configure the feature using the builder   // Configure the feature using the builder
-  .configure(new TreeFeatureConfig.Builder( +  new TreeFeatureConfig.Builder( 
-    new SimpleBlockStateProvider(Blocks.NETHERITE_BLOCK.getDefaultState()), // Trunk block provider+    BlockStateProvider.of(Blocks.NETHERITE_BLOCK), // Trunk block provider
     new StraightTrunkPlacer(8, 3, 0), // places a straight trunk     new StraightTrunkPlacer(8, 3, 0), // places a straight trunk
-    new SimpleBlockStateProvider(Blocks.DIAMOND_BLOCK.getDefaultState()), // Foliage block provider +    BlockStateProvider.of(Blocks.DIAMOND_BLOCK), // Foliage block provider
-    new SimpleBlockStateProvider(RICH_SAPLING.getDefaultState()), // Sapling provider; used to determine what blocks the tree can generate on+
     new BlobFoliagePlacer(ConstantIntProvider.create(5), ConstantIntProvider.create(0), 3), // places leaves as a blob (radius, offset from trunk, height)     new BlobFoliagePlacer(ConstantIntProvider.create(5), ConstantIntProvider.create(0), 3), // places leaves as a blob (radius, offset from trunk, height)
     new TwoLayersFeatureSize(1, 0, 1) // The width of the tree at different layers; used to see how tall the tree can be without clipping into blocks     new TwoLayersFeatureSize(1, 0, 1) // The width of the tree at different layers; used to see how tall the tree can be without clipping into blocks
-  ).build()) +  ).build()));
-  .spreadHorizontally() +
-  .applyChance(3); // About a 33% chance to generate per chunk (1/x) +
-</code> +
- +
-Now we just register the ''ConfiguredFeature'' to the game like normal and make the biome modification using Fabric's API: +
- +
-<code java> +
-@Override +
-public void onInitialize() { +
-  RegistryKey<ConfiguredFeature<?, ?>> treeRich = RegistryKey.of(Registry.CONFIGURED_FEATURE_KEY, new Identifier("tutorial", "tree_rich")); +
- +
-  Registry.register(BuiltinRegistries.CONFIGURED_FEATURE, treeRich.getValue(), TREE_RICH); +
- +
-  // You should use the VEGETAL_DECORATION generation step for trees +
-  BiomeModifications.addFeature(BiomeSelectors.foundInOverworld(), GenerationStep.Feature.VEGETAL_DECORATION, treeRich); +
-}+
 </code> </code>
  
Line 61: Line 46:
 <code java> <code java>
 public class RichSaplingGenerator extends SaplingGenerator { public class RichSaplingGenerator extends SaplingGenerator {
-  private final ConfiguredFeature<TreeFeatureConfig, ?> feature; 
- 
-  public RichSaplingGenerator(ConfiguredFeature<?, ?> feature) { 
-    this.feature = (ConfiguredFeature<TreeFeatureConfig, ?>) feature; 
-  } 
- 
   @Nullable   @Nullable
   @Override   @Override
-  protected ConfiguredFeature<TreeFeatureConfig, ?> getTreeFeature(Random random, boolean bees) { +  protected RegistryEntry<ConfiguredFeature<TreeFeatureConfig, ?>> getTreeFeature(Random random, boolean bees) { 
-    return feature;+    return Tutorial.TREE_RICH;
   }   }
 } }
Line 94: Line 73:
  
 <code java> <code java>
-public static final RICH_SAPLING = new RichSaplingBlock(new RichSaplingGenerator(TREE_RICH), FabricBlockSettings.copyOf(Blocks.OAK_SAPLING.getDefaultState()));+public static final RichSaplingBlock RICH_SAPLING = new RichSaplingBlock(new RichSaplingGenerator(TREE_RICH), FabricBlockSettings.copyOf(Blocks.OAK_SAPLING));
  
 public static void register() { public static void register() {
-  Registry.register(Registry.BLOCK, new Identifier("tutorial", "rich_sapling"), RICH_SAPLING); +  Registry.register(Registries.BLOCK, new Identifier("tutorial", "rich_sapling"), RICH_SAPLING); 
-  Registry.register(Registry.ITEM, new Identifier("tutorial", "rich_sapling"), new BlockItem(RICH_SAPLING, ItemGroup.MISC));+  Registry.register(Registries.ITEM, new Identifier("tutorial", "rich_sapling"), new BlockItem(RICH_SAPLING, new FabricItemSettings()));
 } }
  
Line 129: Line 108:
 @Mixin(TrunkPlacerType.class) @Mixin(TrunkPlacerType.class)
 public interface TrunkPlacerTypeInvoker { public interface TrunkPlacerTypeInvoker {
-    @Invoker+    @Invoker("register")
     static <P extends TrunkPlacer> TrunkPlacerType<P> callRegister(String id, Codec<P> codec) {     static <P extends TrunkPlacer> TrunkPlacerType<P> callRegister(String id, Codec<P> codec) {
         throw new IllegalStateException();         throw new IllegalStateException();
Line 163: Line 142:
     public List<FoliagePlacer.TreeNode> generate(TestableWorld world, BiConsumer<BlockPos, BlockState> replacer, Random random, int height, BlockPos startPos, TreeFeatureConfig config) {     public List<FoliagePlacer.TreeNode> generate(TestableWorld world, BiConsumer<BlockPos, BlockState> replacer, Random random, int height, BlockPos startPos, TreeFeatureConfig config) {
         // Set the ground beneath the trunk to dirt         // Set the ground beneath the trunk to dirt
-        this.setToDirt(world, replacer, random, startPos.down(), config);+       setToDirt(world, replacer, random, startPos.down(), config);
                  
         // Iterate until the trunk height limit and place two blocks using the getAndSetState method from TrunkPlacer         // Iterate until the trunk height limit and place two blocks using the getAndSetState method from TrunkPlacer
Line 184: Line 163:
  
 <code java> <code java>
-public static final TrunkPlacerType<RichTrunkPlacer> RICH_TRUNK_PLACER = TrunkPlacerTypeInvoker.callRegister("rich_trunk_placer", RichTrunkPlacer.CODEC);+public static final TrunkPlacerType<RichTrunkPlacer> RICH_TRUNK_PLACER = TrunkPlacerTypeInvoker.callRegister("tutorial:rich_trunk_placer", RichTrunkPlacer.CODEC);
 </code> </code>
  
Line 241: Line 220:
     public static final Codec<RichFoliagePlacer> CODEC = RecordCodecBuilder.create(instance ->     public static final Codec<RichFoliagePlacer> CODEC = RecordCodecBuilder.create(instance ->
         fillFoliagePlacerFields(instance)         fillFoliagePlacerFields(instance)
-        .and(IntProvider.createValidatingCodec(1, 512).fieldOf("foliage_height").forGetter(RichFoliagePlacer::getFoliageHeight)+        .and(IntProvider.createValidatingCodec(1, 512).fieldOf("foliage_height").forGetter(RichFoliagePlacer::getFoliageHeight))
         .apply(instance, RichFoliagePlacer::new));         .apply(instance, RichFoliagePlacer::new));
  
Line 298: Line 277:
  
 <code java> <code java>
-public static final FoliagePlacerType<RichFoliagePlacer> RICH_FOLIAGE_PLACER = FoliagePlacerTypeInvoker.callRegister("rich_foliage_placer", RichFoliagePlacer.CODEC);+public static final FoliagePlacerType<RichFoliagePlacer> RICH_FOLIAGE_PLACER = FoliagePlacerTypeInvoker.callRegister("tutorial:rich_foliage_placer", RichFoliagePlacer.CODEC);
 </code> </code>
  
Line 350: Line 329:
     // Our constructor doesn't have any arguments, so we create a unit codec that returns the singleton instance     // Our constructor doesn't have any arguments, so we create a unit codec that returns the singleton instance
     public static final Codec<RichTreeDecorator> CODEC = Codec.unit(() -> INSTANCE);     public static final Codec<RichTreeDecorator> CODEC = Codec.unit(() -> INSTANCE);
 +    
 +    private RichTreeDecorator() {}
  
     @Override     @Override
Line 357: Line 338:
  
     @Override     @Override
-    public void generate(TestableWorld world, BiConsumer<BlockPos, BlockState> replacer, Random random, List<BlockPos> logPositions, List<BlockPos> leavesPositions) {+    public void generate(TreeDecorator.Generator generator) {
         // Iterate through block positions         // Iterate through block positions
-        for (BlockPos logPosition : logPositions) {+        generator.getLogPositions().forEach(pos -> { 
 +            Random random = generator.getRandom();
             // Pick a value from 0 (inclusive) to 4 (exclusive) and if it's 0, continue             // Pick a value from 0 (inclusive) to 4 (exclusive) and if it's 0, continue
             // This is the chance for spawning the gold block             // This is the chance for spawning the gold block
Line 380: Line 362:
                 replacer.accept(targetPosition, Blocks.GOLD_BLOCK.getDefaultState());                 replacer.accept(targetPosition, Blocks.GOLD_BLOCK.getDefaultState());
             }             }
-        }+        });
     }     }
 } }
Line 389: Line 371:
  
 <code java> <code java>
-public static final TreeDecoratorType<RichTreeDecorator> RICH_TREE_DECORATOR = TreeDecoratorTypeInvoker.callRegister("rich_tree_decorator", RichTreeDecorator.CODEC);+public static final TreeDecoratorType<RichTreeDecorator> RICH_TREE_DECORATOR = TreeDecoratorTypeInvoker.callRegister("tutorial:rich_tree_decorator", RichTreeDecorator.CODEC);
 </code> </code>
  
Line 406: Line 388:
 <code java> <code java>
 public class RichSaplingGenerator extends SaplingGenerator { public class RichSaplingGenerator extends SaplingGenerator {
-    private final ConfiguredFeature<TreeFeatureConfig, ?> feature; 
- 
-    public RichSaplingGenerator(ConfiguredFeature<?, ?> feature) { 
-        this.feature = (ConfiguredFeature<TreeFeatureConfig, ?>) feature; 
-    } 
- 
     @Nullable     @Nullable
     @Override     @Override
-    protected ConfiguredFeature<TreeFeatureConfig, ?> getTreeFeature(Random random, boolean bees) {+    protected RegistryEntry<ConfiguredFeature<TreeFeatureConfig, ?>> getTreeFeature(Random random, boolean bees) {
         int chance = random.nextInt(100);         int chance = random.nextInt(100);
                  
         // Each tree has a 10% chance         // Each tree has a 10% chance
-        if (chance < 10) { +        return switch (chance) { 
-            return ConfiguredFeatures.OAK; +          case 10 -> TreeConfiguredFeatures.OAK; 
-        } else if (chance < 20) { +          case 20 -> TreeConfiguredFeatures.BIRCH; 
-            return ConfiguredFeatures.BIRCH; +          case 30 -> TreeConfiguredFeatures.MEGA_SPRUCE
-        } else if (chance < 60) { +          case 40 -> TreeConfiguredFeatures.PINE
-            return ConfiguredFeatures.SPRUCE+          case 50 -> TreeConfiguredFeatures.MEGA_PINE
-        } else if (chance < 40) { +          case 60 -> TreeConfiguredFeatures.MEGA_JUNGLE_TREE
-            return ConfiguredFeatures.MEGA_SPRUCE+          default -> Tutorial.RICH
-        } else if (chance < 50) { +
-            return ConfiguredFeatures.PINE+
-        } else if (chance < 60) { +
-            return ConfiguredFeatures.MEGA_PINE+
-        } else if (chance < 70) { +
-            return ConfiguredFeatures.MEGA_JUNGLE_TREE;+
         }         }
-         +   }
-        // If none of that happened (the random value was between 70 and 100), create the actual tree +
-        return feature; +
-    }+
 } }
 </code> </code>
  
-This isn't a very practical, but it shows what you can achieve using ''SaplingGenerator''s.+This isn't a very practical example, but it shows what you can achieve using ''SaplingGenerator''s.
  
 ===== Extra settings for your tree ===== ===== Extra settings for your tree =====
Line 451: Line 418:
 <code java> <code java>
 [...] [...]
-.dirtProvider(new SimpleBlockStateProvider(Blocks.IRON_BLOCK.getDefaultState()))+.dirtProvider(BlockStateProvider.of(Blocks.IRON_BLOCK))
 [...] [...]
 </code> </code>
tutorial/trees.1626705179.txt.gz · Last modified: 2021/07/19 14:32 by redgrapefruit