===== Custom Chunk Generators =====
''ChunkGenerator''s are the mechanisms by which the game generates the world. They handle terrain shaping, surface building, and biome placement.
Vanilla Minecraft's ''NoiseChunkGenerator'' is extremely powerful and configurable. Using ''DensityFunction''s and ''SurfaceRule''s, the possibilities for customization are almost endless. **If you're wondering "can I do x with ''NoiseChunkGenerator'' and datapacks", the answer is probably yes.** Most situations do not call for a custom ''ChunkGenerator'', and since creating one requires a lot of boilerplate code, **it is highly recommended that you avoid making custom ''ChunkGenerator''s when possible.**
As a rule of thumb, you should only implement a ''ChunkGenerator'' if you need to create a world that is **not** based on random noise. Examples of this might include a world generated from an external voxel engine, or a custom map like SethBling's Skygrid. If you're not sure whether a ''ChunkGenerator'' is right for you, ask someone in the Discord.
Ready? Let's get started.
==== Creating a ChunkGenerator ====
The first step in creating a chunk generator is making a new ''ChunkGenerator'' class. Extend vanilla's ''ChunkGenerator'', and let your IDE generate all the required function stubs. Here's an explanation of everything you likely need to worry about:
public class ExampleChunkGenerator extends ChunkGenerator {
/* this is a very important field, we will come back to the codec later */
public static final Codec CODEC;
/* you can add whatever fields you want to this constructor, as long as they're added to the codec as well */
public ExampleChunkGenerator() {
}
/* the method that creates non-noise caves (i.e., all the caves we had before the caves and cliffs update) */
@Override
public void carve(ChunkRegion chunkRegion, long seed, NoiseConfig noiseConfig, BiomeAccess biomeAccess, StructureAccessor structureAccessor, Chunk chunk, GenerationStep.Carver carverStep) {
}
/* the method that places grass, dirt, and other things on top of the world, as well as handling the bedrock and deepslate layers,
as well as a few other miscellaneous things. without this method, your world is just a blank stone (or whatever your default block is) canvas (plus any ores, etc) */
@Override
public void buildSurface(ChunkRegion region, StructureAccessor structures, NoiseConfig noiseConfig, Chunk chunk) {
}
/* the method that paints biomes on top of the already-generated terrain. if you leave this method alone, the entire world will be a River biome.
note that this does not mean that the world will all be water; but drowned and salmon will spawn. */
@Override
public CompletableFuture populateBiomes(Registry biomeRegistry, Executor executor, NoiseConfig noiseConfig, Blender blender, StructureAccessor structureAccessor, Chunk chunk) {
return super.populateBiomes(biomeRegistry, executor, noiseConfig, blender, structureAccessor, chunk);
}
/* this method spawns entities in the world */
@Override
public void populateEntities(ChunkRegion region) {
}
/* the distance between the highest and lowest points in the world. in vanilla, this is 384 (64+320) */
@Override
public int getWorldHeight() {
return 0;
}
/* this method builds the shape of the terrain. it places stone everywhere, which will later be overwritten with grass, terracotta, snow, sand, etc
by the buildSurface method. it also is responsible for putting the water in oceans. it returns a CompletableFuture-- you'll likely want this to be delegated to worker threads. */
@Override
public CompletableFuture populateNoise(Executor executor, Blender blender, NoiseConfig noiseConfig, StructureAccessor structureAccessor, Chunk chunk) {
}
@Override
public int getSeaLevel() {
return 0;
}
/* the lowest value that blocks can be placed in the world. in a vanilla world, this is -64. */
@Override
public int getMinimumY() {
return 0;
}
/* this method returns the height of the terrain at a given coordinate. it's used for structure generation */
@Override
public int getHeight(int x, int z, Heightmap.Type heightmap, HeightLimitView world, NoiseConfig noiseConfig) {
return 0;
}
/* this method returns a "core sample" of the world at a given coordinate. it's used for structure generation */
@Override
public VerticalBlockSample getColumnSample(int x, int z, HeightLimitView world, NoiseConfig noiseConfig) {
}
/* this method adds text to the f3 menu. for NoiseChunkGenerator, it's the NoiseRouter line */
@Override
public void getDebugHudText(List text, NoiseConfig noiseConfig, BlockPos pos) {
}
@Override
protected Codec extends ChunkGenerator> getCodec() {
return CODEC;
}
}
Pretty much all of these methods need to be overridden for your generator to work properly. You can copy some of them from ''NoiseChunkGenerator''. It is especially recommended that you implement the private ''populateNoise'' method from ''NoiseChunkGenerator'' as well, and just copy over the public method. The public method from ''NoiseChunkGenerator'' just delegates the actual generation to worker threads, which each run the private method. This is an easy way to get parallelized generation-- **the chunk generator is not multithreaded by default**!
==== Registering and Codecs ====
Like other features in the game, ''ChunkGenerator''s have to be registered. However, you don't pass the registry a static instance-- instead, you give it a ''Codec'' instance. You can put whatever you want in the codec-- Mojang provides serialization codecs for a lot of useful objects, including entire registries. The easiest way to do this is via ''RecordCodecBuilder''. Here's an example codec:
public static final Codec CODEC = RecordCodecBuilder.create(instance ->
instance.group(
BiomeSource.CODEC.fieldOf("biome_source").forGetter(ExampleChunkGenerator::getBiomeSource),
Codec.INT.fieldOf("sea_level").forGetter(ExampleChunkGenerator::getSeaLevel),
Codec.INT.fieldOf("world_height").forGetter(ExampleChunkGenerator::getWorldHeight),
Identifier.fieldOf("custom_block").forGetter(ExampleChunkGenerator::getCustomBlockID)
).apply(instance, ExampleChunkGenerator::new));
Each line in the codec corresponds to a different field in your chunk generator that should be configurable. In this example, I pass a ''BiomeSource'' and a few ''int''s. Most objects you will want to add into your chunk generator will have a ''Codec'' defined for them. Primitives have theirs defined in the ''Codec'' class, and other classes (for example, ''BiomeSource'' or ''Identifier'') have theirs defined in their classes.
To use the fields properly, you have to add them, along with getters, into your class:
private final BiomeSource biomeSource;
public BiomeSource getBiomeSource() {
return this.biomeSource;
}
private final int seaLevel;
public int getSeaLevel() {
return this.seaLevel;
}
// and the same for the other fields
and then, create a constructor for your generator that uses each of those fields **in the same order that you listed them in the codec**:
public ExampleChunkGenerator(BiomeSource biomeSource, int seaLevel, int worldHeight, Identifier customBlockID) {
super(biomeSource);
this.seaLevel = seaLevel;
this.worldHeight = worldHeight;
this.customBlock = Registries.BLOCK.getOrThrow(RegistryKey.of(RegistryKeys.BLOCK, defaultBlock)).getDefaultState();
this.customBlockID = customBlockID; // this line is included because we need to have a getter for the ID specifically
}
Then, all that's left is to register your chunk generator. Put the following line into your mod initializer ''onInitialize'' method:
Registry.register(Registry.CHUNK_GENERATOR, new Identifier("wiki-example", "example"), ExampleChunkGenerator.CODEC);
Now, you can create json files to use your new chunk generator:
// resources/data/wiki-example/dimension/example.json
{
"type": "minecraft:overworld",
"generator": {
"type": "wiki-example:example",
"custom_block": "minecraft:dirt",
"sea_level": 0,
"world_height": 384,
"biome_source": // etc.
And you're done! If you want to use your new generator, it might be helpful to wrap it with a [[tutorial:world_presets|WorldPreset]].