using System.Collections.Generic; using System.Linq; using UnityEngine; public class MapGenerator : MonoBehaviour { public int totalRooms = 20; public int mainRoomsCount = 10; public float mapRadius = 50f; public float minRoomWidth = 4f; public float maxRoomWidth = 10f; public float minRoomHeight = 4f; public float maxRoomHeight = 10f; public int separationIterations = 10; private readonly List _rooms = new(); private List _mainRooms = new(); private readonly List _connections = new(); private readonly HashSet _corridorTiles = new(); private void Start() { GenerateMap(); } public void GenerateMap() { ClearAll(); GenerateRooms(); SeparateRooms(); SelectMainRooms(); ConnectRoomsWithMST(); CreateCorridors(); Debug.Log($"생성된 전체 방 수: {_rooms.Count}"); Debug.Log($"메인 방 수: {_mainRooms.Count}"); Debug.Log($"연결 수: {_connections.Count}"); } private void ClearAll() { _rooms.Clear(); _mainRooms.Clear(); _connections.Clear(); } private void GenerateRooms() { for (var i = 0; i < totalRooms; i++) { // 랜덤 위치 선택 var angle = Random.Range(0f, Mathf.PI * 2); var distance = Random.Range(0f, mapRadius); var position = new Vector2( Mathf.Cos(angle) * distance, Mathf.Sin(angle) * distance ); // 랜덤 크기 방 생성 var width = Random.Range(minRoomWidth, maxRoomWidth); var height = Random.Range(minRoomHeight, maxRoomHeight); _rooms.Add(new Room(position, new Vector2(width, height))); } } private void SeparateRooms() { for (var iter = 0; iter < separationIterations; iter++) { var stillOverlapping = false; for (var i = 0; i < _rooms.Count; i++) for (var j = i + 1; j < _rooms.Count; j++) if (_rooms[i].Overlaps(_rooms[j])) { stillOverlapping = true; // 방들이 겹치면 서로 밀어냄 var direction = (_rooms[i].Position - _rooms[j].Position).normalized; var overlap = _rooms[i].GetOverlapDistance(_rooms[j]); _rooms[i].Position += direction * overlap * 0.5f; _rooms[j].Position -= direction * overlap * 0.5f; } // 더 이상 겹치는 방이 없으면 종료 if (!stillOverlapping) break; } } private void SelectMainRooms() { // 방 크기에 따라 정렬 (큰 것부터) _rooms.Sort((a, b) => (b.Size.x * b.Size.y).CompareTo(a.Size.x * a.Size.y)); // 가장 큰 방 mainRoomsCount개만 선택 _mainRooms = _rooms.Take(Mathf.Min(mainRoomsCount, _rooms.Count)).ToList(); } private void ConnectRoomsWithMST() { var edges = new List(); // 모든 방 쌍에 대해 거리 계산 for (var i = 0; i < _mainRooms.Count; i++) for (var j = i + 1; j < _mainRooms.Count; j++) { var dist = Vector2.Distance(_mainRooms[i].Position, _mainRooms[j].Position); edges.Add(new Edge(i, j, dist)); } // 크루스칼 MST 알고리즘 edges.Sort((a, b) => a.Weight.CompareTo(b.Weight)); var ds = new DisjointSet(_mainRooms.Count); foreach (var edge in edges) if (ds.Find(edge.U) != ds.Find(edge.V)) { ds.Union(edge.U, edge.V); _connections.Add(new Connection(_mainRooms[edge.U], _mainRooms[edge.V])); } } private void CreateCorridors() { foreach (var conn in _connections) { var roomA = conn.RoomA; var roomB = conn.RoomB; // 두 방의 중심점 var startPos = Vector2Int.RoundToInt(roomA.Position); var endPos = Vector2Int.RoundToInt(roomB.Position); // 두 가지 L자 패턴 if (Random.Range(0, 2) == 0) { // 수평 먼저, 수직 나중 ㄱ CreateCorridor(_corridorTiles, startPos, new Vector2Int(endPos.x, startPos.y)); CreateCorridor(_corridorTiles, new Vector2Int(endPos.x, startPos.y), endPos); } else { // 수직 먼저, 수평 나중 ┌ CreateCorridor(_corridorTiles, startPos, new Vector2Int(startPos.x, endPos.y)); CreateCorridor(_corridorTiles, new Vector2Int(startPos.x, endPos.y), endPos); } } } /// /// 중복 없는 복도 생성 /// /// /// /// private void CreateCorridor(HashSet corridorTiles, Vector2Int start, Vector2Int end) { var position = start; var direction = new Vector2Int( Mathf.Clamp(end.x - start.x, -1, 1), Mathf.Clamp(end.y - start.y, -1, 1) ); while (position != end) { corridorTiles.Add(position); // 다음 위치로 이동 if (position.x != end.x) position.x += direction.x; else if (position.y != end.y) position.y += direction.y; } } #region Debug 테스트용 public Color roomColor = Color.blue; public Color mainRoomColor = Color.red; public Color corridorColor = Color.green; private void OnDrawGizmos() { // 방 Gizmos.color = roomColor; foreach (var room in _rooms) { if (_mainRooms.Contains(room)) Gizmos.color = mainRoomColor; else Gizmos.color = roomColor; Gizmos.DrawWireCube(room.Position, room.Size); } // 복도 Gizmos.color = corridorColor; var tileSize = 1f; // 타일 크기 if (_corridorTiles != null) foreach (var pos in _corridorTiles) Gizmos.DrawWireCube(new Vector3(pos.x, pos.y, 0), new Vector3(tileSize, tileSize, 0)); } #endregion #region Class // 방 클래스 public class Room { public Vector2 Position; public Vector2 Size; public Room(Vector2 pos, Vector2 size) { Position = pos; Size = size; } public bool Overlaps(Room other) { return !(Position.x + Size.x / 2 < other.Position.x - other.Size.x / 2 || Position.x - Size.x / 2 > other.Position.x + other.Size.x / 2 || Position.y + Size.y / 2 < other.Position.y - other.Size.y / 2 || Position.y - Size.y / 2 > other.Position.y + other.Size.y / 2); } public float GetOverlapDistance(Room other) { var dx = Mathf.Min(Position.x + Size.x / 2, other.Position.x + other.Size.x / 2) - Mathf.Max(Position.x - Size.x / 2, other.Position.x - other.Size.x / 2); var dy = Mathf.Min(Position.y + Size.y / 2, other.Position.y + other.Size.y / 2) - Mathf.Max(Position.y - Size.y / 2, other.Position.y - other.Size.y / 2); // 실제 겹치는 거리 계산 if (dx > 0 && dy > 0) return Mathf.Min(dx, dy) + 0.1f; // 약간의 여유 공간 추가 return 0; } } // 연결 클래스 private class Connection { public readonly Room RoomA, RoomB; public Connection(Room a, Room b) { RoomA = a; RoomB = b; } } // MST용 간선 클래스 private class Edge { public readonly int U, V; public readonly float Weight; public Edge(int u, int v, float w) { U = u; V = v; Weight = w; } } #endregion // Disjoint Set 자료구조 private class DisjointSet { private readonly int[] _parent; public DisjointSet(int n) { _parent = new int[n]; for (var i = 0; i < n; i++) _parent[i] = i; } public int Find(int x) { if (_parent[x] == x) return x; return _parent[x] = Find(_parent[x]); } public void Union(int a, int b) { var rootA = Find(a); var rootB = Find(b); if (rootA != rootB) _parent[rootB] = rootA; } } }