맵 자동 생성(3)
이번 글에서는 만들어진 방들을 연결하기 위한 방법을 다뤄보겠다. 모든 방(노드)를 빠짐없이 연결하고 이를 최대한 효율적으로 연결하기 위해서는 최소스패닝트리 즉, MST알고리즘을 사용하면
tpree.tistory.com
이전 글에 이어서 방 생성에 대해서 다뤄보고 문, 벽까지 완성해보려한다.
이전엔 문의 방향 위치 상관없이 그냥 생성만 해보았다. 그래서 통로와 관계없이 문이 있고 이런데
통로가 어디로 이어져 있으며, 어떻게 방향을 정해 문을 배치할 것인가를 고민해봐야 한다.
우선 a - b 를 연결하는 문과 통로를 만든다고 가정하자.
a -> b 는 방향이 어느쪽인지를 파악해야한다. 그리고 그쪽 벽에 문을 설치해야한다.
방향은 간단한 벡터의 빼기연산 (b - a) 로 구할 수 있다.
이 방향을 다시 한 번 매핑해야하는데 이 방향을 상,하,좌,우로 구분해야하는 것이다. 이것은 역탄젠트 함수를 통해 각도를 알아내면 된다.
그리고 그 방향으로 랜덤한 위치에 문을 생성하면 된다.
문은 두개의 프리팹 front door, side door 로 앞뒤를 보는 문, 좌,우측을 보는 문으로 구분했다.
IEnumerator GenerateCorridor()
{
// kruscal 알고리즘 세팅
mst = kruscal.Run();
foreach (Edge e in mst)
{
Vector2 dir = (roomMap[e.V2].GetCenter() - roomMap[e.V1].GetCenter()).normalized;
// 각도 계산 (도 단위)
float angle = Mathf.Atan2(dir.y, dir.x) * Mathf.Rad2Deg;
if (angle < 0) angle += 360; // 0~360도로 변환
// 각도를 기본 방향으로 매핑
Vector2 snappedDir;
if (angle >= 45 && angle < 135)
snappedDir = Vector2.up; // 90도 ± 45도
else if (angle >= 135 && angle < 225)
snappedDir = Vector2.left; // 180도 ± 45도
else if (angle >= 225 && angle < 315)
snappedDir = Vector2.down; // 270도 ± 45도
else
snappedDir = Vector2.right; // 0도 ± 45도
GameObject door = CreateDoor(snappedDir);
roomMap[e.V1].SetDoor(door, snappedDir);
GameObject door2 =CreateDoor(-snappedDir);
roomMap[e.V2].SetDoor(door2, -snappedDir);
}
CreateWall();
FilledFloorTile();
yield return null;
}
roomMap은 방 id, RoomController 클래스를 묶은 dictionary 이다.
각도를 계산하고 CreateDoor는 그냥 방향에 맞는 프리팹을 만들어 리턴해주는 함수이다.
Dictionary<Vector2,GameObject> DirToDoor = new Dictionary<Vector2,GameObject>();
public void Start()
{
DirToDoor.Clear();
DirToDoor.Add(Vector2.down, frontDoor);
DirToDoor.Add(Vector2.up, frontDoor);
DirToDoor.Add(Vector2.left, sideDoor);
DirToDoor.Add(Vector2.right, sideDoor);
}
GameObject CreateDoor(Vector2 dir)
{
dir = dir.normalized;
GameObject prefab = DirToDoor[dir];
GameObject go = Instantiate(prefab,Vector3.zero,Quaternion.identity);
return go;
}
방향을 주면 프리팹을 얻을 수 있게 dictionary를 만들었고 CreateDoor 에서 생성해서 리턴해준다.
생성했으니 방에 세팅을 해야한다. 이를 위해서 RoomController 클래스에 SetDoor 함수가 있는데
door를 받아서 해당방향에 랜덤으로 세팅한다.
public void SetDoor(GameObject door, Vector2 dir)
{
if (dir == Vector2.left || dir == Vector2.right)
{
int randH = Random.Range(0, Height - 2);
door.transform.SetParent(transform);
Vector3 pos = Vector3.zero;
if (dir == Vector2.right) pos.x = Width - 1;
pos.y = randH;
roomArrayData[randH, (int)pos.x] = 2;
roomArrayData[randH + 1, (int)pos.x] = 2;
if (dir == Vector2.left) pos.x -= 0.5f;
if (dir == Vector2.right) pos.x += 0.5f;
door.transform.localPosition = pos;
}
else
{
int randW = Random.Range(0, Width - 2);
door.transform.SetParent(transform);
Vector3 pos = Vector3.zero;
if (dir == Vector2.up) pos.y = Height - 1;
pos.x = randW;
roomArrayData[(int)pos.y, randW] = 2;
roomArrayData[(int)pos.y, randW+1] = 2;
door.transform.localPosition = pos;
}
}
방향에 따라 랜덤하게 세팅한다.
모든 간선에 대해서 이 작업을 완료하면 방이 생성되게 된다.
그리고 벽을 생성하고, 문 바로 앞에 바닥타일을 추가하여 빈곳을 마무리하면 된다.
IEnumerator GenerateCorridor()
{
// 문 생성
CreateWall();
FilledFloorTile();
}
둘 다 모든 방에 대해서 벽과 문 앞에 바닥타일을 설치하는 작업을 수행한다.
void CreateWall()
{
foreach (RoomController rc in visualRooms)
{
for(int i=0;i<rc.Height;i++)
{
for(int j=0;j<rc.Width;j++)
{
if (rc.roomArrayData[i,j]==1)
{
GameObject go = Instantiate(wallTile);
go.transform.SetParent(rc.transform);
go.transform.localPosition = new Vector2(j, i);
}
}
}
}
}
void FilledFloorTile()
{
foreach (RoomController rc in visualRooms)
{
for (int i = 0; i < rc.Height; i++)
{
for (int j = 0; j < rc.Width; j++)
{
if(rc.roomArrayData[i, j]==2)
{
GameObject inst;
inst = Instantiate(floorTile, Vector3.zero, Quaternion.identity);
inst.transform.SetParent(rc.transform);
inst.transform.localPosition = new Vector3(j, i, 0);
rc.roomArrayData[i, j] = 5;
}
}
}
}
}
둘다 for문은 같은 구조이다.
여기서 roomArrayData는 하나의 방을 2차원 배열로 표현한 것인데, RoomController가 가지고 있다.
1인 경우는 벽이고, 2인 경우는 문이다.
이렇게하면
위 사진처럼 경로에 따라 문의 개수와 방향이 랜덤하게 생성되어 있고, 모서리들은 벽이 생성되어 있다.
이 과정에서 또 하나 고생한 것은 모든 타일들은 코드로 위치를 맞추려면 기준이 동일해야한다는 것이다.
사진을 보면 좌측 하단에 오브젝트의 원점을 표시하는 저 화살표가 보일 것이다.
저 오브젝트를 기준으로 해서 어떻게 생성할지를 모든 오브젝트가 동일하게 맞춰야 하는데, 그것을 모르고
position만 맞으면 되겠지 생각하고 구현했는데 문과 벽을 생성하다보니, 위치가 계속 어긋나서 보니 이것이 문제였다.
기존에는 어떤 프리팹은 원점을 가운데로 둔것도 있고, 다른 것은 원점을 좌측하단에 둔것도 있어서 위치가 어긋나는 것이었다.
이 프로젝트 또한 혹시나 따라하는 사람은 아마 그대로 안될 가능성이 높다.
나는 오브젝트의 좌측하단을 기준으로 맞췄다.
이제 거의 막바지에 온 것 같다.
다음은 문과 문을 연결해주는 통로를 직접 만들면 된다. 일자, ㄱ자, ㄹ자, ㄷ자 등을 어떻게 만들지는 모르겠지만 또 연구해서
완성시켜봐야겠다.
'2d 자동 맵생성' 카테고리의 다른 글
맵 자동 생성(6) (0) | 2025.05.12 |
---|---|
맵 자동 생성(5) (2) | 2025.05.11 |
맵 자동 생성(3) (0) | 2025.05.10 |
맵 자동 생성(2) (1) | 2025.05.09 |
맵 자동 생성 (1) (0) | 2025.05.09 |