Search Unity

Generating a town in a procedural world

Discussion in 'Scripting' started by Afropenguinn, Jul 27, 2015.

  1. Afropenguinn

    Afropenguinn

    Joined:
    May 15, 2013
    Posts:
    305
    Hey Unity Community! Been learning about procedural generation for a school project we are working on. Specifically I am building an infinite tilemap. I have a simple world being generated from a seed using perlin noise. It has plains and then patches of forested area. As the camera moves through the world I have a GetTile function that determines what the tile at the given location should be. Now I wanted to place towns randomly throughout the world! So my question is this:

    What level of control is possible with this? I know if I was using a fixed are I could build a 2D array of tiles and then I woud have pretty tight control over how the town was generated. I could say "Only place X number of homes, Y number of stores, and Z number of trees", or "Make roads run in X pattern here but Y pattern here, then connect them together". But frankly I don't know how to go about doing that without a fixed area. And if I define a fixed area on the map based on say a center point then how would my GetTile function know where these limits are without storing them somewhere?

    As always, thanks in advance your all your help :)
     
  2. GroZZleR

    GroZZleR

    Joined:
    Feb 1, 2015
    Posts:
    3,201
    You definitely want to allocate fixed amount of space, first. You can use your GetTile() function to generate a 50x50 block of new tiles, then manipulate that grid to generate your town.

    As for how you generate your town, there's a lot of different ways:
    - Create a bunch of predefined layouts that you've created by hand and copy over with some elements of randomness (this is how Spelunky works)
    - Use a pathing system where you start with the town square and path out randomly from there, placing buildings as you go.
    - Some sort of celluar automata.

    Play around with some random change and see what happens - the best way to build procedural content is to wing it until you end up with something you like, then refine that.
     
  3. Afropenguinn

    Afropenguinn

    Joined:
    May 15, 2013
    Posts:
    305
    Well that is the part I am most confused about, is how to make my GetTile() function find where a town is. Right now it looks like this:
    Code (CSharp):
    1. public Tile GetTile(int x, int y)
    2. {
    3.     Random.seed = GameManager.seed + (x.ToString() + y.ToString()).GetHashCode();
    4.    
    5.     float scale = .1f;
    6.     float value = PerlinNoise(x * scale, y * scale);
    7.  
    8.     if (value >= .85f)
    9.     {
    10.         int roll = Random.Range(0, 101);
    11.         if (roll >= 66)
    12.         {
    13.             return Tile.database["Mountain1"];
    14.         }
    15.         else if (roll >= 33)
    16.         {
    17.             return Tile.database["Mountain2"];
    18.         }
    19.         else
    20.         {
    21.             return Tile.database["Mountain3"];
    22.         }
    23.     }
    24.     if (value >= .66f)
    25.     {
    26.         int roll = Random.Range(0, 101);
    27.         if (roll >= 75)
    28.         {
    29.             return Tile.database["Forest1"];
    30.         }
    31.         else if (roll >= 50)
    32.         {
    33.             return Tile.database["Forest2"];
    34.         }
    35.         else if (roll >= 25)
    36.         {
    37.             return Tile.database["Forest3"];
    38.         }
    39.         else
    40.         {
    41.             return Tile.database["Forest4"];
    42.         }
    43.     }
    44.     else
    45.     {
    46.         int roll = Random.Range(0, 101);
    47.         if (roll >= 75)
    48.         {
    49.             return Tile.database["Snow1"];
    50.         }
    51.         else if (roll >= 50)
    52.         {
    53.             return Tile.database["Snow2"];
    54.         }
    55.         else if (roll >= 25)
    56.         {
    57.             return Tile.database["Snow3"];
    58.         }
    59.         else
    60.         {
    61.             return Tile.database["Snow4"];
    62.         }
    63.     }
    64. }
    So how would I define that fixed area with nothing but the seed, while still being able to somewhat control where they spawn (as I want them to be spread out)? I don't think there is really a way to generate a tile based on what is next to it because that would create an infinite loop.

    Sorry if this question is a bit broad, I just don't know where to begin really! Been looking at a lot of material on the subject but I haven't really found the answer.
     
  4. Strategos

    Strategos

    Joined:
    Aug 24, 2012
    Posts:
    255
    The key with procedural generation is rules.

    It looks like you have generated your map , areas of snow mountains and forest.

    Do a second pass on your grid to place towns using a set of rules.

    Example.

    Give each grid a small chance of having a town if it the correct terrain type (ie not mountains).

    You could have another pass which only allows towns if they arent too close or overlapping as a double check.

    If it has a town then start the town generation process.

    First generate your towns size. Give each size of town a set of rules, such as its radius, number of houses, number of stores etc. Maybe towns over a certain size might have a chance of large buildings like a town hall, castle or cathedral.

    You can go into as much detail with your rules as you want. You could change the rules based on the terrain, you could have different layout types. Generate these rules in order starting with the broadest rule and getting more and more refined at each step.

    Once you have generated all your towns you could do another pass on your map to generate roads that link them all based on another set of rules. You could also assign factions if you wanted and change the style of each town to give more variety.

    It goes on and on the possibilities are endless.

    (Yes I love procedural generation)
     
  5. Afropenguinn

    Afropenguinn

    Joined:
    May 15, 2013
    Posts:
    305
    All of that sounds great in theory, but wouldn't that mean that every time I use GetTile() I will need to run the town creation function to see what kind of town was built? This seems very costly. And how would I know where all the towns are after they were built? There could be thousands of them and I would have to check every one to build roads between them. Is there something I am misunderstanding?
     
  6. Strategos

    Strategos

    Joined:
    Aug 24, 2012
    Posts:
    255
    The very nature of procedural generation is that you regenerate your data every-time. The guiding principal here is predictable random numbers , which you are clearly using in your get tile function.

    This can be a heavy duty operation, so you have to come up with ways of optimising it.

    With regards to roads etc, there are different approaches. but as you are using predictable random numbers you could get the positions of all the towns in a large area in advance. You don't have to generate the towns just know where they are.

    As long as your seeds are the same the numbers will be the same.

    Edit some of these jobs become much easier if you limit yourself to a large but finite world btw.

    Have a look at Sir you are being hunted for instance.

    http://www.big-robot.com/2012/07/02/procedural-british-countryside-generation/
     
    Last edited: Jul 29, 2015
  7. Afropenguinn

    Afropenguinn

    Joined:
    May 15, 2013
    Posts:
    305
    Ok, so clearly I need to rethink my system. Right now I generate individual tiles, not areas. This is something new I came up with:
    Code (CSharp):
    1. public class Cell2048
    2. {
    3.     public Cell1024[,] cells = new Cell1024[2,2];
    4. }
    5.  
    6. public class Cell1024
    7. {
    8.     public Cell512[,] cells = new Cell512[2,2];
    9. }
    10.  
    11. public class Cell512
    12. {
    13.     public Cell256[,] cells = new Cell256[2,2];
    14. }
    15.  
    16. public class Cell256
    17. {
    18.     public Cell128[,] cells = new Cell128[2,2];
    19. }
    20.  
    21. public class Cell128
    22. {
    23.     public Cell64[,] cells = new Cell64[2,2];
    24. }
    25.  
    26. public class Cell64
    27. {
    28.     public Tile[,] tiles = new Tile[64,64];
    29. }
    30.  
    31. public class Tile
    32. {
    33.     public byte id;
    34.     public byte sid;
    35.     public byte trot;
    36.     public byte mrot;
    37. }
    So basically there are different resolutions of cells, each with varying degrees of detail (not present in the code yet). I figure larger structures like rivers could go in the higher resolution cells, while cities could be randomly placed in the mid sized resolution cells. Am I starting to head in the right direction here?
     
    Last edited: Aug 5, 2015