맵 자동 생성(9)
이번 글에서는 복도에 콜라이더 설정을 해보겠다.추가로 시작 전 랜덤한 방을 선택해 그 안에서 캐릭터가 시작하도록 했다. 복도에 콜라이더를 설정해서 복도로만 다니게 해야하는데 이게 생각
tpree.tistory.com
이전 글에서 복도 콜라이더 생성을 다뤄봤었다.
하지만 버그가 있었는데 일자로 복도가 생성됐을때, 중간지점에 모서리가 생겼던 것이다.
또 특정한 패턴의 복도 모양에서는 모서리 쪽에 콜라이더가 이상하게 생성되는 버그가 있었다.
결국 둘 다 방향 설정의 문제다.
직선이 쭉 있다가 모서리가 생기는 것은 기존 알고리즘으로 되지만, 모서리 다음 얼마 안가서 바로 모서리를 생성해야하면
기존 알고리즘에서는 해결이 불가능하다.
기존에는 랜덤 포지션을 기준으로 ㄱ 자 모양을 두번 만들자는 것이 알고리즘의 핵심이었다.
이 방법을 쓰면 두번째 함수가 호출 되었을 때, 지금 시작하는 부분이 직선과 이어지는 부분인지, 모서리라서 꺾어야하는 부분인지
알 수가 없다. 기존 데이터를 저장하지 않았기 때문이다.
그렇다고 모든 통로에 대한 데이터를 전부 저장하는 것은 너무 비효율적인 메모리사용이라고 생각한다.
그래서 아예 알고리즘을 갈아엎었다.
복도 생성하는 과정에 있어서 나누어지면 안되고 한번의 함수 호출로 처리를 해야겠구나.
이렇게 판단하였다.
그렇게 방향을 잡고 다시 생각을 해보니 '굳이 ㄱ자 두번이 아니라 일자 3번을 for문으로 돌면서 만들면 되겠구나' 하는 생각이 들었다.
그러면 총 3개의 (startVec, endVec) 을 만들고 for문 돌면서 생성해야겠다.
모서리는 현재 방향과 다음 방향을 알 수 있으니 쉽게 만들 수 있다.
이 생각을 바탕으로 함수를 구현했다.
void CreateCorridors(GameObject parent, Vector2 v, Vector2 v2 /*randPos*/, Vector2 v3, bool mode /* true: x -> y -> x, false: y -> x -> y */)
{
Vector2Int pos = new Vector2Int((int)v.x, (int)v.y);
Vector2Int pos2 = new Vector2Int((int)v2.x, (int)v2.y);
Vector2Int pos3 = new Vector2Int((int)v3.x, (int)v3.y);
(Vector2Int, Vector2Int)[] path = new (Vector2Int, Vector2Int)[3];
// 경로 설정
if (mode)
{
path[0] = (pos, new Vector2Int(pos2.x, pos.y)); // x축
path[1] = (new Vector2Int(pos2.x, pos.y), new Vector2Int(pos2.x, pos3.y)); // y축
path[2] = (new Vector2Int(pos2.x, pos3.y), pos3); // x축
}
else
{
path[0] = (pos, new Vector2Int(pos.x, pos2.y)); // y축
path[1] = (new Vector2Int(pos.x, pos2.y), new Vector2Int(pos3.x, pos2.y)); // x축
path[2] = (new Vector2Int(pos3.x, pos2.y), pos3); // y축
}
함수를 보면 벡터3개를 받는다. 시작,끝점과 랜덤포지션이다.
mode는 x->y->x 인지, y->x->y 인지를 위해 필요하다.
매개변수로 path를 생성한다.
path가 이해가지 않는다면 종이에 그려보면 된다.
이 path를 바탕으로 for문을 돈다.
for (int i = 0; i < 3; i++)
{
Vector2Int start = path[i].Item1;
Vector2Int end = path[i].Item2;
int dx = (start.x == end.x) ? 0 : (end.x > start.x) ? 1 : -1;
int dy = (start.y == end.y) ? 0 : (end.y > start.y) ? 1 : -1;
// 직선 타일 생성
if (dx != 0) // x축 이동
{
int initX = (i == 0) ? start.x : start.x + dx;
for (int x = initX; x != end.x; x += dx)
{
GameObject go = Instantiate(corridorTile[0], new Vector2(x, start.y), Quaternion.identity);
go.transform.SetParent(parent.transform, true);
}
}
else if (dy != 0) // y축 이동
{
int initY = (i == 0) ? start.y : start.y + dy;
for (int y = initY; y != end.y; y += dy)
{
GameObject go = Instantiate(corridorTile[1], new Vector2(start.x, y), Quaternion.identity);
go.transform.SetParent(parent.transform, true);
}
}
// 모서리 타일 생성 (i < 2일 때만, 마지막 끝점 제외)
if (i < 2 && (dx != 0 || dy != 0))
{
GameObject go = null;
if (dx == 0 && dy == 0) // 동일 좌표
continue;
// 다음 경로의 방향 확인
Vector2Int nextStart = path[i + 1].Item1;
Vector2Int nextEnd = path[i + 1].Item2;
int nextDx = (nextStart.x == nextEnd.x) ? 0 : (nextEnd.x > nextStart.x) ? 1 : -1;
int nextDy = (nextStart.y == nextEnd.y) ? 0 : (nextEnd.y > nextStart.y) ? 1 : -1;
if (dx != 0 && nextDy != 0) // x -> y
{
GameObject prefab = GetDirToCorridorTile((dx > 0), (nextDy < 0));
go = Instantiate(prefab, new Vector2(end.x, end.y), Quaternion.identity);
}
else if (dy != 0 && nextDx != 0) // y -> x
{
GameObject prefab = GetDirToCorridorTile((nextDx < 0), (dy > 0));
go = Instantiate(prefab, new Vector2(end.x, end.y), Quaternion.identity);
}
else // 직선 경로
{
go = Instantiate(dx != 0 ? corridorTile[0] : corridorTile[1], new Vector2(end.x, end.y), Quaternion.identity);
}
if (go != null)
go.transform.SetParent(parent.transform, true);
}
// 최종 끝점 처리 (i == 2)
if (i==2)
{
GameObject go = null;
go = Instantiate((dx != 0)? corridorTile[0] : corridorTile[1],new Vector2(end.x,end.y),Quaternion.identity);
if (go != null)
go.transform.SetParent(parent.transform, true);
}
}
for문 코드이다. 좀 길어보이지만 별거없다.
하나씩 살펴보자.
Vector2Int start = path[i].Item1;
Vector2Int end = path[i].Item2;
int dx = (start.x == end.x) ? 0 : (end.x > start.x) ? 1 : -1;
int dy = (start.y == end.y) ? 0 : (end.y > start.y) ? 1 : -1;
// 직선 타일 생성
if (dx != 0) // x축 이동
{
int initX = (i == 0) ? start.x : start.x + dx;
for (int x = initX; x != end.x; x += dx)
{
GameObject go = Instantiate(corridorTile[0], new Vector2(x, start.y), Quaternion.identity);
go.transform.SetParent(parent.transform, true);
}
}
else if (dy != 0) // y축 이동
{
int initY = (i == 0) ? start.y : start.y + dy;
for (int y = initY; y != end.y; y += dy)
{
GameObject go = Instantiate(corridorTile[1], new Vector2(start.x, y), Quaternion.identity);
go.transform.SetParent(parent.transform, true);
}
}
dx, dy 는 이동할 방향을 뜻한다. dx의 경우, 1은 오른쪽, -1은 왼쪽이다. dy도 1이면 위로, -1이면 아래로 이동이다.
0인 경우는 x로 일자, y로 일자를 구분한다.
if-else문에 바로 사용한다.
initX는 두번째 path부터는 맨처음자리를 생성하지않는다. 이유는 모서리와 겹치기 때문이다.
그냥 쭉 일자로 생성하는 것이다.
if (i < 2 && (dx != 0 || dy != 0))
{
GameObject go = null;
if (dx == 0 && dy == 0) // 동일 좌표
continue;
// 다음 경로의 방향 확인
Vector2Int nextStart = path[i + 1].Item1;
Vector2Int nextEnd = path[i + 1].Item2;
int nextDx = (nextStart.x == nextEnd.x) ? 0 : (nextEnd.x > nextStart.x) ? 1 : -1;
int nextDy = (nextStart.y == nextEnd.y) ? 0 : (nextEnd.y > nextStart.y) ? 1 : -1;
if (dx != 0 && nextDy != 0) // x -> y
{
GameObject prefab = GetDirToCorridorTile((dx > 0), (nextDy < 0));
go = Instantiate(prefab, new Vector2(end.x, end.y), Quaternion.identity);
}
else if (dy != 0 && nextDx != 0) // y -> x
{
GameObject prefab = GetDirToCorridorTile((nextDx < 0), (dy > 0));
go = Instantiate(prefab, new Vector2(end.x, end.y), Quaternion.identity);
}
else // 직선 경로
{
go = Instantiate(dx != 0 ? corridorTile[0] : corridorTile[1], new Vector2(end.x, end.y), Quaternion.identity);
}
if (go != null)
go.transform.SetParent(parent.transform, true);
}
위 코드는 모서리 생성 코드이다.
(i==2는 마지막 경로이기 때문에 모서리가 필요없다.)
다음 경로에 대해서 dx,dy를 구한다. 그게 nextDx,nextDy 이다.
현재 방향과 다음 방향을 알았으니 이 두 방향을 조합하면 모서리를 어떻게 만들어야할지 구할 수 있다.
그리고 이 두 방향가지고 어떤 모서리르 만들지 리턴해주는 함수를 지난 글에서 소개한 바 있다.
그대로 생성해주면 된다.
일자라면 dx와 nextDx가 0이거나 dy, nextDy가 0일 것이다. 따라서 else문에 걸려서 일자 통로를 만들게 된다.
마지막이다.
// 최종 끝점 처리 (i == 2)
if (i==2)
{
GameObject go = null;
go = Instantiate((dx != 0)? corridorTile[0] : corridorTile[1],new Vector2(end.x,end.y),Quaternion.identity);
if (go != null)
go.transform.SetParent(parent.transform, true);
}
사실 맨 위에서 예외처리해도 되지만 이게 더 깔끔할 것 같아서 아래에서 작성한다.
모서리를 위해 남겨둔 자리에 마지막 경로는 방향 그대로 통로를 하나 설치해주는 작업이다.
아래는 전체코드이다.
void CreateCorridors(GameObject parent, Vector2 v, Vector2 v2 /*randPos*/, Vector2 v3, bool mode /* true: x -> y -> x, false: y -> x -> y */)
{
Vector2Int pos = new Vector2Int((int)v.x, (int)v.y);
Vector2Int pos2 = new Vector2Int((int)v2.x, (int)v2.y);
Vector2Int pos3 = new Vector2Int((int)v3.x, (int)v3.y);
(Vector2Int, Vector2Int)[] path = new (Vector2Int, Vector2Int)[3];
// 경로 설정
if (mode)
{
path[0] = (pos, new Vector2Int(pos2.x, pos.y)); // x축
path[1] = (new Vector2Int(pos2.x, pos.y), new Vector2Int(pos2.x, pos3.y)); // y축
path[2] = (new Vector2Int(pos2.x, pos3.y), pos3); // x축
}
else
{
path[0] = (pos, new Vector2Int(pos.x, pos2.y)); // y축
path[1] = (new Vector2Int(pos.x, pos2.y), new Vector2Int(pos3.x, pos2.y)); // x축
path[2] = (new Vector2Int(pos3.x, pos2.y), pos3); // y축
}
for (int i = 0; i < 3; i++)
{
Vector2Int start = path[i].Item1;
Vector2Int end = path[i].Item2;
int dx = (start.x == end.x) ? 0 : (end.x > start.x) ? 1 : -1;
int dy = (start.y == end.y) ? 0 : (end.y > start.y) ? 1 : -1;
// 직선 타일 생성
if (dx != 0) // x축 이동
{
int initX = (i == 0) ? start.x : start.x + dx;
for (int x = initX; x != end.x; x += dx)
{
GameObject go = Instantiate(corridorTile[0], new Vector2(x, start.y), Quaternion.identity);
go.transform.SetParent(parent.transform, true);
}
}
else if (dy != 0) // y축 이동
{
int initY = (i == 0) ? start.y : start.y + dy;
for (int y = initY; y != end.y; y += dy)
{
GameObject go = Instantiate(corridorTile[1], new Vector2(start.x, y), Quaternion.identity);
go.transform.SetParent(parent.transform, true);
}
}
// 모서리 타일 생성 (i < 2일 때만, 마지막 끝점 제외)
if (i < 2 && (dx != 0 || dy != 0))
{
GameObject go = null;
if (dx == 0 && dy == 0) // 동일 좌표
continue;
// 다음 경로의 방향 확인
Vector2Int nextStart = path[i + 1].Item1;
Vector2Int nextEnd = path[i + 1].Item2;
int nextDx = (nextStart.x == nextEnd.x) ? 0 : (nextEnd.x > nextStart.x) ? 1 : -1;
int nextDy = (nextStart.y == nextEnd.y) ? 0 : (nextEnd.y > nextStart.y) ? 1 : -1;
if (dx != 0 && nextDy != 0) // x -> y
{
GameObject prefab = GetDirToCorridorTile((dx > 0), (nextDy < 0));
go = Instantiate(prefab, new Vector2(end.x, end.y), Quaternion.identity);
}
else if (dy != 0 && nextDx != 0) // y -> x
{
GameObject prefab = GetDirToCorridorTile((nextDx < 0), (dy > 0));
go = Instantiate(prefab, new Vector2(end.x, end.y), Quaternion.identity);
}
else // 직선 경로
{
go = Instantiate(dx != 0 ? corridorTile[0] : corridorTile[1], new Vector2(end.x, end.y), Quaternion.identity);
}
if (go != null)
go.transform.SetParent(parent.transform, true);
}
// 최종 끝점 처리 (i == 2)
if (i==2)
{
GameObject go = null;
go = Instantiate((dx != 0)? corridorTile[0] : corridorTile[1],new Vector2(end.x,end.y),Quaternion.identity);
if (go != null)
go.transform.SetParent(parent.transform, true);
}
}
}
어떤 통로를 보아도 중간에 막혀있는 것 없이 깔끔하게 생성되었다.
하루종일 이거만 고쳤다...
'2d 자동 맵생성' 카테고리의 다른 글
맵 자동 생성(12) (0) | 2025.05.15 |
---|---|
맵 자동 생성(11) (0) | 2025.05.14 |
맵 자동 생성(9) (0) | 2025.05.13 |
맵 자동 생성(8) (0) | 2025.05.13 |
맵 자동 생성(7) (0) | 2025.05.13 |