How to: Create custom world generators

Discussion in 'Resources' started by MatorKaleen, Jun 4, 2012.

Thread Status:
Not open for further replies.
  1. Offline

    MatorKaleen

    World generation has always been a very important part of Minecraft. Without, Minecraft would never have evolved as it did.
    For Bukkit developers it also offers new possibilities: from simple plot generators up to massive worlds (think of the Aether, even if works with Forge, but who cares ;)).

    So, now I want to give you a little overview, how world generation in bukkit is actually done.

    Table of contents:
    1. Prerequisites
    2. What is a chunk?
    3. Coding of the plugin class
    4. Coding of the generator class
    5. Use it with bukkit
    6. Summary
    Prerequisites

    Of course you need to know, how to code with Java. And i wont explain, how to build a plugin.
    Even some mathematic skills are an advantage, especially spatial awareness.

    What is a chunk?

    Chunks are small parts of the world, that a generated, if a players get close to it.
    A Minecraft chunk has a size of 16x16x256. To save memory, the chunk itself is split into 16 16x16x16 parts. The idea behind this is, that chunks in which is no block (or air) will be set to NULL.

    The complete chunk is represented by this array:
    Code:JAVA
    1. byte[][] result = new byte[world.getMaxHeight() / 16][];

    Before you can set blocks, you have to initialize the chunk part in which the block is:
    Code:JAVA
    1. result[partY] = new byte[4096];

    partY is the position of the part in the chunk from bottom to top.

    As you can see, each part consists of 12 bits. The first 4 Bits are the y coordinate, the 4 in the middle the z coordinate and the last 4 the x coordinate. For example, we want to set a stone block at the position P(12|8|5):
    To set the block, you just have to set the Material ID into the array:
    Code:JAVA
    1. result[2140] = (byte)Material.STONE.getId();


    Coding of the plugin class

    To use the generator in your plugin, you have to override a function in your main class:
    Code:JAVA
    1. import org.bukkit.generator.ChunkGenerator;
    2. import org.bukkit.plugin.java.JavaPlugin;
    3.  
    4. public class YourWorldGeneratorPlugin extends JavaPlugin
    5. {
    6. public ChunkGenerator getDefaultWorldGenerator(String worldName, String id)
    7. {
    8. return new YourGenerator();
    9. }
    10. }

    For more infos about getDefaultWorldGenerator(String worldName, String id) look here.

    Coding of the generator class

    For today, we will code just a simple flat world generator.
    Take a look into the docu: click!
    You can see, to generate a very simple world, you need a class that extends
    Code:JAVA
    1. org.bukkit.generator.ChunkGenerator

    and has the function
    Code:JAVA
    1. byte[][] generateBlockSections(World world, Random random, int x, int z, BiomeGrid biomes)


    Your basic class structure will look like this:
    Code:JAVA
    1. import java.util.Random;
    2.  
    3. import org.bukkit.World;
    4. import org.bukkit.generator.ChunkGenerator;
    5.  
    6. public class YourGenerator extends ChunkGenerator {
    7. public byte[][] generateBlockSections(World world, Random random, int chunkX, int chunkZ, BiomeGrid biomeGrid)
    8. {
    9. byte[][] result = new byte[world.getMaxHeight() / 16][]; //world height / chunk part height (=16, look above)
    10. return result;
    11. }
    12. }

    With that basis, you can generate everthing (even thinks like the Mandelbulb, but thats another topic).

    Now let us add some blocks. To make it easier, we add a simple method, to set a block in the chunk:
    Code:JAVA
    1. void setBlock(byte[][] result, int x, int y, int z, byte blkid) {
    2. // is this chunk part already initialized?
    3. if (result[y >> 4] == null) {
    4. // Initialize the chunk part
    5. result[y >> 4] = new byte[4096];
    6. }
    7. // set the block (look above, how this is done)
    8. result[y >> 4][((y & 0xF) << 8) | (z << 4) | x] = blkid;
    9. }


    From here it's very easy:
    First we generate a layer of bedrock at y=0:
    Code:JAVA
    1. for(x = 0; x < 16; x++)
    2. {
    3. for(z = 0; z < 16; z++)
    4. {
    5. setBlock(result, x, 0, z, (byte)Material.BEDROCK.getId());
    6. }
    7. }


    Then you generate two layers of dirt:
    Code:JAVA
    1. for(x = 0; x < 16; x++)
    2. {
    3. for(y = 1; y <= 2; y++)
    4. {
    5. for (z = 0; z < 16; z++)
    6. {
    7. setBlock(result, x, y, z, (byte)Material.DIRT.getId());
    8. }
    9. }
    10. }


    And at last you create a layer of grass:
    Code:JAVA
    1. for(x = 0; x < 16; x++)
    2. {
    3. for(z = 0; z < 16; z++)
    4. {
    5. setBlock(result, x, 3, z, (byte)Material.GRASS.getId());
    6. }
    7. }


    There is an advanced version of this code attached.

    Use it with bukkit

    Now we have a generator written. But there are a few things to do:

    First you have to make an entry in your plugin.yml
    Code:
    load: STARTUP
    If you don't add this line, bukkit will generate the world and THEN load your plugin, so the plugin has to be loaded on startup.
    Now you're ready to export it as jar file and copy it into your plugin folder.

    As last step open your bukkit.yml and add this lines of code:
    Code:
    worlds:
      YourWorldNameHere:
        generator: YourWorldGeneratorPlugin
    You have only to modify your level name in server.properties and you're ready to start bukkit.

    Summary

    After reading this, you should be able, to create own worlds with enough imagination. But there is more possible. Just have a look at BlockPopulator and PerlinNoiseGenerator in the Bukkit documentation.
    I have one tip for you: Never put very heavy load code for the first generateBlockSections() call. On the first server start its ok, but later it will after every restart cause a huge laaag, once a player enters a new area.

    If you find any mistakes, please report them to me.

    Thanks for reading!

    Mator

    BTW: As of 27.07.13 im currently planing to make a second tutorial about the generation itself. I just want to code an example project.
     
    Konato_K, Xtreme727, Dzikoysk and 5 others like this.
  2. Offline

    EDawg878

    I'm reposting a question from the PlotMe developer zachbora:
    Do you think you could explain how to make a generator that supports block values?
     
  3. Offline

    MatorKaleen

    I'll try it. But first, I will finish this tutorial here (maybe today ;), i hadn't much time in the last few days) and then i will look at that problem.
     
  4. you can use the "ChunkMaker" class of the plugin "MultiWorld" to make those chunks whit cuboids and other stuff, whtout to code mutch by your own
     
  5. Offline

    sablednah

    I cant see it!
     
  6. Offline

    MatorKaleen

    Yes, I just have to comment it, but at the moment, I have to concentrate at school (OMG Reallife! :D)
     
  7. Offline

    pivotgamer84

    Could you explain how to make biomes generate?

    I want to be able to specify that a specific biome generates at the spawn, but everything else has an ocean biome?

    An example of this would be a survival island generator, where the island is unique to every seed by using biomes instead of specific blocks and block locations.
     
  8. Offline

    MatorKaleen

    Example code uploaded. If you find errors, please report it to me ;)
     
  9. I would also like to learn how to do this.
     
  10. Offline

    MatorKaleen

    Hm... Thats an intersting question. There are two possibilities: You handle it with the populator (The easy way), or the complex way: You create a delayed task. But i have to research this. Atm I'm working at another project. When I finished this, I will maybe make a tutorial about this.
     
  11. Offline

    sd5

    MatorKaleen: Your code is too difficult. Instead of the byte[][] you should use a byte[] with size 65536 (16 * 16 * 256) called blocks and add a method:
    Code:
    private int convert(int x, int y, int z) {
        return (x * 16 + z) * 256 + y;
    }
    To create a block simply do now:
    Code:
    blocks[convert(x, y, z)] = (byte) Material.STONE.getId();
    Finally just return blocks.
     
    JWhy likes this.
  12. Offline

    MatorKaleen

    sd5
    The way, you describe, is
    1. Deprecated (look here)
    2. Much more memory intensive: a byte[65536] is larger than a byte[16][], because you needn't to initialise every 16 arrays in the second dimension (it's hard to explain in english for me, sry)

    Maybe this is more difficult as your one, but I think, this is the more effecient way.

    Mator
     
  13. Offline

    ZachBora

    Very interesting. Wish I had known about this before I did my plot generator.

    I will change the code and see if it works. It's too bad that chunk gen still doesn't support block data.
     
  14. Offline

    Aza24

    Looks good! Just one tip, use the getMaxHeight(); method instead of specifying the height.

    Code:Java
    1. public byte[][] generateBlockSections(World world, Random random, int chunkX, int chunkZ, BiomeGrid biomeGrid) {
    2. byte[][] result = new byte[world.getMaxHeight() / 16][];
    3. return result;
    4. }
     
  15. Offline

    MatorKaleen

    [quote uid=90565375 name="Aza24" post=1327402]Looks good! Just one tip, use the getMaxHeight(); method instead of specifying the height.

    Code:Java
    1. public byte[][] generateBlockSections(World world, Random random, int chunkX, int chunkZ, BiomeGrid biomeGrid) {
    2. byte[][] result = new byte[world.getMaxHeight() / 16][];
    3. return result;
    4. }
    [/quote]

    Thanks for the tip, I will update the download the next few days ;)

    Here's a little new screenshot, what I'm doing atm: <Edit by Moderator: Redacted mediafire url>
     
    Last edited by a moderator: Nov 10, 2016
  16. Offline

    Aza24

    [quote uid=90686358 name="MatorKaleen" post=1328161]Thanks for the tip, I will update the download the next few days ;)

    Here's a little new screenshot, what I'm doing atm: <Edit by Moderator: Redacted mediafire url>

    Cool, do you know how to make light update on generated chunks because I generated glowstone and no light is given off until the block is updated.
     
    Last edited by a moderator: Nov 10, 2016
  17. Offline

    MatorKaleen

    After a long time, I updated my tutorial a little bit. Just wanted to say :D
     
  18. Offline

    Skyost

  19. Offline

    MatorKaleen

    I never wanted to touch Minecraft again (for some reasons xD), but I just logged in and saw, somebody replied to my post. I'm downloading eclipse right now :confused:
     
    negative_codezZ and Skyost like this.
  20. Offline

    Monkeyboystein

    Ok. Looking at this where would i put the:

    for(x = 0; x < 16; x++)
    {
    for(y = 1; y <= 2; y++)
    {
    for (z = 0; z < 16; z++)
    {
    setBlock(result, x, y, z, (byte)Material.ICE.getId());
    }
    }
    }
     
  21. Offline

    JWhy

    Monkeyboystein: You'd use setBlock(...) in your generateBlockSections(...) method:
    Code:
        public byte[][] generateBlockSections(World world, Random random,
                int chunkX, int chunkZ, BiomeGrid biomeGrid) {
            byte[][] result = new byte[world.getMaxHeight() / 16][];
     
            for (int x = 0; x < 16; x++) {
                for (int y = 1; y <= 2; y++) {
                    for (int z = 0; z < 16; z++) {
                        setBlock(result, x, y, z, (byte) Material.ICE.getId());
                    }
                }
            }
     
            return result;
        }
     
Thread Status:
Not open for further replies.

Share This Page