tutorial:features
Differences
This shows you the differences between two versions of the page.
Both sides previous revisionPrevious revisionNext revision | Previous revision | ||
tutorial:features [2021/07/29 09:59] – Fixed a parameter rename mschae23 | tutorial:features [2024/10/28 17:58] (current) – Fix new identifier vs Identifier.of cassiancc | ||
---|---|---|---|
Line 1: | Line 1: | ||
- | ===== Adding Features | + | ===== Adding Features ===== |
Rocks, trees, ores, and ponds are all examples of 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. | They are simple generation additions to the world which generate depending on how they are configured. | ||
- | In this tutorial, we' | + | In this tutorial, we' |
- | If you want to do something similar to vanilla (like ores), you should first look for an existing feature you can use. | + | 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. |
- | In that case, skip the "Create | + | In that case, skip the "Creating |
There are 3 steps that are required to add a feature to a biome. | There are 3 steps that are required to add a feature to a biome. | ||
Line 12: | Line 12: | ||
* Use [[https:// | * Use [[https:// | ||
- | Note that the Biome Modification API is marked as experimental. If the API doesn' | ||
==== Creating a feature ==== | ==== Creating a feature ==== | ||
- | A simple Feature looks like this: | + | 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, |
- | <code java> | + | We'll first create a new class for our feature. This is where the generation logic will go. |
- | public class StoneSpiralFeature | + | |
- | public | + | <yarncode |
+ | public class ExampleFeature | ||
+ | public | ||
super(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 | @Override | ||
- | public boolean | + | public boolean |
- | | + | |
- | | + | // 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(); | ||
+ | | ||
- | for (int y = 0; y < 15; y++) { | + | // don't worry about where these come from-- we'll implement these methods soon |
- | offset = offset.rotateYClockwise(); | + | |
- | | + | |
- | } | + | |
- | return true; | + | 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!" |
- | </code> | + | |
- | In our implementation, we'll build a simple | + | // find the surface of the world |
+ | class_2338 testPos = new class_2338(origin); | ||
+ | for (int y = 0; y < world.method_31605(); | ||
+ | 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 | ||
+ | | ||
+ | testPos = testPos.method_10086(); | ||
- | The '' | + | // ensure we don't try to place blocks outside the world |
- | You can pass in '' | + | if (testPos.getY() |
+ | } | ||
+ | return true; | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | // the game couldn't find a place to put the pillar | ||
+ | return false; | ||
+ | } | ||
+ | } | ||
- | '' | + | </ |
- | If the feature | + | Now, we need to implement that '' |
- | In the case of the feature being configured to spawn at a certain rate per biome, | + | <yarncode |
- | + | public record | |
- | Our feature | + | public static final Codec<ExampleFeatureConfig> CODEC = RecordCodecBuilder.create( |
- | as that allows you to reuse it for different things, and also lets players change them through data packs if they want. | + | |
- | A simple config for our feature could look like this: | + | // you can add as many of these as you want, one for each parameter |
- | + | Codecs.POSITIVE_INT.fieldOf(" | |
- | <code java> | + | |
- | public record | + | .apply(instance, |
- | public static final Codec<SpiralFeatureConfig> CODEC = RecordCodecBuilder.create(instance -> instance.group( | + | |
- | | + | |
- | | + | |
- | ).apply(instance, | + | |
} | } | ||
- | </code> | + | </yarncode> |
- | Note that we use an '' | + | Now that we have our config defined, the errors in our feature |
- | This is because they are more powerful to use. For example, you could configure | + | |
- | + | ||
- | Now, let's make our feature | + | |
<code java> | <code java> | ||
- | public class SpiralFeature extends Feature< | + | public class ExampleMod implements ModInitializer |
- | public | + | public |
- | | + | |
- | } | + | |
- | | + | |
- | public | + | public |
- | | + | |
- | SpiralFeatureConfig config = context.getConfig(); | + | |
- | + | ||
- | Direction offset = Direction.NORTH; | + | |
- | int height = config.height().get(context.getRandom()); | + | |
- | + | ||
- | for (int y = 0; y < height; y++) { | + | |
- | offset = offset.rotateYClockwise(); | + | |
- | BlockPos blockPos = pos.up(y).offset(offset); | + | |
- | + | ||
- | context.getWorld().setBlockState(blockPos, config.block().getBlockState(context.getRandom(), | + | |
} | } | ||
- | |||
- | return true; | ||
- | } | ||
} | } | ||
</ | </ | ||
- | Features | + | If you plan to configure and use your feature using datapacks, you can stop here. To implement it in code, read on. |
- | <code java> | + | ==== Configuring a feature ==== |
- | public class ExampleMod implements ModInitializer { | + | We need to give a configuration to a feature, before we can add it to biomes. Make sure to register configured features as well as features. Here 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 '' |
- | private static final Feature< | + | |
- | @Override | + | < |
- | public | + | public |
- | Registry.register(Registry.FEATURE, | + | |
- | } | + | |
- | } | + | |
- | </ | + | |
- | ==== Configuring a feature ==== | + | public static final Identifier EXAMPLE_FEATURE_ID |
- | We need to give a configuration to a feature, before we can add it to biomes. Make sure to register configured features as well as features. | + | public static final ExampleFeature EXAMPLE_FEATURE |
- | <code java> | + | |
- | public class ExampleMod implements ModInitializer { | + | EXAMPLE_FEATURE, |
- | | + | |
- | | + | ); |
- | .spreadHorizontally() | + | |
- | .applyChance(5); | + | |
- | | + | |
- | public void onInitialize() { | + | |
- | [...] | + | public void onInitialize() { |
- | + | | |
- | RegistryKey< | + | Registry.register(BuiltinRegistries.CONFIGURED_FEATURE, |
- | new Identifier(" | + | } |
- | Registry.register(BuiltinRegistries.CONFIGURED_FEATURE, | + | |
- | } | + | |
} | } | ||
- | </code> | + | </yarncode> |
- | Note that the height of the spiral (15 blocks) and the block to place (stone) is now configurable here. | + | FIXME The last line has to be updated |
- | + | ||
- | The Decorator represents how and where the world will place your feature. | + | |
- | To choose the correct Decorator, check out vanilla features with a similar style to your own. | + | |
==== Adding a configured feature to a biome ==== | ==== Adding a configured feature to a biome ==== | ||
- | We use the Biome Modification API. | + | We use the Biome Modification API. The final stage of feature configuration is creating a '' |
- | <code java> | + | Our final initializer class looks like this: |
+ | <yarncode | ||
public class ExampleMod implements ModInitializer { | public class ExampleMod implements ModInitializer { | ||
- | [...] | ||
- | | + | public static final Identifier EXAMPLE_FEATURE_ID = Identifier.of(" |
- | public void onInitialize() { | + | public static final ExampleFeature EXAMPLE_FEATURE = new ExampleFeature(ExampleFeatureConfig.CODEC); |
- | [...] | + | public static final ConfiguredFeature< |
- | BiomeModifications.addFeature(BiomeSelectors.all(), GenerationStep.Feature.UNDERGROUND_ORES, stoneSpiral); | + | EXAMPLE_FEATURE, |
- | } | + | new ExampleFeatureConfig(10, |
+ | ); | ||
+ | // our PlacedFeature. this 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()) | ||
+ | ); | ||
+ | |||
+ | | ||
+ | public void onInitialize() { | ||
+ | // register the features | ||
+ | Registry.register(class_7923.field_41144, | ||
+ | |||
+ | // add it to overworld biomes using FAPI | ||
+ | | ||
+ | | ||
+ | // the feature is to be added while flowers and trees are being generated | ||
+ | | ||
+ | RegistryKey.of(RegistryKeys.PLACED_FEATURE, | ||
+ | } | ||
} | } | ||
- | </code> | + | </yarncode> |
The first argument of '' | The first argument of '' | ||
Line 150: | Line 161: | ||
For above-ground houses you may go with '' | For above-ground houses you may go with '' | ||
- | === Result === | + | For more information, |
- | {{https://i.imgur.com/Kr59o0B.png}} | + | |
tutorial/features.1627552748.txt.gz · Last modified: 2021/07/29 09:59 by mschae23