├── .gitattributes ├── 20x20 Maze Example.png ├── CellScript.cs ├── MazeGenerator.cs ├── README.txt └── Setting up the generator.pdf /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /20x20 Maze Example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/c00pala/Unity-2D-Maze-Generator/fded0fbe59e434ce29aaf78f8e66bb5ce7cc9159/20x20 Maze Example.png -------------------------------------------------------------------------------- /CellScript.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | using UnityEngine; 4 | 5 | public class CellScript : MonoBehaviour { 6 | 7 | public GameObject wallL; 8 | public GameObject wallR; 9 | public GameObject wallU; 10 | public GameObject wallD; 11 | } 12 | -------------------------------------------------------------------------------- /MazeGenerator.cs: -------------------------------------------------------------------------------- 1 | /* ---------------------------- 2 | * 2D Maze Generator for Unity. 3 | * Uses Deapth-First searching 4 | * and Recursive Backtracking. 5 | * ---------------------------- 6 | * Generates a 2x2 centre room in 7 | * the middle of the Maze. 8 | * ---------------------------- 9 | * Author: c00pala 10 | * ~13/05/2018~ 11 | * ---------------------------- */ 12 | 13 | using System.Collections; 14 | using System.Collections.Generic; 15 | using UnityEngine; 16 | 17 | public class MazeGenerator : MonoBehaviour { 18 | 19 | #region Variables: 20 | // ------------------------------------------------------ 21 | // User defined variables - set in editor: 22 | // ------------------------------------------------------ 23 | [Header("Maze generation values:")] 24 | [Tooltip("How many cells tall is the maze. MUST be an even number. " + 25 | "If number is odd, it will be reduced by 1.\n\n" + 26 | "Minimum value of 4.")] 27 | public int mazeRows; 28 | [Tooltip("How many cells wide is the maze. Must be an even number. " + 29 | "If number is odd, it will be reduced by 1.\n\n" + 30 | "Minimum value of 4.")] 31 | public int mazeColumns; 32 | 33 | [Header("Maze object variables:")] 34 | [Tooltip("Cell prefab object.")] 35 | [SerializeField] 36 | private GameObject cellPrefab; 37 | 38 | [Tooltip("If you want to disable the main sprite so the cell has no background, set to TRUE. This will create a maze with only walls.")] 39 | public bool disableCellSprite; 40 | 41 | // ------------------------------------------------------ 42 | // System defined variables - You don't need to touch these: 43 | // ------------------------------------------------------ 44 | 45 | // Variable to store size of centre room. Hard coded to be 2. 46 | private int centreSize = 2; 47 | 48 | // Dictionary to hold and locate all cells in maze. 49 | private Dictionary allCells = new Dictionary(); 50 | // List to hold unvisited cells. 51 | private List unvisited = new List(); 52 | // List to store 'stack' cells, cells being checked during generation. 53 | private List stack = new List(); 54 | 55 | // Array will hold 4 centre room cells, from 0 -> 3 these are: 56 | // Top left (0), top right (1), bottom left (2), bottom right (3). 57 | private Cell[] centreCells = new Cell[4]; 58 | 59 | // Cell variables to hold current and checking Cells. 60 | private Cell currentCell; 61 | private Cell checkCell; 62 | 63 | // Array of all possible neighbour positions. 64 | private Vector2[] neighbourPositions = new Vector2[] { new Vector2(-1, 0), new Vector2(1, 0), new Vector2(0, 1), new Vector2(0, -1) }; 65 | 66 | // Size of the cells, used to determine how far apart to place cells during generation. 67 | private float cellSize; 68 | 69 | private GameObject mazeParent; 70 | #endregion 71 | 72 | /* This Start run is an example, you can delete this when 73 | * you want to start calling the maze generator manually. 74 | * To generate a maze is really easy, just call the GenerateMaze() function 75 | * pass a rows value and columns value as parameters and the generator will 76 | * do the rest for you. Enjoy! 77 | */ 78 | private void Start() 79 | { 80 | GenerateMaze(mazeRows, mazeColumns); 81 | } 82 | 83 | private void GenerateMaze(int rows, int columns) 84 | { 85 | if (mazeParent != null) DeleteMaze(); 86 | 87 | mazeRows = rows; 88 | mazeColumns = columns; 89 | CreateLayout(); 90 | } 91 | 92 | // Creates the grid of cells. 93 | public void CreateLayout() 94 | { 95 | InitValues(); 96 | 97 | // Set starting point, set spawn point to start. 98 | Vector2 startPos = new Vector2(-(cellSize * (mazeColumns / 2)) + (cellSize / 2), -(cellSize * (mazeRows / 2)) + (cellSize / 2)); 99 | Vector2 spawnPos = startPos; 100 | 101 | for (int x = 1; x <= mazeColumns; x++) 102 | { 103 | for (int y = 1; y <= mazeRows; y++) 104 | { 105 | GenerateCell(spawnPos, new Vector2(x, y)); 106 | 107 | // Increase spawnPos y. 108 | spawnPos.y += cellSize; 109 | } 110 | 111 | // Reset spawnPos y and increase spawnPos x. 112 | spawnPos.y = startPos.y; 113 | spawnPos.x += cellSize; 114 | } 115 | 116 | CreateCentre(); 117 | RunAlgorithm(); 118 | MakeExit(); 119 | } 120 | 121 | // This is where the fun stuff happens. 122 | public void RunAlgorithm() 123 | { 124 | // Get start cell, make it visited (i.e. remove from unvisited list). 125 | unvisited.Remove(currentCell); 126 | 127 | // While we have unvisited cells. 128 | while (unvisited.Count > 0) 129 | { 130 | List unvisitedNeighbours = GetUnvisitedNeighbours(currentCell); 131 | if (unvisitedNeighbours.Count > 0) 132 | { 133 | // Get a random unvisited neighbour. 134 | checkCell = unvisitedNeighbours[Random.Range(0, unvisitedNeighbours.Count)]; 135 | // Add current cell to stack. 136 | stack.Add(currentCell); 137 | // Compare and remove walls. 138 | CompareWalls(currentCell, checkCell); 139 | // Make currentCell the neighbour cell. 140 | currentCell = checkCell; 141 | // Mark new current cell as visited. 142 | unvisited.Remove(currentCell); 143 | } 144 | else if (stack.Count > 0) 145 | { 146 | // Make current cell the most recently added Cell from the stack. 147 | currentCell = stack[stack.Count - 1]; 148 | // Remove it from stack. 149 | stack.Remove(currentCell); 150 | } 151 | } 152 | } 153 | 154 | public void MakeExit() 155 | { 156 | // Create and populate list of all possible edge cells. 157 | List edgeCells = new List(); 158 | 159 | foreach (KeyValuePair cell in allCells) 160 | { 161 | if (cell.Key.x == 0 || cell.Key.x == mazeColumns || cell.Key.y == 0 || cell.Key.y == mazeRows) 162 | { 163 | edgeCells.Add(cell.Value); 164 | } 165 | } 166 | 167 | // Get edge cell randomly from list. 168 | Cell newCell = edgeCells[Random.Range(0, edgeCells.Count)]; 169 | 170 | // Remove appropriate wall for chosen edge cell. 171 | if (newCell.gridPos.x == 0) RemoveWall(newCell.cScript, 1); 172 | else if (newCell.gridPos.x == mazeColumns) RemoveWall(newCell.cScript, 2); 173 | else if (newCell.gridPos.y == mazeRows) RemoveWall(newCell.cScript, 3); 174 | else RemoveWall(newCell.cScript, 4); 175 | 176 | Debug.Log("Maze generation finished."); 177 | } 178 | 179 | public List GetUnvisitedNeighbours(Cell curCell) 180 | { 181 | // Create a list to return. 182 | List neighbours = new List(); 183 | // Create a Cell object. 184 | Cell nCell = curCell; 185 | // Store current cell grid pos. 186 | Vector2 cPos = curCell.gridPos; 187 | 188 | foreach (Vector2 p in neighbourPositions) 189 | { 190 | // Find position of neighbour on grid, relative to current. 191 | Vector2 nPos = cPos + p; 192 | // If cell exists. 193 | if (allCells.ContainsKey(nPos)) nCell = allCells[nPos]; 194 | // If cell is unvisited. 195 | if (unvisited.Contains(nCell)) neighbours.Add(nCell); 196 | } 197 | 198 | return neighbours; 199 | } 200 | 201 | // Compare neighbour with current and remove appropriate walls. 202 | public void CompareWalls(Cell cCell, Cell nCell) 203 | { 204 | // If neighbour is left of current. 205 | if (nCell.gridPos.x < cCell.gridPos.x) 206 | { 207 | RemoveWall(nCell.cScript, 2); 208 | RemoveWall(cCell.cScript, 1); 209 | } 210 | // Else if neighbour is right of current. 211 | else if (nCell.gridPos.x > cCell.gridPos.x) 212 | { 213 | RemoveWall(nCell.cScript, 1); 214 | RemoveWall(cCell.cScript, 2); 215 | } 216 | // Else if neighbour is above current. 217 | else if (nCell.gridPos.y > cCell.gridPos.y) 218 | { 219 | RemoveWall(nCell.cScript, 4); 220 | RemoveWall(cCell.cScript, 3); 221 | } 222 | // Else if neighbour is below current. 223 | else if (nCell.gridPos.y < cCell.gridPos.y) 224 | { 225 | RemoveWall(nCell.cScript, 3); 226 | RemoveWall(cCell.cScript, 4); 227 | } 228 | } 229 | 230 | // Function disables wall of your choosing, pass it the script attached to the desired cell 231 | // and an 'ID', where the ID = the wall. 1 = left, 2 = right, 3 = up, 4 = down. 232 | public void RemoveWall(CellScript cScript, int wallID) 233 | { 234 | if (wallID == 1) cScript.wallL.SetActive(false); 235 | else if (wallID == 2) cScript.wallR.SetActive(false); 236 | else if (wallID == 3) cScript.wallU.SetActive(false); 237 | else if (wallID == 4) cScript.wallD.SetActive(false); 238 | } 239 | 240 | public void CreateCentre() 241 | { 242 | // Get the 4 centre cells using the rows and columns variables. 243 | // Remove the required walls for each. 244 | centreCells[0] = allCells[new Vector2((mazeColumns / 2), (mazeRows / 2) + 1)]; 245 | RemoveWall(centreCells[0].cScript, 4); 246 | RemoveWall(centreCells[0].cScript, 2); 247 | centreCells[1] = allCells[new Vector2((mazeColumns / 2) + 1, (mazeRows / 2) + 1)]; 248 | RemoveWall(centreCells[1].cScript, 4); 249 | RemoveWall(centreCells[1].cScript, 1); 250 | centreCells[2] = allCells[new Vector2((mazeColumns / 2), (mazeRows / 2))]; 251 | RemoveWall(centreCells[2].cScript, 3); 252 | RemoveWall(centreCells[2].cScript, 2); 253 | centreCells[3] = allCells[new Vector2((mazeColumns / 2) + 1, (mazeRows / 2))]; 254 | RemoveWall(centreCells[3].cScript, 3); 255 | RemoveWall(centreCells[3].cScript, 1); 256 | 257 | // Create a List of ints, using this, select one at random and remove it. 258 | // We then use the remaining 3 ints to remove 3 of the centre cells from the 'unvisited' list. 259 | // This ensures that one of the centre cells will connect to the maze but the other three won't. 260 | // This way, the centre room will only have 1 entry / exit point. 261 | List rndList = new List { 0, 1, 2, 3 }; 262 | int startCell = rndList[Random.Range(0, rndList.Count)]; 263 | rndList.Remove(startCell); 264 | currentCell = centreCells[startCell]; 265 | foreach(int c in rndList) 266 | { 267 | unvisited.Remove(centreCells[c]); 268 | } 269 | } 270 | 271 | public void GenerateCell(Vector2 pos, Vector2 keyPos) 272 | { 273 | // Create new Cell object. 274 | Cell newCell = new Cell(); 275 | 276 | // Store reference to position in grid. 277 | newCell.gridPos = keyPos; 278 | // Set and instantiate cell GameObject. 279 | newCell.cellObject = Instantiate(cellPrefab, pos, cellPrefab.transform.rotation); 280 | // Child new cell to parent. 281 | if (mazeParent != null) newCell.cellObject.transform.parent = mazeParent.transform; 282 | // Set name of cellObject. 283 | newCell.cellObject.name = "Cell - X:" + keyPos.x + " Y:" + keyPos.y; 284 | // Get reference to attached CellScript. 285 | newCell.cScript = newCell.cellObject.GetComponent(); 286 | // Disable Cell sprite, if applicable. 287 | if (disableCellSprite) newCell.cellObject.GetComponent().enabled = false; 288 | 289 | // Add to Lists. 290 | allCells[keyPos] = newCell; 291 | unvisited.Add(newCell); 292 | } 293 | 294 | public void DeleteMaze() 295 | { 296 | if (mazeParent != null) Destroy(mazeParent); 297 | } 298 | 299 | public void InitValues() 300 | { 301 | // Check generation values to prevent generation failing. 302 | if (IsOdd(mazeRows)) mazeRows--; 303 | if (IsOdd(mazeColumns)) mazeColumns--; 304 | 305 | if (mazeRows <= 3) mazeRows = 4; 306 | if (mazeColumns <= 3) mazeColumns = 4; 307 | 308 | // Determine size of cell using localScale. 309 | cellSize = cellPrefab.transform.localScale.x; 310 | 311 | // Create an empty parent object to hold the maze in the scene. 312 | mazeParent = new GameObject(); 313 | mazeParent.transform.position = Vector2.zero; 314 | mazeParent.name = "Maze"; 315 | } 316 | 317 | public bool IsOdd(int value) 318 | { 319 | return value % 2 != 0; 320 | } 321 | 322 | public class Cell 323 | { 324 | public Vector2 gridPos; 325 | public GameObject cellObject; 326 | public CellScript cScript; 327 | } 328 | } 329 | 330 | 331 | -------------------------------------------------------------------------------- /README.txt: -------------------------------------------------------------------------------- 1 | A 2D Maze Generator for Unity. 2 | 3 | Will generate a 2D Maze using Sprites provided 4 | Contains a 2x2 Centre room as the starting point. 5 | An exit will be placed on one of the edge Cells. 6 | 7 | For usage and info, see the provided PDF 'Setting up the Generator'. 8 | See the example image for an example of a 20x20 Cell Maze generation. -------------------------------------------------------------------------------- /Setting up the generator.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/c00pala/Unity-2D-Maze-Generator/fded0fbe59e434ce29aaf78f8e66bb5ce7cc9159/Setting up the generator.pdf --------------------------------------------------------------------------------