299 lines
8.7 KiB
C#
299 lines
8.7 KiB
C#
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<Room> _rooms = new();
|
|
private List<Room> _mainRooms = new();
|
|
private readonly List<Connection> _connections = new();
|
|
private readonly HashSet<Vector2Int> _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<Edge>();
|
|
|
|
// 모든 방 쌍에 대해 거리 계산
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 중복 없는 복도 생성
|
|
/// </summary>
|
|
/// <param name="corridorTiles"></param>
|
|
/// <param name="start"></param>
|
|
/// <param name="end"></param>
|
|
private void CreateCorridor(HashSet<Vector2Int> 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;
|
|
}
|
|
}
|
|
} |