Search Unity

  1. The Unity Pro & Visual Studio Professional Bundle gives you the tools you need to develop faster & collaborate more efficiently. Learn more.
    Dismiss Notice
  2. Improved Prefab workflow (includes Nested Prefabs!), 2D isometric Tilemap and more! Get the 2018.3 Beta now.
    Dismiss Notice
  3. Want more efficiency in your development work? Sign up to receive weekly tech and creative know-how from Unity experts.
    Dismiss Notice
  4. Participate with students all over the world and build projects to teach people. Join now!
    Dismiss Notice
  5. Build games and experiences that can load instantly and without install. Explore the Project Tiny Preview today!
    Dismiss Notice
  6. Improve your Unity skills with a certified instructor in a private, interactive classroom. Watch the overview now.
    Dismiss Notice
  7. Want to see the most recent patch releases? Take a peek at the patch release page.
    Dismiss Notice

Procedural Cave Generation

Discussion in 'Community Learning & Teaching' started by SebastianLague, Feb 9, 2015.

  1. AaronC5513

    AaronC5513

    Joined:
    Oct 31, 2015
    Posts:
    5
    Hey, Sebastian I have a question. Is it possible to disable components on the player depending on whether it is 2D or 3D mode? If so how it is possible? The Rigidbody component did not seem to have an enabled field.
     
  2. FireHawkX

    FireHawkX

    Joined:
    Apr 26, 2016
    Posts:
    14
    Hi @SebastianLague : Hope you are well, its been quite some time since you last came here to answer a post... I am using your tutorial as a base for a stage in my game... its working perfectly so far... But I was wondering if you ever had the idea of doing that "part 10th" you talked about so long ago?... You also wrote about having an add-on where you would talk about player spawn or treasure spawn... I actually created one in the code, i added it in the "ConnectClosestRooms" method while making sure it would only add one thing per loop... it works, but its nowhere close to perfect... I have no control that sometimes the player spawns pretty much near the exit or where stuff appears...

    I would love to have that part 10 as it would be so much help for me and many others i am sure!! :)
     
  3. masaraat00

    masaraat00

    Joined:
    Dec 16, 2016
    Posts:
    1
    Hey @SebastianLague I'm a first time programmer using your code and tutorials for a semester project in my beginner computer science class. I'm on the first tutorial, and when I click the left click of the button, it doesn't change the map. Also, the map still has the lines on it that we got rid of at about at 9:55. Can you help me out? At first, I was coding alongside you in the tutorials, but I had some issues I couldn't figure out so I copy and pasted yours. Any input as to why this may be occurring? Thanks!
     
  4. careyduane

    careyduane

    Joined:
    Jun 16, 2015
    Posts:
    9
    I'm don't know if anyone has added a way to save the meshes, but here is a simple script that will save each mesh
    then you can recreate the cave by just putting them together. You can save multiple cave just by doing your normal
    click for a new cave and then you can save it with the "F3" key. Here is the script:

    #if UNITY_EDITOR
    using UnityEngine;
    using UnityEditor;
    // Usage: Attach to gameobject, assign target gameobject (from where the mesh is taken), Run, Press savekey
    public class SaveMeshInEditor : MonoBehaviour
    {
    private string objectPath = "Assets/Templates/";
    private int saveNumber = 0;
    public KeyCode saveKey = KeyCode.F3;
    private string saveName0 = "Ground";
    private string saveName1 = "Walls";
    private string saveName2 = "Cave_Mesh";
    public Transform selectedGameObject0;
    public Transform selectedGameObject1;
    public Transform selectedGameObject2;
    void Update()
    {
    if (Input.GetKeyDown(saveKey))
    {
    SaveAsset(saveName0, selectedGameObject0);
    SaveAsset(saveName1, selectedGameObject1);
    SaveAsset(saveName2, selectedGameObject2);
    saveNumber++;
    }
    }
    void SaveAsset(string saveName, Transform selectedGameObject)
    {
    var mf = selectedGameObject.GetComponent<MeshFilter>();
    if (mf)
    {
    var savePath = objectPath + saveName + saveNumber + ".asset";
    Debug.Log("Saved Mesh to:" + savePath);
    AssetDatabase.CreateAsset(mf.mesh, savePath);
    }
    }
    }
    #endif

    It will save the meshes in Assets/Templates and then the name of the asset with a number on the end
    so that you can have multiple saves and keep the data correct. In other words

    Ground0
    Walls0
    Cave_Mesh0

    Make up one map

    Ground1
    Walls1
    Cave_Mesh1
    Make up another map
     
    BerniceChua likes this.
  5. BerniceChua

    BerniceChua

    Joined:
    Nov 30, 2016
    Posts:
    32
    Hi @SebastianLague, thank you very much for making this tutorial!! ^__^ It is really interesting and useful.

    I have a question about video "E03. Creating the Mesh". In the part where you make the switch statements for 3 points, is there a reason why there's no option for:
    Code (CSharp):
    1. case 7:
    2.         MeshFromPoints(square.topLeft, square.topRight, square.bottomRight);
    3.         break;
    4. case 11:
    5.        MeshFromPoints(square.topRight, square.bottomRight, square.bottomLeft);
    6.        break;
    7. case 13:
    8.        MeshFromPoints(square.bottomRight, square.bottomLeft, square.topLeft);
    9.        break;
    10. case 14:
    11.        MeshFromPoints(square.bottomLeft, square.topLeft, square.topRight);
    12.        break;
    Thanks in advance.

    (Edited for formatting.)
     
  6. jwheeler1106

    jwheeler1106

    Joined:
    Feb 6, 2017
    Posts:
    1
    Sebastian, thanks for putting this up. Now, I'm doing my own project which uses procedural generation, is it possible to work on my own project and follow along with your tutorial, or do the steps have to be followed to a T?
     
  7. ApocFI

    ApocFI

    Joined:
    May 26, 2017
    Posts:
    21
    E: Solved this. I used the same method as in the processmap() room threshold method.
    I realise this is not "hot" topic anymore, but as I feel that this tutorial has a sturdy base for random generation, so I'd like to ask for some help... Any help is greatly appreciated :)
    I've set up the source code to function according to this series. What I'd like to do is find the free dungeon floor squares and be able to randomly spawn things on them. I've got it working, but only in quite a primitive way. What I do is use the same method as in the mapgenerator script Randomfill(). This will randomly roll for each "0,0" tile and see if stuff should be instantiated on it. While it works, it still spawns some things inside the walls, since randomFillMap() is called before the smoothing function possibly fills in the smaller rooms left isolated.

    While my method kinda works, I'm not satisfied with it. What I think should be done is:
    - Create a list of available "free" coordinates during randomfill() method
    - Remove coordinates that are filled with walls during smoothing
    - Add coordinates for tiles that are "zeroed" during the smoothing

    Then with the complete list, go through each tile and calculate if a thing should be instantiated on it. Or perhaps set predefined amount of "stuff" to be generated on random tiles? I feel this should be done from a separate script to keep things tidy.

    However, I'm very bad with dealing with lists. I watched the unity list tutorial, but I just can't grasp this stuff completely. Even though the cave generation tutorials contain lots of dealing with the lists I'm not really assimilating the information. If anyone could help I'd be so glad!

    Source at github: https://github.com/SebLague/Procedural-Cave-Generation
    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3. using System.Collections.Generic;
    4. using System;
    5.  
    6. public class MapGenerator : MonoBehaviour {
    7.  
    8.     public int width;
    9.     public int height;
    10.  
    11.     public string seed;
    12.     public bool useRandomSeed;
    13.  
    14.     [Range(0,100)]
    15.     public int randomFillPercent;
    16.  
    17.     int[,] map;
    18.  
    19.     void Start() {
    20.         GenerateMap();
    21.     }
    22.  
    23.     void Update() {
    24.         if (Input.GetMouseButtonDown(0)) {
    25.             GenerateMap();
    26.         }
    27.     }
    28.  
    29.     void GenerateMap() {
    30.         map = new int[width,height];
    31.         RandomFillMap();
    32.  
    33.         for (int i = 0; i < 5; i ++) {
    34.             SmoothMap();
    35.         }
    36.  
    37.         ProcessMap ();
    38.  
    39.         int borderSize = 1;
    40.         int[,] borderedMap = new int[width + borderSize * 2,height + borderSize * 2];
    41.  
    42.         for (int x = 0; x < borderedMap.GetLength(0); x ++) {
    43.             for (int y = 0; y < borderedMap.GetLength(1); y ++) {
    44.                 if (x >= borderSize && x < width + borderSize && y >= borderSize && y < height + borderSize) {
    45.                     borderedMap[x,y] = map[x-borderSize,y-borderSize];
    46.                 }
    47.                 else {
    48.                     borderedMap[x,y] =1;
    49.                 }
    50.             }
    51.         }
    52.  
    53.         MeshGenerator meshGen = GetComponent<MeshGenerator>();
    54.         meshGen.GenerateMesh(borderedMap, 1);
    55.     }
    56.  
    57.     void ProcessMap() {
    58.         List<List<Coord>> wallRegions = GetRegions (1);
    59.         int wallThresholdSize = 50;
    60.  
    61.         foreach (List<Coord> wallRegion in wallRegions) {
    62.             if (wallRegion.Count < wallThresholdSize) {
    63.                 foreach (Coord tile in wallRegion) {
    64.                     map[tile.tileX,tile.tileY] = 0;
    65.                 }
    66.             }
    67.         }
    68.  
    69.         List<List<Coord>> roomRegions = GetRegions (0);
    70.         int roomThresholdSize = 50;
    71.         List<Room> survivingRooms = new List<Room> ();
    72.    
    73.         foreach (List<Coord> roomRegion in roomRegions) {
    74.             if (roomRegion.Count < roomThresholdSize) {
    75.                 foreach (Coord tile in roomRegion) {
    76.                     map[tile.tileX,tile.tileY] = 1;
    77.                 }
    78.             }
    79.             else {
    80.                 survivingRooms.Add(new Room(roomRegion, map));
    81.             }
    82.         }
    83.         survivingRooms.Sort ();
    84.         survivingRooms [0].isMainRoom = true;
    85.         survivingRooms [0].isAccessibleFromMainRoom = true;
    86.  
    87.         ConnectClosestRooms (survivingRooms);
    88.     }
    89.  
    90.     void ConnectClosestRooms(List<Room> allRooms, bool forceAccessibilityFromMainRoom = false) {
    91.  
    92.         List<Room> roomListA = new List<Room> ();
    93.         List<Room> roomListB = new List<Room> ();
    94.  
    95.         if (forceAccessibilityFromMainRoom) {
    96.             foreach (Room room in allRooms) {
    97.                 if (room.isAccessibleFromMainRoom) {
    98.                     roomListB.Add (room);
    99.                 } else {
    100.                     roomListA.Add (room);
    101.                 }
    102.             }
    103.         } else {
    104.             roomListA = allRooms;
    105.             roomListB = allRooms;
    106.         }
    107.  
    108.         int bestDistance = 0;
    109.         Coord bestTileA = new Coord ();
    110.         Coord bestTileB = new Coord ();
    111.         Room bestRoomA = new Room ();
    112.         Room bestRoomB = new Room ();
    113.         bool possibleConnectionFound = false;
    114.  
    115.         foreach (Room roomA in roomListA) {
    116.             if (!forceAccessibilityFromMainRoom) {
    117.                 possibleConnectionFound = false;
    118.                 if (roomA.connectedRooms.Count > 0) {
    119.                     continue;
    120.                 }
    121.             }
    122.  
    123.             foreach (Room roomB in roomListB) {
    124.                 if (roomA == roomB || roomA.IsConnected(roomB)) {
    125.                     continue;
    126.                 }
    127.        
    128.                 for (int tileIndexA = 0; tileIndexA < roomA.edgeTiles.Count; tileIndexA ++) {
    129.                     for (int tileIndexB = 0; tileIndexB < roomB.edgeTiles.Count; tileIndexB ++) {
    130.                         Coord tileA = roomA.edgeTiles[tileIndexA];
    131.                         Coord tileB = roomB.edgeTiles[tileIndexB];
    132.                         int distanceBetweenRooms = (int)(Mathf.Pow (tileA.tileX-tileB.tileX,2) + Mathf.Pow (tileA.tileY-tileB.tileY,2));
    133.  
    134.                         if (distanceBetweenRooms < bestDistance || !possibleConnectionFound) {
    135.                             bestDistance = distanceBetweenRooms;
    136.                             possibleConnectionFound = true;
    137.                             bestTileA = tileA;
    138.                             bestTileB = tileB;
    139.                             bestRoomA = roomA;
    140.                             bestRoomB = roomB;
    141.                         }
    142.                     }
    143.                 }
    144.             }
    145.             if (possibleConnectionFound && !forceAccessibilityFromMainRoom) {
    146.                 CreatePassage(bestRoomA, bestRoomB, bestTileA, bestTileB);
    147.             }
    148.         }
    149.  
    150.         if (possibleConnectionFound && forceAccessibilityFromMainRoom) {
    151.             CreatePassage(bestRoomA, bestRoomB, bestTileA, bestTileB);
    152.             ConnectClosestRooms(allRooms, true);
    153.         }
    154.  
    155.         if (!forceAccessibilityFromMainRoom) {
    156.             ConnectClosestRooms(allRooms, true);
    157.         }
    158.     }
    159.  
    160.     void CreatePassage(Room roomA, Room roomB, Coord tileA, Coord tileB) {
    161.         Room.ConnectRooms (roomA, roomB);
    162.         //Debug.DrawLine (CoordToWorldPoint (tileA), CoordToWorldPoint (tileB), Color.green, 100);
    163.  
    164.         List<Coord> line = GetLine (tileA, tileB);
    165.         foreach (Coord c in line) {
    166.             DrawCircle(c,5);
    167.         }
    168.     }
    169.  
    170.     void DrawCircle(Coord c, int r) {
    171.         for (int x = -r; x <= r; x++) {
    172.             for (int y = -r; y <= r; y++) {
    173.                 if (x*x + y*y <= r*r) {
    174.                     int drawX = c.tileX + x;
    175.                     int drawY = c.tileY + y;
    176.                     if (IsInMapRange(drawX, drawY)) {
    177.                         map[drawX,drawY] = 0;
    178.                     }
    179.                 }
    180.             }
    181.         }
    182.     }
    183.  
    184.     List<Coord> GetLine(Coord from, Coord to) {
    185.         List<Coord> line = new List<Coord> ();
    186.  
    187.         int x = from.tileX;
    188.         int y = from.tileY;
    189.  
    190.         int dx = to.tileX - from.tileX;
    191.         int dy = to.tileY - from.tileY;
    192.  
    193.         bool inverted = false;
    194.         int step = Math.Sign (dx);
    195.         int gradientStep = Math.Sign (dy);
    196.  
    197.         int longest = Mathf.Abs (dx);
    198.         int shortest = Mathf.Abs (dy);
    199.  
    200.         if (longest < shortest) {
    201.             inverted = true;
    202.             longest = Mathf.Abs(dy);
    203.             shortest = Mathf.Abs(dx);
    204.  
    205.             step = Math.Sign (dy);
    206.             gradientStep = Math.Sign (dx);
    207.         }
    208.  
    209.         int gradientAccumulation = longest / 2;
    210.         for (int i =0; i < longest; i ++) {
    211.             line.Add(new Coord(x,y));
    212.  
    213.             if (inverted) {
    214.                 y += step;
    215.             }
    216.             else {
    217.                 x += step;
    218.             }
    219.  
    220.             gradientAccumulation += shortest;
    221.             if (gradientAccumulation >= longest) {
    222.                 if (inverted) {
    223.                     x += gradientStep;
    224.                 }
    225.                 else {
    226.                     y += gradientStep;
    227.                 }
    228.                 gradientAccumulation -= longest;
    229.             }
    230.         }
    231.  
    232.         return line;
    233.     }
    234.  
    235.     Vector3 CoordToWorldPoint(Coord tile) {
    236.         return new Vector3 (-width / 2 + .5f + tile.tileX, 2, -height / 2 + .5f + tile.tileY);
    237.     }
    238.  
    239.     List<List<Coord>> GetRegions(int tileType) {
    240.         List<List<Coord>> regions = new List<List<Coord>> ();
    241.         int[,] mapFlags = new int[width,height];
    242.  
    243.         for (int x = 0; x < width; x ++) {
    244.             for (int y = 0; y < height; y ++) {
    245.                 if (mapFlags[x,y] == 0 && map[x,y] == tileType) {
    246.                     List<Coord> newRegion = GetRegionTiles(x,y);
    247.                     regions.Add(newRegion);
    248.  
    249.                     foreach (Coord tile in newRegion) {
    250.                         mapFlags[tile.tileX, tile.tileY] = 1;
    251.                     }
    252.                 }
    253.             }
    254.         }
    255.  
    256.         return regions;
    257.     }
    258.  
    259.     List<Coord> GetRegionTiles(int startX, int startY) {
    260.         List<Coord> tiles = new List<Coord> ();
    261.         int[,] mapFlags = new int[width,height];
    262.         int tileType = map [startX, startY];
    263.  
    264.         Queue<Coord> queue = new Queue<Coord> ();
    265.         queue.Enqueue (new Coord (startX, startY));
    266.         mapFlags [startX, startY] = 1;
    267.  
    268.         while (queue.Count > 0) {
    269.             Coord tile = queue.Dequeue();
    270.             tiles.Add(tile);
    271.  
    272.             for (int x = tile.tileX - 1; x <= tile.tileX + 1; x++) {
    273.                 for (int y = tile.tileY - 1; y <= tile.tileY + 1; y++) {
    274.                     if (IsInMapRange(x,y) && (y == tile.tileY || x == tile.tileX)) {
    275.                         if (mapFlags[x,y] == 0 && map[x,y] == tileType) {
    276.                             mapFlags[x,y] = 1;
    277.                             queue.Enqueue(new Coord(x,y));
    278.                         }
    279.                     }
    280.                 }
    281.             }
    282.         }
    283.         return tiles;
    284.     }
    285.  
    286.     bool IsInMapRange(int x, int y) {
    287.         return x >= 0 && x < width && y >= 0 && y < height;
    288.     }
    289.  
    290.  
    291.     void RandomFillMap() {
    292.  
    293.         if (useRandomSeed) {
    294.             seed = Time.time.ToString();
    295.         }
    296.  
    297.         System.Random pseudoRandom = new System.Random(seed.GetHashCode());
    298.  
    299.         for (int x = 0; x < width; x ++) {
    300.             for (int y = 0; y < height; y ++) {
    301.                 if (x == 0 || x == width-1 || y == 0 || y == height -1) {
    302.                     map[x,y] = 1;
    303.                 }
    304.                 else {
    305.                     map[x,y] = (pseudoRandom.Next(0,100) < randomFillPercent)? 1: 0;
    306.                 }
    307.             }
    308.         }
    309.     }
    310.  
    311.     void SmoothMap() {
    312.         for (int x = 0; x < width; x ++) {
    313.             for (int y = 0; y < height; y ++) {
    314.                 int neighbourWallTiles = GetSurroundingWallCount(x,y);
    315.  
    316.                 if (neighbourWallTiles > 4)
    317.                     map[x,y] = 1;
    318.                 else if (neighbourWallTiles < 4)
    319.                     map[x,y] = 0;
    320.  
    321.             }
    322.         }
    323.     }
    324.  
    325.     int GetSurroundingWallCount(int gridX, int gridY) {
    326.         int wallCount = 0;
    327.         for (int neighbourX = gridX - 1; neighbourX <= gridX + 1; neighbourX ++) {
    328.             for (int neighbourY = gridY - 1; neighbourY <= gridY + 1; neighbourY ++) {
    329.                 if (IsInMapRange(neighbourX,neighbourY)) {
    330.                     if (neighbourX != gridX || neighbourY != gridY) {
    331.                         wallCount += map[neighbourX,neighbourY];
    332.                     }
    333.                 }
    334.                 else {
    335.                     wallCount ++;
    336.                 }
    337.             }
    338.         }
    339.  
    340.         return wallCount;
    341.     }
    342.  
    343.     struct Coord {
    344.         public int tileX;
    345.         public int tileY;
    346.  
    347.         public Coord(int x, int y) {
    348.             tileX = x;
    349.             tileY = y;
    350.         }
    351.     }
    352.  
    353.  
    354.     class Room : IComparable<Room> {
    355.         public List<Coord> tiles;
    356.         public List<Coord> edgeTiles;
    357.         public List<Room> connectedRooms;
    358.         public int roomSize;
    359.         public bool isAccessibleFromMainRoom;
    360.         public bool isMainRoom;
    361.  
    362.         public Room() {
    363.         }
    364.  
    365.         public Room(List<Coord> roomTiles, int[,] map) {
    366.             tiles = roomTiles;
    367.             roomSize = tiles.Count;
    368.             connectedRooms = new List<Room>();
    369.  
    370.             edgeTiles = new List<Coord>();
    371.             foreach (Coord tile in tiles) {
    372.                 for (int x = tile.tileX-1; x <= tile.tileX+1; x++) {
    373.                     for (int y = tile.tileY-1; y <= tile.tileY+1; y++) {
    374.                         if (x == tile.tileX || y == tile.tileY) {
    375.                             if (map[x,y] == 1) {
    376.                                 edgeTiles.Add(tile);
    377.                             }
    378.                         }
    379.                     }
    380.                 }
    381.             }
    382.         }
    383.  
    384.         public void SetAccessibleFromMainRoom() {
    385.             if (!isAccessibleFromMainRoom) {
    386.                 isAccessibleFromMainRoom = true;
    387.                 foreach (Room connectedRoom in connectedRooms) {
    388.                     connectedRoom.SetAccessibleFromMainRoom();
    389.                 }
    390.             }
    391.         }
    392.  
    393.         public static void ConnectRooms(Room roomA, Room roomB) {
    394.             if (roomA.isAccessibleFromMainRoom) {
    395.                 roomB.SetAccessibleFromMainRoom ();
    396.             } else if (roomB.isAccessibleFromMainRoom) {
    397.                 roomA.SetAccessibleFromMainRoom();
    398.             }
    399.             roomA.connectedRooms.Add (roomB);
    400.             roomB.connectedRooms.Add (roomA);
    401.         }
    402.  
    403.         public bool IsConnected(Room otherRoom) {
    404.             return connectedRooms.Contains(otherRoom);
    405.         }
    406.  
    407.         public int CompareTo(Room otherRoom) {
    408.             return otherRoom.roomSize.CompareTo (roomSize);
    409.         }
    410.     }
    411.  
    412. }
    413.  
     
    Last edited: Oct 19, 2017
  8. GregoryFenn

    GregoryFenn

    Joined:
    Feb 13, 2018
    Posts:
    19

    Are you sure you need it? Why not just create an empty GameObject with the 2D and 3D copies of the same object (or same object but with 2d vs 3d components) as two child-objects? Then you can script it to turn one child on active and the other off when you need.
     
    Last edited: Jun 9, 2018
  9. GregoryFenn

    GregoryFenn

    Joined:
    Feb 13, 2018
    Posts:
    19
    "The variable walls of MeshGenerator has not been assigned."

    The error is at line 'walls.mesh = wallMesh;'


    I can't see what I'm doing wrong. The 2d meshg works great but I can't build tall walls yet, Both my code and the code copied from the website produce identical errors.

    Anyone got any ideas? I'm guessing it's something to do with the new unity, because I notice 'Mesh' isn't a valid type anymore, but that's just a hunch.
     
  10. djones_unity841

    djones_unity841

    Joined:
    May 24, 2018
    Posts:
    1
    This is the error message that I'm getting on episode 1. "The associated script can not be loaded. Please fix any compile errors and assign a valid script." I guessed that I typed the code wrong so I deleted what I had and copied what Sebastian has, but I still get this message. Can someone please help me?
     
  11. GabrielSteffens

    GabrielSteffens

    Joined:
    Jul 12, 2018
    Posts:
    1
    Hi @SebastianLague , I'm new to Unity, I wonder how I created a respawn where it would always stay in the upper left corner.
     
  12. Nightning

    Nightning

    Joined:
    Sep 5, 2018
    Posts:
    1
    Hello guys, I'm trying to learn at 100% this work and an issue bothered my mind: how can I do to make walls not only in the outlines but in the entire map? In this way the wall colliders will become the whole cave.

    Thank you for the help