Quick maze generator

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…

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;
        }
}
6 Likes

Cool! :slight_smile:

I’d modify it to remove the top row and right column. Those saw-edge lines look silly.

Well, can we see the changes?

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:

    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;
            }
    }
2 Likes

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.

Brackets missing?

I copied and pasted the entire script from this thread, nothing was left out.

Lines 143 and 171 in the code above are missing a boolean operator, either && or ||, i dunno

Thanks hpjohn, you were right, they should have read:

if (Maze[(int)toCheck.x, (int)toCheck.y] == 1 && HasThreeWallsIntact(toCheck))

and

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.

==============

// 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;
        }
}