Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice

Generating structures in a procedurally generated world

Discussion in 'Scripting' started by Emolk, Feb 6, 2020.

  1. Emolk

    Emolk

    Joined:
    Feb 11, 2014
    Posts:
    241
    Hi, i'm making a 2D platformer as seen below. The world is procedurally generated using perlin noise and the world is made using tiles with a tilemap.



    How would one go about generating structures in the world? There are two problems that i'm not sure how to overcome.

    The first is, say i have found the appropriate place in the world to spawn a structure, how do i go about designing and spawning a structure with tiles? I.E how do i paint a structure using a tilemap and then paste it into the world upon generation? Is there a tool i can use to accomplish this?

    The second is how do i find the appropriate place to spawn the structure in the world? I have done a basic version of this, where i iterate through the map and find tiles that are suitable for spawning a structure on, but this is very basic and only finds spaces that are in the bottom of a cave and have three spaces free. This works fine for say spawning a shopkeeper in (a tiny soon to be structure), but how do i work this for more elaborate structures?
     
  2. _met44

    _met44

    Joined:
    Jun 1, 2013
    Posts:
    633
    Hi, if your generation algorithm cannot provide you with valid spaces to put your buildings, maybe make room for it ?

    Locate places with not too steep or narrow ground and flatten the tiles to make room for what you'd like to place on it.

    Alternatively, you could prelace your buildings and steer your generation algorithm to generate land around it.
     
  3. neoshaman

    neoshaman

    Joined:
    Feb 11, 2011
    Posts:
    6,469
    You should probably have a progression and pacing template in mind first, and some metric, if it's a platformer, while perlin noise can be used (see terraria) it's best to design with gameplay metric in mind, ie what's the minimal space the character need to move, what's the maximal height he can jump, what's the maximal speed, how far can he jump, what are the line of action (ie how far he can hit enemy or interact with objects), etc ...

    By progression template, I mean what's the intended order of events, generally don't want to spawn the player right next to an enemy, so you probably need a no spawn area around the player's spawn, you probably want the end of the level be as far of possible from the player, you probably want item to be regularly space in between the start and the end, and you probably need stronger enemy more toward the end than the beginning, etc ...

    So in general you will use multiple procedural technique as multiple pass to get the correct result.
     
  4. Emolk

    Emolk

    Joined:
    Feb 11, 2014
    Posts:
    241
    The tiles are all able to be manipulated by the Player so thats not that important. I'm still looking for a way to create a structure with tiles that i can easily paste into a scene, does anybody have any ideas?
     
  5. atrin83

    atrin83

    Joined:
    Aug 7, 2020
    Posts:
    1
    i suggest to spawn a sprite cause its easier to interact with and its easier to place.
     
  6. coatline

    coatline

    Joined:
    Jul 31, 2019
    Posts:
    17
    Code (CSharp):
    1.  
    2. [CreateAssetMenu(fileName = "New Structure", menuName = "Structure")]
    3.  
    4. [System.Serializable]
    5. public class Structure : ScriptableObject
    6. {
    7.     public string _name;
    8.     public byte width;
    9.     public byte height;
    10.     //tiles found inside structure
    11.     public List<Tile> tiles;
    12.     //map of tiles with tile type specified by index of the list 'tiles'
    13.     public int[] structureData;
    14. }
    15.  
    What I have opted for is to take a scriptable object as this and plug it in to a separate script in a separate screen.

    The script would look something like this:

    Code (CSharp):
    1.  
    2. public class StructureEditor : MonoBehaviour
    3. {
    4.      //structure you want to edit
    5.     [SerializeField] Structure structureToEdit;
    6.     [SerializeField] Tilemap tilemap;
    7.     //List of all tiles that could be used in structure
    8.     [SerializeField] List<tile> tileData;
    9.      List<Tile> tiles;
    10.  
    11.     void LoadStructureData()
    12.     {
    13.         for (int x = 0; x < structureToEdit.width; x++)
    14.             for (int y = 0; y < structureToEdit.height; y++)
    15.             {
    16.                 tilemap.SetTile(new Vector3Int(x, y, 0), structureToEdit.tiles[structureToEdit.structureData[x + (y * structureToEdit.width)]]);
    17.             }
    18.     }
    19.  
    20.     //hook this up to a button
    21.     public void ClearMap()
    22.     {
    23.         tilemap.ClearAllTiles();
    24.     }
    25.  
    26.     //hook this up to a button
    27.     public void ClearTileData()
    28.     {
    29.         tileData.Clear();
    30.     }
    31.  
    32.     //hook this up to a button
    33.     public void SaveStructureData()
    34.     {
    35.         structureToEdit.structureData = new int[structureToEdit.width * structureToEdit.height];
    36.         tiles = new List<Tile>();
    37.         structureToEdit.tiles.Clear();
    38.  
    39.         for (int x = 0; x < structureToEdit.width; x++)
    40.             for (int y = 0; y < structureToEdit.height; y++)
    41.             {
    42.                 var tile = (RuleTile)tilemap.GetTile(new Vector3Int(x, y, 0));
    43.  
    44.                 for (int i = 0; i < tileData.Count; i++)
    45.                 {
    46.                     if (tileData[i] == tile && !tiles.Contains(tileData[i]))
    47.                     {
    48.                         tiles.Add(tileData[i]);
    49.                     }
    50.                 }
    51.             }
    52.  
    53.         for (int x = 0; x < structureToEdit.width; x++)
    54.             for (int y = 0; y < structureToEdit.height; y++)
    55.             {
    56.                 structureToEdit.structureData[x + (y * structureToEdit.width)] = TilesIndex((RuleTile)tilemap.GetTile(new Vector3Int(x, y, 0)));
    57.             }
    58.  
    59.         structureToEdit.tiles = this.tiles;
    60.     }
    61.  
    62.     int TilesIndex(Tile tile)
    63.     {
    64.         for (int i = 0; i < tiles.Count; i++)
    65.         {
    66.             if (tile == tiles[i])
    67.             {
    68.                 return i;
    69.             }
    70.         }
    71.  
    72.         print($"Did not identify tile {tile}");
    73.         return 0;
    74.     }
    75.  
    76.     //Specify where to put the tiles
    77.     private void OnDrawGizmos()
    78.     {
    79.         for (int x = 0; x < structureToEdit.width; x++)
    80.             for (int y = 0; y < structureToEdit.height; y++)
    81.             {
    82.                 Gizmos.DrawWireCube(new Vector3((x + .5f), (y + .5f)), new Vector3(1, 1));
    83.             }
    84.     }
    85. }
    86.  
    Have a separate scene where you can build these in scene view. Then, when you are finished editing it,
    go to game view and click a button to save the tiles hooked up to this script. You can quite easily take the single dimensional array in the structure scriptableobject and convert it to a two dimensional array and map those to the map.