using System.Collections; using System.Collections.Generic; using System; using UnityEngine; public class CaveGenerator : MonoBehaviour { public int width; public int height; public int smoothnes; public bool cave; public string seed; public bool useRandomSeed; System.Random generator; [Range(1, 100)] public int randomFillPercent; int[,] map; void Start() { if (useRandomSeed) { seed = Time.realtimeSinceStartup.ToString(); } GenerateMap(); } void Update() { if (Input.GetMouseButtonDown(0)) { GenerateMap(); } } public void GenerateMap() { generator = new System.Random(seed.GetHashCode()); map = new int[width, height]; FillMap(); for (int i = 0; i < smoothnes; ++i) { SmoothMap(); } ProcessMap(); int borderSize = 5; int[,] borderedMap = new int[width + borderSize * 2, height + borderSize * 2]; for (int x = 0; x < borderedMap.GetLength(0); ++x) { for (int y = 0; y < borderedMap.GetLength(1); ++y) { if (x >= borderSize && x < width + borderSize && y >= borderSize && y < height + borderSize) { borderedMap[x, y] = map[x - borderSize, y - borderSize]; } else { borderedMap[x, y] = 1; } } } CaveMeshGenerator meshGen = GetComponent(); meshGen.GenerateMesh(borderedMap, 1); } //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); // } // } // } //} void ProcessMap() { List> wallRegions = GetRegions(1); int wallThresholdSize = 30; List survivingRooms = new List(); foreach (List wallRegion in wallRegions) { if (wallRegion.Count < wallThresholdSize) { foreach (Coord tile in wallRegion) { map[tile.tileX, tile.tileY] = 0; } } } List> roomRegions = GetRegions(0); int roomThresholdSize = 30; foreach (List roomRegion in roomRegions) { if (roomRegion.Count < roomThresholdSize) { foreach (Coord tile in roomRegion) { map[tile.tileX, tile.tileY] = 1; } } else { survivingRooms.Add(new Room(roomRegion, map)); } } survivingRooms.Sort(); survivingRooms[0].m_IsMainRoom = survivingRooms[0].m_IsAccessibleFromMainRoom = true; ConnectClosestRooms(survivingRooms); } void ConnectClosestRooms(List allRooms, bool forceAccessibleFromMainRoom = false) { List roomListA = new List(); List roomListB = new List(); if (forceAccessibleFromMainRoom) { foreach (Room room in allRooms) { if (room.m_IsAccessibleFromMainRoom) { roomListB.Add(room); } else { roomListA.Add(room); } } } else { roomListA = allRooms; roomListB = allRooms; } int bestDistance = 0; Coord bestTileA = new Coord(); Coord bestTileB = new Coord(); Room bestRoomA = new Room(); Room bestRoomB = new Room(); bool possibleConnectionFound = false; foreach (Room roomA in roomListA) { if (!forceAccessibleFromMainRoom) { possibleConnectionFound = false; if (roomA.m_ConnectedRooms.Count > 0) { continue; } } foreach (Room roomB in roomListB) { if (roomA == roomB || roomA.IsConnected(roomB)) { continue; } for (int tileIndexA = 0; tileIndexA < roomA.m_EdgeTiles.Count; ++tileIndexA) { for (int tileIndexB = 0; tileIndexB < roomB.m_EdgeTiles.Count; ++tileIndexB) { Coord tileA = roomA.m_EdgeTiles[tileIndexA]; Coord tileB = roomB.m_EdgeTiles[tileIndexB]; int distanceBetweenRooms = (int)(Mathf.Pow(tileA.tileX - tileB.tileX, 2) + Mathf.Pow(tileA.tileY - tileB.tileY, 2)); if (distanceBetweenRooms < bestDistance || !possibleConnectionFound) { bestDistance = distanceBetweenRooms; possibleConnectionFound = true; bestTileA = tileA; bestTileB = tileB; bestRoomA = roomA; bestRoomB = roomB; } } } } if (possibleConnectionFound && !forceAccessibleFromMainRoom) { CreatePassage(bestRoomA, bestRoomB, bestTileA, bestTileB); } } if (forceAccessibleFromMainRoom && possibleConnectionFound) { CreatePassage(bestRoomA, bestRoomB, bestTileA, bestTileB); ConnectClosestRooms(allRooms, true); } if (!forceAccessibleFromMainRoom) { ConnectClosestRooms(allRooms, true); } } void CreatePassage(Room roomA, Room roomB, Coord tileA, Coord tileB) { Room.ConnectRooms(roomA, roomB); if (cave) { List line = GetLine(tileA, tileB); foreach (Coord c in line) { DrawCircle(c, 4); } } else { if (tileA.tileX < tileB.tileX) { for (int x = tileA.tileX; x < tileB.tileX; ++x) { DrawSquare(new Coord(x, tileA.tileY), 1); } } else { for (int x = tileB.tileX; x < tileA.tileX; ++x) { DrawSquare(new Coord(x, tileA.tileY), 1); } } if (tileA.tileY < tileB.tileY) { for (int y = tileA.tileY; y < tileB.tileY; ++y) { DrawSquare(new Coord(tileB.tileX, y), 1); } } else { for (int y = tileB.tileY; y < tileA.tileY; ++y) { DrawSquare(new Coord(tileB.tileX, y), 1); } } } } void DrawSquare(Coord c, int d) { for (int x = -d; x <= d; ++x) { for (int y = -d; y <= d; ++y) { int drawX = c.tileX + x; int drawY = c.tileY + y; if (CheckIfInMapBounds(drawX, drawY)) { map[drawX, drawY] = 0; } } } } void DrawCircle(Coord c, int r) { for(int x = -r; x <= r; ++x) { for (int y = -r; y <= r; ++y) { if (x * x + y * y <= r * r) { int drawX = c.tileX + x; int drawY = c.tileY + y; if (CheckIfInMapBounds(drawX, drawY)) { map[drawX, drawY] = 0; } } } } } List GetLine(Coord from, Coord to) { List line = new List(); int x = from.tileX; int y = from.tileY; int dx = to.tileX - x; int dy = to.tileY - y; bool inverted = false; int step = Math.Sign(dx); int gradientStep = Math.Sign(dy); int longest = Math.Abs(dx); int shortest = Math.Abs(dy); if (longest < shortest) { inverted = true; longest = Math.Abs(dy); shortest = Math.Abs(dx); step = Math.Sign(dy); gradientStep = Math.Sign(dx); } int gradientAccumulation = longest / 2; for (int i = 0; i < longest; ++i) { line.Add(new Coord(x, y)); if (inverted) { y += step; } else { x += step; } gradientAccumulation += shortest; if (gradientAccumulation >= longest) { if (inverted) { x += gradientStep; } else { y += gradientStep; } gradientAccumulation -= longest; } } return line; } Vector3 CoordToWorldPoint(Coord tile) { return new Vector3(-width / 2 + .5f + tile.tileX, 2, -height / 2 + .5f + tile.tileY); } List> GetRegions(int tileType) { List> regions = new List>(); int[,] mapFlags = new int[width, height]; for (int x = 0; x < width; ++x) { for (int y = 0; y < height; ++y) { if(mapFlags[x, y] == 0 && map[x,y] == tileType) { List newRegion = GetRegionTiles(x, y); regions.Add(newRegion); foreach(Coord tile in newRegion) { mapFlags[tile.tileX, tile.tileY] = 1; } } } } return regions; } List GetRegionTiles(int startX, int startY) { List tiles = new List(); int[,] mapFlags = new int[width, height]; int tileType = map[startX, startY]; Queue queue = new Queue(); queue.Enqueue(new Coord(startX, startY)); mapFlags[startX, startY] = 1; while (queue.Count > 0) { Coord tile = queue.Dequeue(); tiles.Add(tile); for (int x = tile.tileX - 1; x <= tile.tileX + 1; ++x) { for (int y = tile.tileY - 1; y <= tile.tileY + 1; ++y) { if (CheckIfInMapBounds(x, y) && (y == tile.tileY || x == tile.tileX)) { if (mapFlags[x, y] == 0 && map[x, y] == tileType) { mapFlags[x, y] = 1; queue.Enqueue(new Coord(x, y)); } } } } } return tiles; } void SmoothMap() { int[,] newMap = new int [width, height]; for (int x = 0; x < width; ++x) { for (int y = 0; y < height; ++y) { newMap[x, y] = DetermineState(x, y); } } map = newMap; } int DetermineState(int x, int y) { if (cave) { int neighbourWalls = GetSurroundingWallCount(x, y); if (neighbourWalls > 4) { return 1; } else if (neighbourWalls < 4) { return 0; } return map[x, y]; } else { if (!CheckIfInMapBounds(x - 1, y) || !CheckIfInMapBounds(x + 1, y) || !CheckIfInMapBounds(x, y - 1) || !CheckIfInMapBounds(x, y + 1)) { return 1; } if (!CheckIfInMapBounds(x - 2, y) || !CheckIfInMapBounds(x + 2, y) || !CheckIfInMapBounds(x, y - 2) || !CheckIfInMapBounds(x, y + 2)) { return map[x, y]; } if ((map[x + 1, y] == 1 && map[x - 1 , y] == 1 && (map[x + 2, y] == 1 || map[x - 2, y] == 1)) || (map[x, y + 1] == 1 && map[x, y - 1] == 1 && (map[x, y + 2] == 1 || map[x, y - 2] == 1))) { return 1; } return 0; } } bool CheckIfInMapBounds(int x, int y) { return (x >= 0 && x < width && y >= 0 && y < height); } 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 (CheckIfInMapBounds(neighbourX, neighbourY)) { if ((neighbourX != gridX || neighbourY != gridY)) { wallCount += map[neighbourX, neighbourY]; } } else { wallCount++; } } } return wallCount; } void FillMap() { 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] = (generator.Next(0, 100) < randomFillPercent) ? 1 : 0; } } } } struct Coord { public int tileX; public int tileY; public Coord(int x, int y) { tileX = x; tileY = y; } } class Room : IComparable { public List m_Tiles; public List m_EdgeTiles; public List m_ConnectedRooms; public int m_RoomSize; public bool m_IsAccessibleFromMainRoom; public bool m_IsMainRoom; public Room() { } public Room(List roomTiles, int[,] map) { m_Tiles = roomTiles; m_RoomSize = m_Tiles.Count; m_ConnectedRooms = new List(); m_EdgeTiles = new List(); foreach (Coord tile in m_Tiles) { for (int x = tile.tileX - 1; x <= tile.tileX + 1; ++x) { for (int y = tile.tileY - 1; y <= tile.tileY + 1; ++y) { if (x == tile.tileX || x == tile.tileY) { if (map[x, y] == 1) { m_EdgeTiles.Add(tile); } } } } } } public void SetAccesibleFromMainRoom() { if(!m_IsAccessibleFromMainRoom) { m_IsAccessibleFromMainRoom = true; foreach (Room connectedRoom in m_ConnectedRooms) { connectedRoom.SetAccesibleFromMainRoom(); } } } public static void ConnectRooms(Room roomA, Room roomB) { if (roomA.m_IsAccessibleFromMainRoom) { roomB.SetAccesibleFromMainRoom(); } else if (roomB.m_IsAccessibleFromMainRoom) { roomA.SetAccesibleFromMainRoom(); } roomA.m_ConnectedRooms.Add(roomB); roomB.m_ConnectedRooms.Add(roomA); } public bool IsConnected(Room otherRoom) { return m_ConnectedRooms.Contains(otherRoom); } public int CompareTo(Room otherRoom) { return otherRoom.m_RoomSize.CompareTo(m_RoomSize); } } }