Hello, here's code for quick maze generator. It simply modification of this code. With it, you can create mazes that look like this... Just attach the script to some object... Code (csharp): using UnityEngine; using System; using System.Collections; using System.Collections.Generic; public class MazeGenerator : MonoBehaviour { public int width, height; public Material brick; private int[,] Maze; private List<Vector3> pathMazes = new List<Vector3>(); private Stack<Vector2> _tiletoTry = new Stack<Vector2>(); private List<Vector2> offsets = new List<Vector2> { new Vector2(0, 1), new Vector2(0, -1), new Vector2(1, 0), new Vector2(-1, 0) }; private System.Random rnd = new System.Random(); private int _width, _height; private Vector2 _currentTile; public Vector2 CurrentTile { get { return _currentTile; } private set { if (value.x < 1 || value.x >= this.width - 1 || value.y < 1 || value.y >= this.height - 1) { throw new ArgumentException("CurrentTile must be within the one tile border all around the maze"); } if (value.x % 2 == 1 || value.y % 2 == 1) { _currentTile = value; } else { throw new ArgumentException("The current square must not be both on an even X-axis and an even Y-axis, to ensure we can get walls around all tunnels"); } } } private static MazeGenerator instance; public static MazeGenerator Instance { get { return instance; } } void Awake() { instance = this; } void Start() { Camera.main.orthographic = true; Camera.main.orthographicSize = 30; GenerateMaze(); } void GenerateMaze() { Maze = new int[width, height]; for (int x = 0; x < width; x++) { for (int y = 0; y < height; y++) { Maze[x, y] = 1; } } CurrentTile = Vector2.one; _tiletoTry.Push(CurrentTile); Maze = CreateMaze(); GameObject ptype = null; for (int i = 0; i <= Maze.GetUpperBound(0); i++) { for (int j = 0; j <= Maze.GetUpperBound(1); j++) { if (Maze[i, j] == 1) { ptype = GameObject.CreatePrimitive(PrimitiveType.Cube); ptype.transform.position = new Vector3(i * ptype.transform.localScale.x, j * ptype.transform.localScale.y, 0); if (brick != null) { ptype.renderer.material = brick; } ptype.transform.parent = transform; } else if (Maze[i, j] == 0) { pathMazes.Add(new Vector3(i, j, 0)); } } } } public int[,] CreateMaze() { //local variable to store neighbors to the current square //as we work our way through the maze List<Vector2> neighbors; //as long as there are still tiles to try while (_tiletoTry.Count > 0) { //excavate the square we are on Maze[(int)CurrentTile.x, (int)CurrentTile.y] = 0; //get all valid neighbors for the new tile neighbors = GetValidNeighbors(CurrentTile); //if there are any interesting looking neighbors if (neighbors.Count > 0) { //remember this tile, by putting it on the stack _tiletoTry.Push(CurrentTile); //move on to a random of the neighboring tiles CurrentTile = neighbors[rnd.Next(neighbors.Count)]; } else { //if there were no neighbors to try, we are at a dead-end //toss this tile out //(thereby returning to a previous tile in the list to check). CurrentTile = _tiletoTry.Pop(); } } return Maze; } /// <summary> /// Get all the prospective neighboring tiles /// </summary> /// <param name="centerTile">The tile to test</param> /// <returns>All and any valid neighbors</returns> private List<Vector2> GetValidNeighbors(Vector2 centerTile) { List<Vector2> validNeighbors = new List<Vector2>(); //Check all four directions around the tile foreach (var offset in offsets) { //find the neighbor's position Vector2 toCheck = new Vector2(centerTile.x + offset.x, centerTile.y + offset.y); //make sure the tile is not on both an even X-axis and an even Y-axis //to ensure we can get walls around all tunnels if (toCheck.x % 2 == 1 || toCheck.y % 2 == 1) { //if the potential neighbor is unexcavated (==1) //and still has three walls intact (new territory) if (Maze[(int)toCheck.x, (int)toCheck.y] == 1 HasThreeWallsIntact(toCheck)) { //add the neighbor validNeighbors.Add(toCheck); } } } return validNeighbors; } /// <summary> /// Counts the number of intact walls around a tile /// </summary> /// <param name="Vector2ToCheck">The coordinates of the tile to check</param> /// <returns>Whether there are three intact walls (the tile has not been dug into earlier.</returns> private bool HasThreeWallsIntact(Vector2 Vector2ToCheck) { int intactWallCounter = 0; //Check all four directions around the tile foreach (var offset in offsets) { //find the neighbor's position Vector2 neighborToCheck = new Vector2(Vector2ToCheck.x + offset.x, Vector2ToCheck.y + offset.y); //make sure it is inside the maze, and it hasn't been dug out yet if (IsInside(neighborToCheck) Maze[(int)neighborToCheck.x, (int)neighborToCheck.y] == 1) { intactWallCounter++; } } //tell whether three walls are intact return intactWallCounter == 3; } private bool IsInside(Vector2 p) { return p.x >= 0 p.y >= 0 p.x < width p.y < height; } }
Hi Tombali, I just purchased your crossword game from the Asset store. Thanks it will go together well with the other kids games I am putting together. Thank as well for sharing your maze code. I am looking to make some changes to the maze generation code so that it will a) have multiple entry/exits and b) connect a few paths so there are not to many long routes. I would also like to remove the ridges if possible. Are you able to help with this? iByte
That will save me a lot of time. Thank you! Why did you use System.Random and not Unity Random class?
Great script, I was wondering is it possible to change the game object to that it uses to a predefined prefab object.
Indeed it is, just put your prefab in the 'wall' slot in the inspector: Code (csharp): using UnityEngine; using System; using System.Collections; using System.Collections.Generic; public class MazeGenerator : MonoBehaviour { public GameObject wall; public int width, height; private int[,] Maze; private List<Vector3> pathMazes = new List<Vector3>(); private Stack<Vector2> _tiletoTry = new Stack<Vector2>(); private List<Vector2> offsets = new List<Vector2> { new Vector2(0, 1), new Vector2(0, -1), new Vector2(1, 0), new Vector2(-1, 0) }; private System.Random rnd = new System.Random(); private int _width, _height; private Vector2 _currentTile; public Vector2 CurrentTile { get { return _currentTile; } private set { if (value.x < 1 || value.x >= this.width - 1 || value.y < 1 || value.y >= this.height - 1) { throw new ArgumentException("CurrentTile must be within the one tile border all around the maze"); } if (value.x % 2 == 1 || value.y % 2 == 1) { _currentTile = value; } else { throw new ArgumentException("The current square must not be both on an even X-axis and an even Y-axis, to ensure we can get walls around all tunnels"); } } } private static MazeGenerator instance; public static MazeGenerator Instance { get { return instance; } } void Awake() { instance = this; } void Start() { Camera.main.orthographic = true; Camera.main.orthographicSize = 30; GenerateMaze(); } void GenerateMaze() { Maze = new int[width, height]; for (int x = 0; x < width; x++) { for (int y = 0; y < height; y++) { Maze[x, y] = 1; } } CurrentTile = Vector2.one; _tiletoTry.Push(CurrentTile); Maze = CreateMaze(); GameObject ptype = null; for (int i = 0; i <= Maze.GetUpperBound(0); i++) { for (int j = 0; j <= Maze.GetUpperBound(1); j++) { if (Maze[i, j] == 1) { ptype = wall; Instantiate(wall, new Vector3(i * ptype.transform.localScale.x, j * ptype.transform.localScale.y, 0), Quaternion.identity); } else if (Maze[i, j] == 0) { pathMazes.Add(new Vector3(i, j, 0)); } } } } public int[,] CreateMaze() { //local variable to store neighbors to the current square //as we work our way through the maze List<Vector2> neighbors; //as long as there are still tiles to try while (_tiletoTry.Count > 0) { //excavate the square we are on Maze[(int)CurrentTile.x, (int)CurrentTile.y] = 0; //get all valid neighbors for the new tile neighbors = GetValidNeighbors(CurrentTile); //if there are any interesting looking neighbors if (neighbors.Count > 0) { //remember this tile, by putting it on the stack _tiletoTry.Push(CurrentTile); //move on to a random of the neighboring tiles CurrentTile = neighbors[rnd.Next(neighbors.Count)]; } else { //if there were no neighbors to try, we are at a dead-end //toss this tile out //(thereby returning to a previous tile in the list to check). CurrentTile = _tiletoTry.Pop(); } } return Maze; } /// <summary> /// Get all the prospective neighboring tiles /// </summary> /// <param name="centerTile">The tile to test</param> /// <returns>All and any valid neighbors</returns> private List<Vector2> GetValidNeighbors(Vector2 centerTile) { List<Vector2> validNeighbors = new List<Vector2>(); //Check all four directions around the tile foreach (var offset in offsets) { //find the neighbor's position Vector2 toCheck = new Vector2(centerTile.x + offset.x, centerTile.y + offset.y); //make sure the tile is not on both an even X-axis and an even Y-axis //to ensure we can get walls around all tunnels if (toCheck.x % 2 == 1 || toCheck.y % 2 == 1) { //if the potential neighbor is unexcavated (==1) //and still has three walls intact (new territory) if (Maze[(int)toCheck.x, (int)toCheck.y] == 1 HasThreeWallsIntact(toCheck)) { //add the neighbor validNeighbors.Add(toCheck); } } } return validNeighbors; } /// <summary> /// Counts the number of intact walls around a tile /// </summary> /// <param name="Vector2ToCheck">The coordinates of the tile to check</param> /// <returns>Whether there are three intact walls (the tile has not been dug into earlier.</returns> private bool HasThreeWallsIntact(Vector2 Vector2ToCheck) { int intactWallCounter = 0; //Check all four directions around the tile foreach (var offset in offsets) { //find the neighbor's position Vector2 neighborToCheck = new Vector2(Vector2ToCheck.x + offset.x, Vector2ToCheck.y + offset.y); //make sure it is inside the maze, and it hasn't been dug out yet if (IsInside(neighborToCheck) Maze[(int)neighborToCheck.x, (int)neighborToCheck.y] == 1) { intactWallCounter++; } } //tell whether three walls are intact return intactWallCounter == 3; } private bool IsInside(Vector2 p) { return p.x >= 0 p.y >= 0 p.x < width p.y < height; } }
Hi, I attached this script to a cylinder and it doesn't work. ArgumentException: CurrentTile must be within the one tile border all around the maze MazeGenerator.set_CurrentTile (Vector2 value) (at Assets/MazeGenerator.cs:45) MazeGenerator.GenerateMaze () (at Assets/MazeGenerator.cs:129) MazeGenerator.Start () (at Assets/MazeGenerator.cs:103) What kind of object do I need to attach to? Thanks.
booyu, I had no issues, I attached the script to an empty game object (zeroed out the position) and set the width/height in the inspector, put the material in the slot, pressed play, and like magic, a maze appeared. Thanks for the code tombali. -Raiden
I can't get this to work at all, I created a new project, placed a cube and added the script to it, these compiler errors appear: Assets/MazeGenerator.cs(143,97): error CS1525: Unexpected symbol `HasThreeWallsIntact' Assets/MazeGenerator.cs(151,22): error CS1519: Unexpected symbol `return' in class, struct, or interface member declaration Assets/MazeGenerator.cs(151,38): error CS1519: Unexpected symbol `;' in class, struct, or interface member declaration Assets/MazeGenerator.cs(171,59): error CS1525: Unexpected symbol `Maze' Assets/MazeGenerator.cs(160,22): error CS0116: A namespace can only contain types and namespace declarations Assets/MazeGenerator.cs(178,22): error CS8025: Parsing error What am I doing wrong here? Thanks for you help.
Thanks hpjohn, you were right, they should have read: Code (CSharp): if (Maze[(int)toCheck.x, (int)toCheck.y] == 1 && HasThreeWallsIntact(toCheck)) and Code (CSharp): if (IsInside(neighborToCheck) && Maze[(int)neighborToCheck.x, (int)neighborToCheck.y] == 1)
What is this error! NullReferenceException: Object reference not set to an instance of an object MazeGenerator.Start () (at Assets/asset/Scripts/MazeGenerator.cs:50)
It means exactly what it says it means. Something is not set to a instance. EG something is null Since it says it's coming from start the only thing that seems logical to be null is the camera. Make sure your camera has the 'Main Camera' tag
How do I get unity to shut up about the p? Assets/Maze.cs(189,34): error CS1525: Unexpected symbol `p'
Something went wrong with the code posting/migration There are missing operators, read the thread (but no one noticed them missing on that line yet)
OK. I found this script had problems too... so I fixed them. Make a cube 1x1x1 , zero it out in space, add this script, set the width/height in the inspector, put the material in the slot, and press play. Hope this helps. I added a MazeString = MazeString+"X"; where all X = blocks and 0 = spaces. I think I can use the public String to create a maze in js, that I can make rooms, add doors, etc, BEFORE making the walls. ============== Code (CSharp): // remember you can NOT have even numbers of height or width in this style of block maze // to ensure we can get walls around all tunnels... so use 21 x 13 , or 7 x 7 for examples. using UnityEngine; using System; using System.Collections; using System.Collections.Generic; public class MazeGenerator : MonoBehaviour { public int width, height; public Material brick; private int[,] Maze; private List<Vector3> pathMazes = new List<Vector3>(); private Stack<Vector2> _tiletoTry = new Stack<Vector2>(); private List<Vector2> offsets = new List<Vector2> { new Vector2(0, 1), new Vector2(0, -1), new Vector2(1, 0), new Vector2(-1, 0) }; private System.Random rnd = new System.Random(); private int _width, _height; private Vector2 _currentTile; public String MazeString; public Vector2 CurrentTile { get { return _currentTile; } private set { if (value.x < 1 || value.x >= this.width - 1 || value.y < 1 || value.y >= this.height - 1){ throw new ArgumentException("Width and Height must be greater than 2 to make a maze"); } _currentTile = value; } } private static MazeGenerator instance; public static MazeGenerator Instance { get {return instance;} } void Awake() { instance = this;} void Start() { MakeBlocks(); } // end of main program // ============= subroutines ============ void MakeBlocks() { Maze = new int[width, height]; for (int x = 0; x < width; x++) { for (int y = 0; y < height; y++) { Maze[x, y] = 1; } } CurrentTile = Vector2.one; _tiletoTry.Push(CurrentTile); Maze = CreateMaze(); // generate the maze in Maze Array. GameObject ptype = null; for (int i = 0; i <= Maze.GetUpperBound(0); i++) { for (int j = 0; j <= Maze.GetUpperBound(1); j++) { if (Maze[i, j] == 1) { MazeString=MazeString+"X"; // added to create String ptype = GameObject.CreatePrimitive(PrimitiveType.Cube); ptype.transform.position = new Vector3(i * ptype.transform.localScale.x, 0, j * ptype.transform.localScale.z); if (brick != null) { ptype.renderer.material = brick; } ptype.transform.parent = transform; } else if (Maze[i, j] == 0) { MazeString=MazeString+"0"; // added to create String pathMazes.Add(new Vector3(i, 0, j)); } } MazeString=MazeString+"\n"; // added to create String } print (MazeString); // added to create String } // ======================================= public int[,] CreateMaze() { //local variable to store neighbors to the current square as we work our way through the maze List<Vector2> neighbors; //as long as there are still tiles to try while (_tiletoTry.Count > 0) { //excavate the square we are on Maze[(int)CurrentTile.x, (int)CurrentTile.y] = 0; //get all valid neighbors for the new tile neighbors = GetValidNeighbors(CurrentTile); //if there are any interesting looking neighbors if (neighbors.Count > 0) { //remember this tile, by putting it on the stack _tiletoTry.Push(CurrentTile); //move on to a random of the neighboring tiles CurrentTile = neighbors[rnd.Next(neighbors.Count)]; } else { //if there were no neighbors to try, we are at a dead-end toss this tile out //(thereby returning to a previous tile in the list to check). CurrentTile = _tiletoTry.Pop(); } } print("Maze Generated ..."); return Maze; } // ================================================ // Get all the prospective neighboring tiles "centerTile" The tile to test // All and any valid neighbors</returns> private List<Vector2> GetValidNeighbors(Vector2 centerTile) { List<Vector2> validNeighbors = new List<Vector2>(); //Check all four directions around the tile foreach (var offset in offsets) { //find the neighbor's position Vector2 toCheck = new Vector2(centerTile.x + offset.x, centerTile.y + offset.y); //make sure the tile is not on both an even X-axis and an even Y-axis //to ensure we can get walls around all tunnels if (toCheck.x % 2 == 1 || toCheck.y % 2 == 1) { //if the potential neighbor is unexcavated (==1) //and still has three walls intact (new territory) if (Maze[(int)toCheck.x, (int)toCheck.y] == 1 && HasThreeWallsIntact(toCheck)) { //add the neighbor validNeighbors.Add(toCheck); } } } return validNeighbors; } // ================================================ // Counts the number of intact walls around a tile //"Vector2ToCheck">The coordinates of the tile to check //Whether there are three intact walls (the tile has not been dug into earlier. private bool HasThreeWallsIntact(Vector2 Vector2ToCheck) { int intactWallCounter = 0; //Check all four directions around the tile foreach (var offset in offsets) { //find the neighbor's position Vector2 neighborToCheck = new Vector2(Vector2ToCheck.x + offset.x, Vector2ToCheck.y + offset.y); //make sure it is inside the maze, and it hasn't been dug out yet if (IsInside(neighborToCheck) && Maze[(int)neighborToCheck.x, (int)neighborToCheck.y] == 1) { intactWallCounter++; } } //tell whether three walls are intact return intactWallCounter == 3; } // ================================================ private bool IsInside(Vector2 p) { //return p.x >= 0 p.y >= 0 p.x < width p.y < height; return p.x >= 0 && p.y >= 0 && p.x < width && p.y < height; } }
thanks for script softwizz...after little erros fix script is working././But however its not creating maze..I have created a gameobject,added sprite rendere to it.n 2d collider (created prefab of this) and attached script to it and dragged prefab..its creating boxes around itself...so in short its scaling itself..not creating maze..would appreciate if u could throw some light...thanks
[/QUOTE] What r u trying to say here? Do u need help with those errors? Just add '&&' and it will work...
Is there any way to modify the walls themselves, like how high/low they are, or how much space to have for walking room in the maze?
Assets/Scripts/MazeGenerator.cs(186,34): error CS1525: Unexpected symbol `p' private bool IsInside(Vector2 p) { return p.x >= 0 p.y >= 0 p.x < width p.y < height;
Only the 1st version worked, the others freeze unity, (4.6 and 5) no errors, did I copy/paste right? You can save the mazes by dragging them into/project/ prefab from hierarchy
Hi all , Can someone tell me how to modify the walls in the script please, like the scale because I find it a bit small. Thank you
I've been asked a few times about my modifications to this script. Note: I did not write the original quick maze generator script, but i did modify it. Upon opening this script in Unity 5, I got this message: "UnityEngine.GameObject.renderer' is obsolete: `Property renderer has been deprecated. Use GetComponent<Renderer>() instead." , then got "Can't add script component... make sure the file name and class name match". So I renamed the script and class (within the script) mazegen, and fixed the obsolete renderer. Now it is up-to-date (May 2015). ================== First: Make sure the maze script works on your computer! Drag this 'mazegen.sc' script into the Project window. I then Created a CUBE, (default size 1x1x1), and in the inspector changed position X,Y,Z all to 0 (zeroed it out), pressed 'f' to center the cube in the Scene window, and dragged the script 'mazegen.cs' onto the cube in the Heirarchy window (or onto the cube in the Scene window.) Select 'Create' material, rename it 'grey' and drag the new material onto the cube itself in the Scene window. It helps to view it with shading so I added a Directional light. (pic1) Finally select the Cube, and note the properties in the Inspector window, scroll down to see the Mazegen (Script). Set Width and Height (both need to be odd numbers), and set the Brick material as the 'grey' material. Remember you can make this material anything you want. Just press PLAY, and you'll see the maze. (pic2) Note: If you go with any EVEN numbers you'll get those 'Saw-Edge' lines (pic3), so use only ODD numbers. I know the above instructions are too basic, but we just want to be clear about what we're doing. If you get this far, and see a maze (pic2) then the simple maze generator script has done it's job. This is not a complete game, or even a playable demo, but only a MAZE GENERATOR. What you do with this maze is how you can make a game to play. ================ Modifying the generated MAZE - Method 1. In order to scale it larger, add a variable 'scale'. line 10 : public int scale; and use it to modify localScale. line 58 : ptype.transform.localScale += new Vector3(scale,scale,scale); I set the scale at '2' in the inspector and pressed play : see (pic4) You can change the height of 'Y' = Vector3 ( X , Y , Z ) line 58 : ptype.transform.localScale += new Vector3(scale,scale*3,scale); Press play and the Y is now 6 units (scale variable = 2, x 3) : see (pic5) ================= Modifying the generated MAZE - Method 2. An advanced method is to have the maze generator algorithyms create an ARRAY that you can see, and modify, before creating the maze.This script makes use of a C# ARRAY function line 42 : Maze = new int[width, height]; My preference is that I like to use javascript (UnityScript), and I could not use a multi-dimensional array in js (an array with width AND height), so a string works well. After running my script once, open the Console window, and see my array of data in the form of a String >> MazeString=MazeString+"."; // added to create String . See (pic6). On the right I have grabbed the string, and viewed it in a fixed width font, so we can see that the walls are the 'X' s and the spaces are the periods. Disable the 'mazegen.cs' script from making blocks in your scene. use // in front of lines 10, 12, 51, 56, 57, 59, 60, 64. When this script runs it will now only generate the 'MazeString'. Because the Awake function calls MakeBlocks(), it is run before Start functions. Check in the Console window that it is printing the string out. Using the same iteration format to get the data out of the String, I wrote another script in JavaScript that will actually create the maze blocks in the Scene window. Now you should have a cube with 'Mazegen (Script)' , and 'Makemaze (Script)' attached. Set Mazecube : GameObject as itself, set the Cubesize (I use 3), and Brick material. When you play the game, you'll see red and white cubes (pic7). I made a couple of lines of codes to show where the red blocks are. An interesting thing about this maze generator, is that is in this style of BLOCKS for a mazes, rather than long thin WALLS, is that every red block will ALWAYS be there; they are like the frame of the maze. You can remove any single grey block and you'll never be stuck; if you remove a red block and put a treasure, trap, door, or exit there it COULD be blocked by a random block placed by the maze generator. To make an entrance or exit, pre-choose a GREY block along the perimeter, and then using the function RemoveBlocks(mazearray), remove the block BEFORE the maze is placed with function makeMaze(). You can also use the RemoveBlocks sub-routine to clear a larger area inside the maze. For the floor just make a large plane or flat cube as the floor. I hope this gives many of you designers good insight into my method of making random mazes. - Michio ================= Code (CSharp): // mazegen.cs // remember you can NOT have even numbers of height or width in this style of block maze // to ensure we can get walls around all tunnels... so use 21 x 13 , or 7 x 7 for examples. using UnityEngine; using System; using System.Collections; using System.Collections.Generic; public class mazegen : MonoBehaviour { public int width, height; public Material brick; private int[,] Maze; private Stack<Vector2> _tiletoTry = new Stack<Vector2>(); private List<Vector2> offsets = new List<Vector2> { new Vector2(0, 1), new Vector2(0, -1), new Vector2(1, 0), new Vector2(-1, 0) }; private System.Random rnd = new System.Random(); private int _width, _height; private Vector2 _currentTile; public static String MazeString; public Vector2 CurrentTile { get { return _currentTile; } private set { if (value.x < 1 || value.x >= this.width - 1 || value.y < 1 || value.y >= this.height - 1){ throw new ArgumentException("Width and Height must be greater than 2 to make a maze"); } _currentTile = value; } } private static mazegen instance; public static mazegen Instance { get {return instance;} } void Awake() { instance = this; MakeBlocks(); } // end of main program // ============= subroutines ============ void MakeBlocks() { Maze = new int[width, height]; for (int x = 0; x < width; x++) { for (int y = 0; y < height; y++) { Maze[x, y] = 1; } } CurrentTile = Vector2.one; _tiletoTry.Push(CurrentTile); Maze = CreateMaze(); // generate the maze in Maze Array. GameObject ptype = null; for (int i = 0; i <= Maze.GetUpperBound(0); i++) { for (int j = 0; j <= Maze.GetUpperBound(1); j++) { if (Maze[i, j] == 1) { MazeString=MazeString+"X"; // added to create String ptype = GameObject.CreatePrimitive(PrimitiveType.Cube); ptype.transform.position = new Vector3(i * ptype.transform.localScale.x, 0, j * ptype.transform.localScale.z); if (brick != null) { ptype.GetComponent<Renderer>().material = brick; } ptype.transform.parent = transform; } else if (Maze[i, j] == 0) { MazeString=MazeString+"."; // added to create String } } MazeString=MazeString+"\n"; // added to create String } print (MazeString); // added to create String } // ======================================= public int[,] CreateMaze() { //local variable to store neighbors to the current square as we work our way through the maze List<Vector2> neighbors; //as long as there are still tiles to try while (_tiletoTry.Count > 0) { //excavate the square we are on Maze[(int)CurrentTile.x, (int)CurrentTile.y] = 0; //get all valid neighbors for the new tile neighbors = GetValidNeighbors(CurrentTile); //if there are any interesting looking neighbors if (neighbors.Count > 0) { //remember this tile, by putting it on the stack _tiletoTry.Push(CurrentTile); //move on to a random of the neighboring tiles CurrentTile = neighbors[rnd.Next(neighbors.Count)]; } else { //if there were no neighbors to try, we are at a dead-end toss this tile out //(thereby returning to a previous tile in the list to check). CurrentTile = _tiletoTry.Pop(); } } print("Maze Generated ..."); return Maze; } // ================================================ // Get all the prospective neighboring tiles "centerTile" The tile to test // All and any valid neighbors</returns> private List<Vector2> GetValidNeighbors(Vector2 centerTile) { List<Vector2> validNeighbors = new List<Vector2>(); //Check all four directions around the tile foreach (var offset in offsets) { //find the neighbor's position Vector2 toCheck = new Vector2(centerTile.x + offset.x, centerTile.y + offset.y); //make sure the tile is not on both an even X-axis and an even Y-axis //to ensure we can get walls around all tunnels if (toCheck.x % 2 == 1 || toCheck.y % 2 == 1) { //if the potential neighbor is unexcavated (==1) //and still has three walls intact (new territory) if (Maze[(int)toCheck.x, (int)toCheck.y] == 1 && HasThreeWallsIntact(toCheck)) { //add the neighbor validNeighbors.Add(toCheck); } } } return validNeighbors; } // ================================================ // Counts the number of intact walls around a tile //"Vector2ToCheck">The coordinates of the tile to check //Whether there are three intact walls (the tile has not been dug into earlier. private bool HasThreeWallsIntact(Vector2 Vector2ToCheck) { int intactWallCounter = 0; //Check all four directions around the tile foreach (var offset in offsets) { //find the neighbor's position Vector2 neighborToCheck = new Vector2(Vector2ToCheck.x + offset.x, Vector2ToCheck.y + offset.y); //make sure it is inside the maze, and it hasn't been dug out yet if (IsInside(neighborToCheck) && Maze[(int)neighborToCheck.x, (int)neighborToCheck.y] == 1) { intactWallCounter++; } } //tell whether three walls are intact return intactWallCounter == 3; } // ================================================ private bool IsInside(Vector2 p) { //return p.x >= 0 p.y >= 0 p.x < width p.y < height; return p.x >= 0 && p.y >= 0 && p.x < width && p.y < height; } } =================== Code (JavaScript): // makemaze.js - MichShire Feb2015 var mazecube : GameObject; var cubesize=3; var brick : Material; private var MazeString : String = ""; private var width : int; private var height : int; private static var mazearray : Array; private var t : boolean = false ; function Start() { MazeString = mazecube.GetComponent("mazegen").MazeString; width = mazecube.GetComponent("mazegen").width; height = mazecube.GetComponent("mazegen").height; print ("mazegen width = " + width + " height = " + height +" array = \n" +MazeString); makeMaze(); } function makeMaze() { var mazearray = MazeString.Split("\n"[0]); //print (mazearray[0]); //mod = mazearray[0]; index = 9; //mazearray[0] = mod.Substring(0, index) + '0' + mod.Substring(index + 1); RemoveBlocks(mazearray); for (var i : int = 0; i <width; i++) { //print ("array " + i + " = " + mazearray[i]); for (var j : int = 0; j <height; j++) { var st=mazearray[i]; //print ("mazei= " + i + mazearray[i] ); //print ("substring= " + st.Substring(0,1) ); if (st.Substring(j,1)=="X") { // make a block if 'X' ... ptype = GameObject.CreatePrimitive(PrimitiveType.Cube); ptype.transform.position = new Vector3(j * cubesize, .5+(cubesize/2), i *cubesize); ptype.transform.localScale = new Vector3(cubesize, cubesize, cubesize); if (brick != null) { ptype.GetComponent.<Renderer>().material = brick; } ptype.transform.parent = transform; } // just to show colored blocks every second block t=!t; if (t==true && (i==0 || i==2 || i==4 ||i==6 || i==8 || i==10 || i==12)){ // ptype.GetComponent.<Renderer>().material.color = Color.red; } } } //t=1; return; } // ==================== function RemoveBlocks(mazearray) { var mod; var index; //print (mazearray[0]); // entrance mod = mazearray[0]; index = 1; mazearray[0] = mod.Substring(0, index) + '0' + mod.Substring(index + 1); // exit mod = mazearray[12]; index = 7; mazearray[12] = mod.Substring(0, index) + '0' + mod.Substring(index + 1); }
This is my code for generate a random maze Code (csharp): using UnityEngine; using System.Collections; using System.Collections.Generic; using System.Linq; public class Cell { public bool isVisited; public GameObject north; public GameObject south; public GameObject east; public GameObject west; public GameObject floor; public int current; //Debug public Vector3 cellPosition; //Debug } public class GeneratorMaze : MonoBehaviour { [Range(3, 200)] public int xSize;//size axis X [Range(3, 200)] public int ySize;//size axis Y [Range(1, 50)] public int sizeWall; //Heigth and Width Wall public GameObject wall; //GameObject Wall private GameObject Floor; //Create GameObject Floor public GameObject floor;//GameObject Floor private Vector3 initialPos; //start position Maze private GameObject[] walls; private GameObject[] floors; private float middle; private GameObject Maze; public List<Cell> cells; private int eastLimited; private int southLimited; private int northLimited; private int westLimited; private int currentRow; public List<Cell> lastCellVisited; private int indexCell; private int currentI; private Cell currentCell; private List<Cell> neighboringCell; private GameObject parent; private int range; void Start() { Create(); } void Update() { } void Create() { //CELL INIT middle = (float)sizeWall / 2; initialPos = Vector3.zero; this.transform.position = initialPos; lastCellVisited = new List<Cell>(); neighboringCell = new List<Cell>(); cells = new List<Cell>(); currentI = 0; range = sizeWall; wall.transform.localScale = new Vector3(0.1f, sizeWall, sizeWall); floor.transform.localScale = new Vector3(sizeWall, sizeWall, sizeWall); createWalls(); createFloor(); createCell(); // this.transform.position = new Vector3(cells[(xSize / 2)].floor.transform.position.x, 0, cells[(ySize / 2)].floor.transform.position.x); } //I create labyrinth floor void createFloor() { Floor = new GameObject("Floor"); //group all the Quad to keep everything in order for (int i = 0; i < ySize; i++) { for (int j = 0; j < xSize; j++) { var insFloor = Instantiate(floor, new Vector3((j * sizeWall) + middle, -middle, i * sizeWall), Quaternion.Euler(90, 0, 0)) as GameObject; insFloor.transform.parent = Floor.transform; //group all the Quad to keep everything in order indexCell = (i * xSize) + j; //I transform the array into an array insFloor.transform.name = indexCell.ToString(); } } } //I create labyrinth walls void createWalls() { Maze = new GameObject("Maze"); //group all the Quad to keep everything in order //X for (int i = 0; i < ySize; i++) { for (int j = 0; j <= xSize; j++) { GameObject wallX = Instantiate(wall, new Vector3(initialPos.x + (j * sizeWall), 0, initialPos.z + (i * sizeWall)), Quaternion.identity) as GameObject; wallX.transform.parent = Maze.transform; } } //Y for (int i = 0; i <= ySize; i++) { for (int j = 0; j < xSize; j++) { GameObject wallY = Instantiate(wall, new Vector3(initialPos.x + (j * sizeWall) + middle, 0, initialPos.z + (i * sizeWall) - middle), Quaternion.Euler(0, 90, 0)) as GameObject; wallY.transform.parent = Maze.transform; } } } //I create the cell, check the fees for each cell walls and floors void createCell() { walls = new GameObject[Maze.transform.childCount]; floors = new GameObject[xSize * ySize]; int south = (xSize + 1) * ySize; int north = ((xSize + 1) * ySize) + xSize; for (int i = 0; i < Maze.transform.childCount; i++) { walls[i] = Maze.transform.GetChild(i).gameObject; } for (int i = 0; i < xSize * ySize; i++) { floors[i] = Floor.transform.GetChild(i).gameObject; } for (int i = 0; i < xSize * ySize; i++) { cells.Add(new Cell()); currentRow = (i / xSize); //find current row for every cell cells[i].south = walls[i + south]; cells[i].north = walls[i + north]; cells[i].east = walls[(currentRow + i) + 1]; cells[i].west = walls[currentRow + i]; cells[i].floor = floors[i]; cells[i].current = i; } mazeCreation(cells); } // Add all of the cells of the current cell that I can visit void AddNears(Cell currentCell) { int currentRow = (currentCell.current / xSize) + 1; int eastLimited = currentCell.current % xSize; int westLimited = eastLimited; int southLimited = xSize - 1; int northLimited = currentCell.current / xSize; //East if (eastLimited != xSize - 1) { if (cells[currentCell.current + 1].isVisited == false) neighboringCell.Add(cells[currentCell.current + 1]); } //West if (westLimited != 0) { if (cells[currentCell.current - 1].isVisited == false) neighboringCell.Add(cells[currentCell.current - 1]); } //South if (currentCell.current > southLimited) { if (cells[currentCell.current - xSize].isVisited == false) neighboringCell.Add(cells[currentCell.current - xSize]); } //North if (northLimited != ySize - 1) { if (cells[currentCell.current + xSize].isVisited == false) neighboringCell.Add(cells[currentCell.current + xSize]); } } //It breaks down the wall between one cell and the other void breakWall(Cell curr, Cell choose) { //East if (choose.current == curr.current + 1) { Destroy(curr.east); currentCell = cells[currentCell.current + 1]; } //West if (choose.current == curr.current - 1) { Destroy(curr.west); currentCell = cells[currentCell.current - 1]; } //South if (choose.current == curr.current - xSize) { Destroy(curr.south); currentCell = cells[currentCell.current - xSize]; } //North if (choose.current == curr.current + xSize) { Destroy(curr.north); currentCell = cells[currentCell.current + xSize]; } } //maze creation algorithm void mazeCreation(List<Cell> allCells) { int visitedCell = 0; int random = Random.Range(0, allCells.Count); //I choose a cell at random currentCell = allCells[random]; //the choice cell becomes the current cell while (visitedCell < allCells.Count) //I run the cycle for all the cells of the matrix { AddNears(currentCell); //add to the selection cell neighboring cells int randomNeighboring = Random.Range(0, neighboringCell.Count); //I choose a neighboring cell at random if (neighboringCell.Any()) //if there are cells neighboring the current cell { lastCellVisited.Add(currentCell); //becomes visited breakWall(currentCell, neighboringCell[randomNeighboring]); //Break the wall neighboringCell.Clear(); //Reset the list of neighboring cells visitedCell++; currentCell.isVisited = true; } else { //else I choose a already been visited cell random = Random.Range(0, lastCellVisited.Count); currentCell = lastCellVisited[random]; } } } }
@Bolt Digging this script. I spent a day with my head in mazes and procedural generation. I have added my own fine tuning to it, I'd love to chat about the code more. I have some questions about the algorithm you used and the structure, choices in your code. I adjusted where you were doing the localScale change. I didn't want to change the PreFabs localScale (97, 98), it changes the prefab itself, so I just made the adjustment to the instances : insFloor, wallX, wallY; this prevents any changes to the prefab itself.
Hello, I have a question regarding the maze creation, are you using any specific famous algorithms to create the maze? or the code is completely created by you.
I did a few Edits myself with these instead of starting the game every time you want to generate one maze, you simply press a button and you get x amount of them in a folder as prefabs (Obs the new script needs to be in the "Editor" folder and the other script should be there when you compiling the game, not when using it in the editor though). I also removed the singelton functions which I am unsure why they are there in the first place since there was no function or variables one might need outside the script :/ I have also corrected some missing "&&" operations and another syntax that didn't work when I implemented the script to my unity scene Edit: wow I just noticed this is an old thread hope didn't bother anyone when posting this Code (CSharp): using System.Collections; using System.Collections; using System.Collections.Generic; using UnityEngine; using System; using UnityEditor; using System.IO; public class MazeGenerator : MonoBehaviour { public int width, height; public Material brick; private int[,] Maze; private List<Vector3> pathMazes = new List<Vector3>(); private Stack<Vector2> _tiletoTry = new Stack<Vector2>(); private List<Vector2> offsets = new List<Vector2> { new Vector2(0, 1), new Vector2(0, -1), new Vector2(1, 0), new Vector2(-1, 0) }; private System.Random rnd = new System.Random(); private int _width, _height; private Vector2 _currentTile; [SerializeField] int amountOfMazes = 1; public Vector2 CurrentTile { get { return _currentTile; } private set { if (value.x < 1 || value.x >= this.width - 1 || value.y < 1 || value.y >= this.height - 1) { throw new ArgumentException("CurrentTile must be within the one tile border all around the maze"); } if (value.x % 2 == 1 || value.y % 2 == 1) { _currentTile = value; } else { throw new ArgumentException("The current square must not be both on an even X-axis and an even Y-axis, to ensure we can get walls around all tunnels"); } } } private void Start() { Camera.main.orthographic = true; Camera.main.orthographicSize = 30; } GameObject parent; public void GenerateMazes() { if (!Directory.Exists("Assets/Mazes")) { Directory.CreateDirectory("Assets/Mazes"); } for (int i = 0; i < amountOfMazes; i++) { string name = "Maze" + (i + 1).ToString(); GameObject newMaze = new GameObject(name); parent = newMaze; GenerateMaze(); string localPath = "Assets/Mazes/" + name + ".prefab"; PrefabUtility.SaveAsPrefabAssetAndConnect(parent, localPath, InteractionMode.UserAction); DestroyImmediate(parent); } } void GenerateMaze() { Maze = new int[width, height]; for (int x = 0; x < width; x++) { for (int y = 0; y < height; y++) { Maze[x, y] = 1; } } CurrentTile = Vector2.one; _tiletoTry.Push(CurrentTile); Maze = CreateMaze(); GameObject ptype = null; for (int i = 0; i <= Maze.GetUpperBound(0); i++) { for (int j = 0; j <= Maze.GetUpperBound(1); j++) { if (Maze[i, j] == 1) { ptype = GameObject.CreatePrimitive(PrimitiveType.Cube); ptype.transform.position = new Vector3(i * ptype.transform.localScale.x, j * ptype.transform.localScale.y, 0); if (brick != null) { ptype.GetComponent<Renderer>().material = brick; } ptype.transform.parent = parent.transform; } else if (Maze[i, j] == 0) { pathMazes.Add(new Vector3(i, j, 0)); } } } } public int[,] CreateMaze() { //local variable to store neighbors to the current square //as we work our way through the maze List<Vector2> neighbors; //as long as there are still tiles to try while (_tiletoTry.Count > 0) { //excavate the square we are on Maze[(int)CurrentTile.x, (int)CurrentTile.y] = 0; //get all valid neighbors for the new tile neighbors = GetValidNeighbors(CurrentTile); //if there are any interesting looking neighbors if (neighbors.Count > 0) { //remember this tile, by putting it on the stack _tiletoTry.Push(CurrentTile); //move on to a random of the neighboring tiles CurrentTile = neighbors[rnd.Next(neighbors.Count)]; } else { //if there were no neighbors to try, we are at a dead-end //toss this tile out //(thereby returning to a previous tile in the list to check). CurrentTile = _tiletoTry.Pop(); } } return Maze; } /// <summary> /// Get all the prospective neighboring tiles /// </summary> /// <param name="centerTile">The tile to test</param> /// <returns>All and any valid neighbors</returns> private List<Vector2> GetValidNeighbors(Vector2 centerTile) { List<Vector2> validNeighbors = new List<Vector2>(); //Check all four directions around the tile foreach (var offset in offsets) { //find the neighbor's position Vector2 toCheck = new Vector2(centerTile.x + offset.x, centerTile.y + offset.y); //make sure the tile is not on both an even X-axis and an even Y-axis //to ensure we can get walls around all tunnels if (toCheck.x % 2 == 1 || toCheck.y % 2 == 1) { //if the potential neighbor is unexcavated (==1) //and still has three walls intact (new territory) if (Maze[(int)toCheck.x, (int)toCheck.y] == 1 && HasThreeWallsIntact(toCheck)) { //add the neighbor validNeighbors.Add(toCheck); } } } return validNeighbors; } /// <summary> /// Counts the number of intact walls around a tile /// </summary> /// <param name="Vector2ToCheck">The coordinates of the tile to check</param> /// <returns>Whether there are three intact walls (the tile has not been dug into earlier.</returns> private bool HasThreeWallsIntact(Vector2 Vector2ToCheck) { int intactWallCounter = 0; //Check all four directions around the tile foreach (var offset in offsets) { //find the neighbor's position Vector2 neighborToCheck = new Vector2(Vector2ToCheck.x + offset.x, Vector2ToCheck.y + offset.y); //make sure it is inside the maze, and it hasn't been dug out yet if (IsInside(neighborToCheck) && Maze[(int)neighborToCheck.x, (int)neighborToCheck.y] == 1) { intactWallCounter++; } } //tell whether three walls are intact return intactWallCounter == 3; } private bool IsInside(Vector2 p) { return p.x >= 0 && p.y >= 0 && p.x < width && p.y < height; } } here is the other script, once again in the "Editor" folder please, otherwise it won't work; if you are unsure what this folder is, just create a new folder in your asset folder with the name "Editor", unity will take it from there Code (CSharp): using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEditor; [CustomEditor(typeof(MazeGenerator))] public class GeneratorEditor : Editor { public override void OnInspectorGUI() { MazeGenerator myTarget = (MazeGenerator)target; base.OnInspectorGUI(); if (GUILayout.Button("Generate maze")) { myTarget.GenerateMazes(); } } }