tutorial:features
Differences
This shows you the differences between two versions of the page.
Both sides previous revisionPrevious revisionNext revision | Previous revisionNext revisionBoth sides next revision | ||
tutorial:features [2021/07/29 09:59] – Fixed a parameter rename mschae23 | tutorial:features [2023/12/18 01:12] – [Creating a feature] updat ecode solidblock | ||
---|---|---|---|
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 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 | + | |
- | | + | |
- | | + | |
- | ).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 Feature< |
+ | |||
+ | public static ConfiguredFeature< | ||
+ | | ||
+ | new ExampleFeatureConfig(10, | ||
+ | ); | ||
- | <code java> | ||
- | public class ExampleMod implements ModInitializer { | ||
- | public static final ConfiguredFeature<?, | ||
- | .decorate(Decorator.HEIGHTMAP.configure(new HeightmapDecoratorConfig(Heightmap.Type.OCEAN_FLOOR_WG))) | ||
- | .spreadHorizontally() | ||
- | .applyChance(5); | ||
- | | + | |
- | public void onInitialize() { | + | public void onInitialize() { |
- | [...] | + | |
- | + | Registry.register(BuiltinRegistries.CONFIGURED_FEATURE, | |
- | RegistryKey< | + | } |
- | new Identifier(" | + | |
- | Registry.register(BuiltinRegistries.CONFIGURED_FEATURE, | + | |
- | } | + | |
} | } | ||
- | </code> | + | </yarncode> |
- | Note that the height | + | ==== Adding a configured feature to a biome ==== |
+ | We use the Biome Modification API. The final stage of feature configuration | ||
- | The Decorator represents how and where the world will place your feature. | + | Our final initializer class looks like this: |
- | To choose the correct Decorator, check out vanilla features with a similar style to your own. | + | < |
+ | public class FeatureExampleMod implements ModInitializer { | ||
- | ==== Adding a configured feature | + | public static final Identifier EXAMPLE_FEATURE_ID |
- | We use the Biome Modification API. | + | public static Feature< |
+ | public static ConfiguredFeature< | ||
+ | (ExampleFeature) EXAMPLE_FEATURE, | ||
+ | new ExampleFeatureConfig(10, | ||
+ | ); | ||
+ | // our PlacedFeature. this is what gets passed | ||
+ | public static PlacedFeature EXAMPLE_FEATURE_PLACED | ||
+ | | ||
+ | EXAMPLE_FEATURE_CONFIGURED | ||
+ | // | ||
+ | ), List.of(SquarePlacementModifier.of()) | ||
+ | ); | ||
- | <code java> | + | @Override |
- | public | + | public |
- | [...] | + | // register the features |
+ | Registry.register(class_7923.field_41144, | ||
+ | Registry.register(BuiltinRegistries.CONFIGURED_FEATURE, | ||
+ | Registry.register(BuiltinRegistries.PLACED_FEATURE, | ||
- | @Override | + | // add it to overworld biomes using FAPI |
- | | + | BiomeModifications.addFeature( |
- | [...] | + | |
- | | + | // the feature is to be added while flowers and trees are being generated |
- | } | + | |
+ | RegistryKey.of(Registry.PLACED_FEATURE_KEY, | ||
+ | } | ||
} | } | ||
- | </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.txt · Last modified: 2023/12/18 01:19 by solidblock