User Tools

Site Tools


tutorial:features

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:features [2020/04/23 18:53] – [Adding a Feature to a Biome] Update feature configuration 2xsaikotutorial:features [2023/12/18 01:19] (current) – [Adding a configured feature to a biome] update code solidblock
Line 1: Line 1:
-===== Generating Features in your World ===== +===== Adding Features ===== 
-Rocks, trees, ores, and ponds are all examples of Features. They are simple generation additions to the world which generate depending on how they are configured. In this tutorial, we'll look at generating a simple stone spiral feature in our world randomly.+Rocks, trees, ores, and ponds are all examples of features. 
 +They are simple generation additions to the world which generate depending on how they are configured. 
 +In this tutorial, we'll generate a simple pillar feature.
  
-==== Creating a Feature class ==== +If you want to do something similar to vanilla (like ores or flower patches), you should first look for an existing feature you can use. 
-A simple Feature looks like this: +In that case, skip the "Creating a featurepart of this tutorial.
-<code java [enable_line_numbers="true"]> +
-public class StoneSpiralFeature extends Feature<DefaultFeatureConfig> {+
  
-    public StoneSpiralFeature(Function<Dynamic<?>, ? extends DefaultFeatureConfig> config) { +There are 3 steps that are required to add a feature to a biome. 
-        super(config); +  * Create a feature 
-    }+  * Configure a feature 
 +  * Use [[https://github.com/FabricMC/fabric/pull/1097|Biome Modification API in Fabric API]] to add your feature to biomes.
  
-    @Override 
-    public boolean generate(IWorld world, ChunkGenerator<? extends ChunkGeneratorConfig> chunkGenerator, Random random, BlockPos pos, DefaultFeatureConfig config) { 
-        BlockPos topPos = world.getTopPosition(Heightmap.Type.WORLD_SURFACE, pos); 
-        Direction offset = Direction.NORTH; 
  
-        for (int y = 1; y < 16; y++) { +==== Creating a feature ==== 
-            offset offset.rotateYClockwise(); +Let's create a feature that spawns a 1x1 pillar of blocks on the ground. As an added challenge, let's also make it configurable, so we can change the height and material of the pillars. 
-            world.setBlockState(topPos.up(y).offset(offset), Blocks.STONE.getDefaultState(), 3);+ 
 +We'll first create a new class for our feature. This is where the generation logic will go. 
 + 
 +<yarncode java> 
 +public class ExampleFeature extends class_3031<ExampleFeatureConfig>
 +  public ExampleFeature(Codec<FeatureConfig> configCodec) { 
 +    super(configCodec); 
 +  } 
 + 
 +  // this method is what is called when the game tries to generate the feature. it is where the actual blocks get placed into the world. 
 +  @Override 
 +  public boolean method_13151(class_5821<ExampleFeatureConfig> context) { 
 +        class_5281 world = context.method_34383(); 
 +        // the origin is the place where the game starts trying to place the feature 
 +        class_2338 origin = context.getOrigin(); 
 +        // we won't use the random here, but we could if we wanted to 
 +        class_5819 random = context.method_33654(); 
 +        ExampleFeatureConfig config = context.method_33656(); 
 + 
 +        // don't worry about where these come from-- we'll implement these methods soon 
 +        int number = config.number(); 
 +        class_2960 blockId = config.blockId(); 
 + 
 +        class_2680 blockState = class_7923.field_41175.get(blockId).method_9564(); 
 +        // ensure the ID is okay 
 +        if (blockState == null) throw new IllegalStateException(blockId + " could not be parsed to a valid block identifier!"); 
 + 
 +        // find the surface of the world 
 +        class_2338 testPos = new class_2338(origin); 
 +        for (int y = 0; y < world.method_31605(); y++) { 
 +            testPos testPos.method_10086(); 
 +            // the tag name is dirt, but includes grass, mud, podzol, etc. 
 +            if (world.method_8320(testPos).isIn(class_3481.field_29822)) { 
 +                if (world.method_8320(testPos.method_10086()).isOf(class_2246.field_10124)) { 
 +                    for (int i = 0; i < number; i++
 +                        // create a simple pillar of blocks 
 +                        world.method_8501(testPosblockState, 0x10); 
 +                        testPos = testPos.method_10086(); 
 + 
 +                        // ensure we don't try to place blocks outside the world 
 +                        if (testPos.getY() >= world.method_8624()) break; 
 +                    } 
 +                    return true; 
 +                } 
 +            }
         }         }
 +       // the game couldn't find a place to put the pillar
 +        return false;
 +    }
 +}
  
-        return true;+</yarncode> 
 +Now, we need to implement that ''ExampleFeatureConfig'' record. This is where we define the variables that we use in our ''Feature''. This config is essentially a wrapper for the parameters we want to pass to our feature. Note: while this tutorial only uses integers and BlockStates, other useful objects in the game also have codecs that can give you more control over how your feature generates. ''BlockStateProvider''s are a good example of this. 
 +<yarncode java> 
 +public record ExampleFeatureConfig(int number, Identifier blockId) implements FeatureConfig { 
 +    public static final Codec<ExampleFeatureConfig> CODEC = RecordCodecBuilder.create( 
 +        instance -> instance.group( 
 +                        // you can add as many of these as you want, one for each parameter 
 +                        Codecs.POSITIVE_INT.fieldOf("number").forGetter(ExampleFeatureConfig::number), 
 +                        Identifier.CODEC.fieldOf("blockID").forGetter(ExampleFeatureConfig::blockId)) 
 +                .apply(instance, ExampleFeatureConfig::new)); 
 +
 +</yarncode> 
 + 
 +Now that we have our config defined, the errors in our feature class will resolve. But we're not done yet-- now we need to add our feature to the game. Features can be registered like most other content in the game, and there aren't any special builders or mechanics you'll have to worry about. 
 + 
 +<code java> 
 +public class ExampleMod implements ModInitializer { 
 +    public static final Identifier EXAMPLE_FEATURE_ID = new Identifier("tutorial", "example_feature"); 
 +    public static final ExampleFeature EXAMPLE_FEATURE = new ExampleFeature(ExampleFeatureConfig.CODEC); 
 + 
 +    @Override 
 +    public void onInitialize() { 
 +        Registry.register(Registries.FEATURE, EXAMPLE_FEATURE_ID, EXAMPLE_FEATURE);
     }     }
 } }
 </code> </code>
  
-The constructor takes in a ''Function<Dynamic<? extends DefaultFeatureConfig>>''which is a factory for data fixer config instancesYou can pass in ''DefaultFeatureConfig::deserialize'' for default config featureseither directly in the super call or when you instantiate the feature.+If you plan to configure and use your feature using datapacksyou can stop hereTo implement it in coderead on.
  
-`generate` is called when the chunk decides to generate the feature. If the feature is configured to spawn every chunk, this would be called for each chunk being generated as well. In the case of the feature being configured to spawn at a certain rate per biome, `generate` would only be called in instances where the world wants to spawn the structure+==== Configuring a feature ==== 
 +We need to give a configuration to a feature, before we can add it to biomesMake sure to register configured features as well as featuresHere is where we specify the parameters of our feature. We'll generate 10-block-high pillars out of netherite. In fact, we could register as many of these ''ConfiguredFeatures'' as we wanted, just changing the config parameters each time.
  
-In our implementation, we'll build a simple 16-block tall spiral of stone starting at the top block in the world:+<yarncode java> 
 +public class FeatureExampleMod implements ModInitializer {
  
-<code java> +    public static final Identifier EXAMPLE_FEATURE_ID = new Identifier("tutorial""example_feature"); 
-@Override +    public static final ExampleFeature EXAMPLE_FEATURE new ExampleFeature(ExampleFeatureConfig.CODEC);
-public boolean generate(IWorld worldChunkGenerator<? extends ChunkGeneratorConfig> chunkGenerator, Random random, BlockPos pos, DefaultFeatureConfig config{ +
-    BlockPos topPos world.getTopPosition(Heightmap.Type.WORLD_SURFACE, pos)+
-    Direction offset = Direction.NORTH;+
  
-    for (int y 1; y 16; y++) { +    public static final ConfiguredFeature<ExampleFeatureConfig, ExampleFeature> EXAMPLE_FEATURE_CONFIGURED new ConfiguredFeature<>
-        offset = offset.rotateYClockwise(); +                    EXAMPLE_FEATURE, 
-        world.setBlockState(topPos.up(y).offset(offset), Blocks.STONE.getDefaultState(), 3); +                    new ExampleFeatureConfig(10, new Identifier("minecraft", "netherite_block"))); 
-    }+    );
  
-    return true;+ 
 +    @Override 
 +    public void onInitialize() { 
 +        Registry.register(Registry.FEATURE, EXAMPLE_FEATURE_ID, EXAMPLE_FEATURE); 
 +        Registry.register(BuiltinRegistries.CONFIGURED_FEATURE, EXAMPLE_FEATURE_ID, EXAMPLE_FEATURE_CONFIGURED); 
 +    }
 } }
-</code>+</yarncode
  
-==== Registering a Feature ==== +FIXME The last line has to be updated
-Features can be registered like most other content in the game, and there aren't any special builders or mechanics you'll have to worry about.  +
-<code java> +
-private static final Feature<DefaultFeatureConfig> LAVA_HOLE = Registry.register( +
- Registry.FEATURE, +
- new Identifier("tutorial", "stone_spiral"), +
- new StoneSpiralFeature(DefaultFeatureConfig::deserialize) +
-); +
-</code>+
  
-==== Adding a Feature to a Biome ==== +==== Adding a configured feature to a biome ==== 
-Biome has method called ''addFeature'', which is used to add Features to the biome's generation processYou can view more detailed usage of this method inside each Biome class (such as ''ForestBiome'' or ''SavannaBiome'').+We use the Biome Modification API. The final stage of feature configuration is creating a ''PlacedFeature''This class is a replacement for ''Decorators'' on ''ConfiguredFeatures''Don't forget to register it as well!
  
-We can iterate over ''Registry.BIOME'' to add our Feature to every Biome. +Our final initializer class looks like this: 
-<code java> +<yarncode java> 
-Registry.BIOME.forEach(biome -> biome.addFeature+public class ExampleMod implements ModInitializer { 
-        GenerationStep.Feature.RAW_GENERATION+ 
- LAVA_HOLE.configure(new DefaultFeatureConfig()) +    public static final Identifier EXAMPLE_FEATURE_ID = new Identifier("tutorial", "example_feature"); 
- .createDecoratedFeature(Decorator.CHANCE_HEIGHTMAP.configure(new ChanceDecoratorConfig(100))); +    public static final ExampleFeature EXAMPLE_FEATURE = new ExampleFeature(ExampleFeatureConfig.CODEC); 
-)); +    public static final ConfiguredFeature<ExampleFeatureConfig, ExampleFeature> EXAMPLE_FEATURE_CONFIGURED = new ConfiguredFeature<>
-</code>+                    EXAMPLE_FEATURE
 +                    new ExampleFeatureConfig(10, new Identifier("minecraft", "netherite_block")) 
 +    ); 
 +    // our PlacedFeaturethis is what gets passed to the biome modification API to add to the biome. 
 +    public static PlacedFeature EXAMPLE_FEATURE_PLACED = new PlacedFeature( 
 +            RegistryEntry.of( 
 +                    EXAMPLE_FEATURE_CONFIGURED 
 +                  //  the SquarePlacementModifier makes the feature generate a cluster of pillars each time 
 +            ), List.of(SquarePlacementModifier.of()) 
 +    ); 
 + 
 +    @Override 
 +    public void onInitialize(
 +        // register the features 
 +        Registry.register(class_7923.field_41144, EXAMPLE_FEATURE_ID, EXAMPLE_FEATURE); 
 + 
 +        // add it to overworld biomes using FAPI 
 +        BiomeModifications.addFeature( 
 +                BiomeSelectors.foundInOverworld(), 
 +                // the feature is to be added while flowers and trees are being generated 
 +                GenerationStep.Feature.VEGETAL_DECORATION, 
 +                RegistryKey.of(RegistryKeys.PLACED_FEATURE, EXAMPLE_FEATURE_ID)); 
 +    } 
 +} 
 +</yarncode> 
 + 
 +The first argument of ''addFeature'' determines what biomes the structure is generated in. 
 + 
 +The second argument helps determine when the structure is generated. 
 +For above-ground houses you may go with ''SURFACE_STRUCTURES'', and for caves, you might go with ''RAW_GENERATION''.
  
-The first argument of ''addFeature'' helps determine when the structure is generatedFor above-ground houses you may go with ''SURFACE_STRUCTURES'', and for caves, you might go with ''RAW_GENERATION''.+For more information, the code used in this tutorial is available [[https://github.com/itsmiir/wiki-example/tree/1.19/src/main/java/com/miir/example/feature|on Github]].
  
-The second argument of ''addFeature'' is a ConfiguredFeature, which you can create through ''Biome.configureFeature''. The latter takes in an instance of your feature, an instance of your feature's config class, a decorator, and a decorator config. 
  
-The Decorator represents how the world chooses to place your Feature. ''CHANCE_HEIGHTMAP'' works by looking at a heightmap, whereas ''NOISE_HEIGHTMAP_32'' works with noise. To choose the correct Decorator, check out vanilla Features with a similar style to your own. The decorator config branches off this; in the case of ''CHANCE_HEIGHTMAP'', you would pass in an instance of ''ChanceHeightmapDecorator'' 
  
-=== Results === 
-{{https://i.imgur.com/Kr59o0B.png}} 
tutorial/features.1587668001.txt.gz · Last modified: 2020/04/23 18:53 by 2xsaiko