Project
Procedural Cave Generation
Generating Content
Tutorial
Advanced
1 Hour10 Mins
Unity Technologies
Overview
You have completed 0% of this Tutorial.
1.
Cellular Automata
2.
Marching Squares
3.
Creating Meshes
Summary
Part 1 of 9: Cellular Automata. In this lesson a random seed is set using "System.Random". This is a valid approach. It is worth noting that Unity also has a Random class and a Random.seed built in. These can be used as an alternative to System.Random.
Topics we'll cover
Language
English
Recommended Unity Versions
5
1.
Cellular Automata
Part 1 of 9: Cellular Automata.
In this lesson a random seed is set using "System.Random". This is a valid approach. It is worth noting that Unity also has a Random class and a Random.seed built in. These can be used as an alternative to System.Random.

MapGenerator

using UnityEngine; using System.Collections; using System; public class MapGenerator : MonoBehaviour { public int width; public int height; public string seed; public bool useRandomSeed; [Range(0,100)] public int randomFillPercent; int[,] map; void Start() { GenerateMap(); } void Update() { if (Input.GetMouseButtonDown(0)) { GenerateMap(); } } void GenerateMap() { map = new int[width,height]; RandomFillMap(); for (int i = 0; i < 5; i ++) { SmoothMap(); } } void RandomFillMap() { if (useRandomSeed) { seed = Time.time.ToString(); } System.Random pseudoRandom = new System.Random(seed.GetHashCode()); for (int x = 0; x < width; x ++) { for (int y = 0; y < height; y ++) { if (x == 0 || x == width-1 || y == 0 || y == height -1) { map[x,y] = 1; } else { map[x,y] = (pseudoRandom.Next(0,100) < randomFillPercent)? 1: 0; } } } } void SmoothMap() { for (int x = 0; x < width; x ++) { for (int y = 0; y < height; y ++) { int neighbourWallTiles = GetSurroundingWallCount(x,y); if (neighbourWallTiles > 4) map[x,y] = 1; else if (neighbourWallTiles < 4) map[x,y] = 0; } } } int GetSurroundingWallCount(int gridX, int gridY) { int wallCount = 0; for (int neighbourX = gridX - 1; neighbourX <= gridX + 1; neighbourX ++) { for (int neighbourY = gridY - 1; neighbourY <= gridY + 1; neighbourY ++) { if (neighbourX >= 0 && neighbourX < width && neighbourY >= 0 && neighbourY < height) { if (neighbourX != gridX || neighbourY != gridY) { wallCount += map[neighbourX,neighbourY]; } } else { wallCount ++; } } } return wallCount; } void OnDrawGizmos() { if (map != null) { for (int x = 0; x < width; x ++) { for (int y = 0; y < height; y ++) { Gizmos.color = (map[x,y] == 1)?Color.black:Color.white; Vector3 pos = new Vector3(-width/2 + x + .5f,0, -height/2 + y+.5f); Gizmos.DrawCube(pos,Vector3.one); } } } } }

2.
Marching Squares
Part 2 of 9: Marching Squares.

MapGenerator

using UnityEngine; using System.Collections; using System; public class MapGenerator : MonoBehaviour { public int width; public int height; public string seed; public bool useRandomSeed; [Range(0,100)] public int randomFillPercent; int[,] map; void Start() { GenerateMap(); } void Update() { if (Input.GetMouseButtonDown(0)) { GenerateMap(); } } void GenerateMap() { map = new int[width,height]; RandomFillMap(); for (int i = 0; i < 5; i ++) { SmoothMap(); } MeshGenerator meshGen = GetComponent<MeshGenerator>(); meshGen.GenerateMesh(map, 1); } void RandomFillMap() { if (useRandomSeed) { seed = Time.time.ToString(); } System.Random pseudoRandom = new System.Random(seed.GetHashCode()); for (int x = 0; x < width; x ++) { for (int y = 0; y < height; y ++) { if (x == 0 || x == width-1 || y == 0 || y == height -1) { map[x,y] = 1; } else { map[x,y] = (pseudoRandom.Next(0,100) < randomFillPercent)? 1: 0; } } } } void SmoothMap() { for (int x = 0; x < width; x ++) { for (int y = 0; y < height; y ++) { int neighbourWallTiles = GetSurroundingWallCount(x,y); if (neighbourWallTiles > 4) map[x,y] = 1; else if (neighbourWallTiles < 4) map[x,y] = 0; } } } int GetSurroundingWallCount(int gridX, int gridY) { int wallCount = 0; for (int neighbourX = gridX - 1; neighbourX <= gridX + 1; neighbourX ++) { for (int neighbourY = gridY - 1; neighbourY <= gridY + 1; neighbourY ++) { if (neighbourX >= 0 && neighbourX < width && neighbourY >= 0 && neighbourY < height) { if (neighbourX != gridX || neighbourY != gridY) { wallCount += map[neighbourX,neighbourY]; } } else { wallCount ++; } } } return wallCount; } void OnDrawGizmos() { /* if (map != null) { for (int x = 0; x < width; x ++) { for (int y = 0; y < height; y ++) { Gizmos.color = (map[x,y] == 1)?Color.black:Color.white; Vector3 pos = new Vector3(-width/2 + x + .5f,0, -height/2 + y+.5f); Gizmos.DrawCube(pos,Vector3.one); } } } */ } }

MeshGenerator

using UnityEngine; using System.Collections; public class MeshGenerator : MonoBehaviour { public SquareGrid squareGrid; public void GenerateMesh(int[,] map, float squareSize) { squareGrid = new SquareGrid(map, squareSize); } void OnDrawGizmos() { if (squareGrid != null) { for (int x = 0; x < squareGrid.squares.GetLength(0); x ++) { for (int y = 0; y < squareGrid.squares.GetLength(1); y ++) { Gizmos.color = (squareGrid.squares[x,y].topLeft.active)?Color.black:Color.white; Gizmos.DrawCube(squareGrid.squares[x,y].topLeft.position, Vector3.one * .4f); Gizmos.color = (squareGrid.squares[x,y].topRight.active)?Color.black:Color.white; Gizmos.DrawCube(squareGrid.squares[x,y].topRight.position, Vector3.one * .4f); Gizmos.color = (squareGrid.squares[x,y].bottomRight.active)?Color.black:Color.white; Gizmos.DrawCube(squareGrid.squares[x,y].bottomRight.position, Vector3.one * .4f); Gizmos.color = (squareGrid.squares[x,y].bottomLeft.active)?Color.black:Color.white; Gizmos.DrawCube(squareGrid.squares[x,y].bottomLeft.position, Vector3.one * .4f); Gizmos.color = Color.grey; Gizmos.DrawCube(squareGrid.squares[x,y].centreTop.position, Vector3.one * .15f); Gizmos.DrawCube(squareGrid.squares[x,y].centreRight.position, Vector3.one * .15f); Gizmos.DrawCube(squareGrid.squares[x,y].centreBottom.position, Vector3.one * .15f); Gizmos.DrawCube(squareGrid.squares[x,y].centreLeft.position, Vector3.one * .15f); } } } } public class SquareGrid { public Square[,] squares; public SquareGrid(int[,] map, float squareSize) { int nodeCountX = map.GetLength(0); int nodeCountY = map.GetLength(1); float mapWidth = nodeCountX * squareSize; float mapHeight = nodeCountY * squareSize; ControlNode[,] controlNodes = new ControlNode[nodeCountX,nodeCountY]; for (int x = 0; x < nodeCountX; x ++) { for (int y = 0; y < nodeCountY; y ++) { Vector3 pos = new Vector3(-mapWidth/2 + x * squareSize + squareSize/2, 0, -mapHeight/2 + y * squareSize + squareSize/2); controlNodes[x,y] = new ControlNode(pos,map[x,y] == 1, squareSize); } } squares = new Square[nodeCountX -1,nodeCountY -1]; for (int x = 0; x < nodeCountX-1; x ++) { for (int y = 0; y < nodeCountY-1; y ++) { squares[x,y] = new Square(controlNodes[x,y+1], controlNodes[x+1,y+1], controlNodes[x+1,y], controlNodes[x,y]); } } } } public class Square { public ControlNode topLeft, topRight, bottomRight, bottomLeft; public Node centreTop, centreRight, centreBottom, centreLeft; public Square (ControlNode _topLeft, ControlNode _topRight, ControlNode _bottomRight, ControlNode _bottomLeft) { topLeft = _topLeft; topRight = _topRight; bottomRight = _bottomRight; bottomLeft = _bottomLeft; centreTop = topLeft.right; centreRight = bottomRight.above; centreBottom = bottomLeft.right; centreLeft = bottomLeft.above; } } public class Node { public Vector3 position; public int vertexIndex = -1; public Node(Vector3 _pos) { position = _pos; } } public class ControlNode : Node { public bool active; public Node above, right; public ControlNode(Vector3 _pos, bool _active, float squareSize) : base(_pos) { active = _active; above = new Node(position + Vector3.forward * squareSize/2f); right = new Node(position + Vector3.right * squareSize/2f); } } }

3.
Creating Meshes
Creating Meshes.

MapGenerator

using UnityEngine; using System.Collections; using System; public class MapGenerator : MonoBehaviour { public int width; public int height; public string seed; public bool useRandomSeed; [Range(0,100)] public int randomFillPercent; int[,] map; void Start() { GenerateMap(); } void Update() { if (Input.GetMouseButtonDown(0)) { GenerateMap(); } } void GenerateMap() { map = new int[width,height]; RandomFillMap(); for (int i = 0; i < 5; i ++) { SmoothMap(); } MeshGenerator meshGen = GetComponent<MeshGenerator>(); meshGen.GenerateMesh(map, 1); } void RandomFillMap() { if (useRandomSeed) { seed = Time.time.ToString(); } System.Random pseudoRandom = new System.Random(seed.GetHashCode()); for (int x = 0; x < width; x ++) { for (int y = 0; y < height; y ++) { if (x == 0 || x == width-1 || y == 0 || y == height -1) { map[x,y] = 1; } else { map[x,y] = (pseudoRandom.Next(0,100) < randomFillPercent)? 1: 0; } } } } void SmoothMap() { for (int x = 0; x < width; x ++) { for (int y = 0; y < height; y ++) { int neighbourWallTiles = GetSurroundingWallCount(x,y); if (neighbourWallTiles > 4) map[x,y] = 1; else if (neighbourWallTiles < 4) map[x,y] = 0; } } } int GetSurroundingWallCount(int gridX, int gridY) { int wallCount = 0; for (int neighbourX = gridX - 1; neighbourX <= gridX + 1; neighbourX ++) { for (int neighbourY = gridY - 1; neighbourY <= gridY + 1; neighbourY ++) { if (neighbourX >= 0 && neighbourX < width && neighbourY >= 0 && neighbourY < height) { if (neighbourX != gridX || neighbourY != gridY) { wallCount += map[neighbourX,neighbourY]; } } else { wallCount ++; } } } return wallCount; } void OnDrawGizmos() { /* if (map != null) { for (int x = 0; x < width; x ++) { for (int y = 0; y < height; y ++) { Gizmos.color = (map[x,y] == 1)?Color.black:Color.white; Vector3 pos = new Vector3(-width/2 + x + .5f,0, -height/2 + y+.5f); Gizmos.DrawCube(pos,Vector3.one); } } } */ } }

MeshGenerator

using UnityEngine; using System.Collections; using System.Collections.Generic; public class MeshGenerator : MonoBehaviour { public SquareGrid squareGrid; List<Vector3> vertices; List<int> triangles; public void GenerateMesh(int[,] map, float squareSize) { squareGrid = new SquareGrid(map, squareSize); vertices = new List<Vector3>(); triangles = new List<int>(); for (int x = 0; x < squareGrid.squares.GetLength(0); x ++) { for (int y = 0; y < squareGrid.squares.GetLength(1); y ++) { TriangulateSquare(squareGrid.squares[x,y]); } } Mesh mesh = new Mesh(); GetComponent<MeshFilter>().mesh = mesh; mesh.vertices = vertices.ToArray(); mesh.triangles = triangles.ToArray(); mesh.RecalculateNormals(); } void TriangulateSquare(Square square) { switch (square.configuration) { case 0: break; // 1 points: case 1: MeshFromPoints(square.centreBottom, square.bottomLeft, square.centreLeft); break; case 2: MeshFromPoints(square.centreRight, square.bottomRight, square.centreBottom); break; case 4: MeshFromPoints(square.centreTop, square.topRight, square.centreRight); break; case 8: MeshFromPoints(square.topLeft, square.centreTop, square.centreLeft); break; // 2 points: case 3: MeshFromPoints(square.centreRight, square.bottomRight, square.bottomLeft, square.centreLeft); break; case 6: MeshFromPoints(square.centreTop, square.topRight, square.bottomRight, square.centreBottom); break; case 9: MeshFromPoints(square.topLeft, square.centreTop, square.centreBottom, square.bottomLeft); break; case 12: MeshFromPoints(square.topLeft, square.topRight, square.centreRight, square.centreLeft); break; case 5: MeshFromPoints(square.centreTop, square.topRight, square.centreRight, square.centreBottom, square.bottomLeft, square.centreLeft); break; case 10: MeshFromPoints(square.topLeft, square.centreTop, square.centreRight, square.bottomRight, square.centreBottom, square.centreLeft); break; // 3 point: case 7: MeshFromPoints(square.centreTop, square.topRight, square.bottomRight, square.bottomLeft, square.centreLeft); break; case 11: MeshFromPoints(square.topLeft, square.centreTop, square.centreRight, square.bottomRight, square.bottomLeft); break; case 13: MeshFromPoints(square.topLeft, square.topRight, square.centreRight, square.centreBottom, square.bottomLeft); break; case 14: MeshFromPoints(square.topLeft, square.topRight, square.bottomRight, square.centreBottom, square.centreLeft); break; // 4 point: case 15: MeshFromPoints(square.topLeft, square.topRight, square.bottomRight, square.bottomLeft); break; } } void MeshFromPoints(params Node[] points) { AssignVertices(points); if (points.Length >= 3) CreateTriangle(points[0], points[1], points[2]); if (points.Length >= 4) CreateTriangle(points[0], points[2], points[3]); if (points.Length >= 5) CreateTriangle(points[0], points[3], points[4]); if (points.Length >= 6) CreateTriangle(points[0], points[4], points[5]); } void AssignVertices(Node[] points) { for (int i = 0; i < points.Length; i ++) { if (points[i].vertexIndex == -1) { points[i].vertexIndex = vertices.Count; vertices.Add(points[i].position); } } } void CreateTriangle(Node a, Node b, Node c) { triangles.Add(a.vertexIndex); triangles.Add(b.vertexIndex); triangles.Add(c.vertexIndex); } void OnDrawGizmos() { /* if (squareGrid != null) { for (int x = 0; x < squareGrid.squares.GetLength(0); x ++) { for (int y = 0; y < squareGrid.squares.GetLength(1); y ++) { Gizmos.color = (squareGrid.squares[x,y].topLeft.active)?Color.black:Color.white; Gizmos.DrawCube(squareGrid.squares[x,y].topLeft.position, Vector3.one * .4f); Gizmos.color = (squareGrid.squares[x,y].topRight.active)?Color.black:Color.white; Gizmos.DrawCube(squareGrid.squares[x,y].topRight.position, Vector3.one * .4f); Gizmos.color = (squareGrid.squares[x,y].bottomRight.active)?Color.black:Color.white; Gizmos.DrawCube(squareGrid.squares[x,y].bottomRight.position, Vector3.one * .4f); Gizmos.color = (squareGrid.squares[x,y].bottomLeft.active)?Color.black:Color.white; Gizmos.DrawCube(squareGrid.squares[x,y].bottomLeft.position, Vector3.one * .4f); Gizmos.color = Color.grey; Gizmos.DrawCube(squareGrid.squares[x,y].centreTop.position, Vector3.one * .15f); Gizmos.DrawCube(squareGrid.squares[x,y].centreRight.position, Vector3.one * .15f); Gizmos.DrawCube(squareGrid.squares[x,y].centreBottom.position, Vector3.one * .15f); Gizmos.DrawCube(squareGrid.squares[x,y].centreLeft.position, Vector3.one * .15f); } } } */ } public class SquareGrid { public Square[,] squares; public SquareGrid(int[,] map, float squareSize) { int nodeCountX = map.GetLength(0); int nodeCountY = map.GetLength(1); float mapWidth = nodeCountX * squareSize; float mapHeight = nodeCountY * squareSize; ControlNode[,] controlNodes = new ControlNode[nodeCountX,nodeCountY]; for (int x = 0; x < nodeCountX; x ++) { for (int y = 0; y < nodeCountY; y ++) { Vector3 pos = new Vector3(-mapWidth/2 + x * squareSize + squareSize/2, 0, -mapHeight/2 + y * squareSize + squareSize/2); controlNodes[x,y] = new ControlNode(pos,map[x,y] == 1, squareSize); } } squares = new Square[nodeCountX -1,nodeCountY -1]; for (int x = 0; x < nodeCountX-1; x ++) { for (int y = 0; y < nodeCountY-1; y ++) { squares[x,y] = new Square(controlNodes[x,y+1], controlNodes[x+1,y+1], controlNodes[x+1,y], controlNodes[x,y]); } } } } public class Square { public ControlNode topLeft, topRight, bottomRight, bottomLeft; public Node centreTop, centreRight, centreBottom, centreLeft; public int configuration; public Square (ControlNode _topLeft, ControlNode _topRight, ControlNode _bottomRight, ControlNode _bottomLeft) { topLeft = _topLeft; topRight = _topRight; bottomRight = _bottomRight; bottomLeft = _bottomLeft; centreTop = topLeft.right; centreRight = bottomRight.above; centreBottom = bottomLeft.right; centreLeft = bottomLeft.above; if (topLeft.active) configuration += 8; if (topRight.active) configuration += 4; if (bottomRight.active) configuration += 2; if (bottomLeft.active) configuration += 1; } } public class Node { public Vector3 position; public int vertexIndex = -1; public Node(Vector3 _pos) { position = _pos; } } public class ControlNode : Node { public bool active; public Node above, right; public ControlNode(Vector3 _pos, bool _active, float squareSize) : base(_pos) { active = _active; above = new Node(position + Vector3.forward * squareSize/2f); right = new Node(position + Vector3.right * squareSize/2f); } } }

Woohoo! You nailed this tutorial.
Continue rocking your Procedural Cave Generation project.
Next Step:
Walls and Rooms
Tutorial
Advanced
70 Mins
Unity Technologies