기록

240201 BSP 알고리즘을 이용한 랜덤맵 생성

hayo_su 2024. 2. 2. 15:56

전에 벽으로 나누어진 랜덤맵을 만든 내용을 바탕으로 이를 확장하였다.

 

 

240104 BSP Algorithm을 이용한 Random Map Generator

BSP 알고리즘 아이디어 1. 전체공간을 가지고있는 RootNode를 가지고 시작한다. 2. 가로 세로 중 긴 축을 기준으로 랜덤 비율을 사용하여 자른다. 3. 왼쪽 영역을 LeftChildNode, 오른쪽 영역을 RightChildNode

hayo-su.tistory.com


목차

1. Leaf Node 저장

2. Leaf Node 랜덤 룸 생성

3. 룸을 연결하는 통로 생성

4. 결과


1. Leaf Node 저장

말단 노드를 확인하였을 때 LeafNodeList에 저장하도록 하였다.

// 현재 Node를 n개로 나누고싶다는 의미
bool URectRoomMapGenerator::DivideNode(std::shared_ptr<Node> tree, int n, int _size, float _rate)
{
    if (n == 1) // 더이상 방을 나눌 필요가 없을 때
    {
        if (_size > GetRoomSize(tree->nodeRect)) // 최소 방 크기를 만족하지 않는다면
        {
            return false;
        }
        LeafNodeList.push_back(tree);
        return true;
    }

 

 

인덱스, x축 시작점, y축 시작점, x축 끝점, y축 끝점 순으로 leaf노드(말단노드)에 해당하는 노드드를 저장하도록 하였다.

 

 

 

 

 

 

 

 

 

 

 

 


2. Leaf Node 랜덤 룸 생성

    for (std::shared_ptr<Node> _leaf : LeafNodeList)
    {
        CreateRoom(_leaf);
    }

 

모든 말단 노드에 만들 수 있는 도형을 랜덤으로 create하도록 하였다.

 

삼각형

// 삼각형
if (CurRect.width >= 5 && CurRect.height >= 5)
{
	int point_len = GameEngineRandom::MainRandom.RandomInt(0, CurRect.width - 2);
	int b_x = CurRect.x;
	int b_y = CurRect.y + point_len;


	int a_x = CurRect.x + CurRect.height - 2;
	int a_y = CurRect.y;

	int c_x = CurRect.x + CurRect.height - 2;
	int c_y = CurRect.y + CurRect.width - 2;

	if (base_map[b_x][b_y] != 0 || base_map[a_x][a_y] != 0 || base_map[c_x][c_y] != 0)
	{
		return;
	}

	for (int i = CurRect.x; i < CurRect.x + CurRect.height; ++i)
	{
		for (int j = CurRect.y; j < CurRect.y + CurRect.width; ++j)
		{
			if ((i * (b_y - a_y) < ((b_x - a_x) * (j - b_y) + b_x * (b_y - a_y))) || (i * (b_y - c_y) > ((b_x - c_x) * (j - b_y) + b_x * (b_y - c_y))))
			{
				if (base_map[i][j] == 0)
				{
					base_map[i][j] = 1;
				}
			}
		}
	}
}

 

사각형

// 사각형
if (CurRect.width >= 5 && CurRect.height >= 5)
{

    int start_x = GameEngineRandom::MainRandom.RandomInt(CurRect.x, CurRect.x + CurRect.height / 4);
    int start_y = GameEngineRandom::MainRandom.RandomInt(CurRect.y, CurRect.y + CurRect.width / 4);
    int len_x = GameEngineRandom::MainRandom.RandomInt(CurRect.height / 2, CurRect.x + CurRect.height- start_x); 
    int len_y = GameEngineRandom::MainRandom.RandomInt(CurRect.width / 2, CurRect.y + CurRect.width - start_y);


    for (int i = CurRect.x; i < CurRect.x + CurRect.height; ++i)
    {
        for (int j = CurRect.y; j < CurRect.y + CurRect.width; ++j)
        {
            if (i < start_x || start_x >= start_x + len_x || j < start_y || j >= start_y + len_y)
            {
                if (base_map[i][j] == 0)
                {
                    base_map[i][j] = 1;
                }
            }
        }
    }
}

 

마름모

// 마름모
if (CurRect.width >= 3 && CurRect.height >= 3)
{
    int point_x = (CurRect.height) / 2 - 1;
    int point_y = (CurRect.width) / 2 - 1;

    int a_x = CurRect.x + point_x;
    int a_y = CurRect.y;

    int b_x = CurRect.x;
    int b_y = CurRect.y + point_y;


    int c_x = CurRect.x + point_x;
    int c_y = CurRect.y + point_y * 2;


    int d_x = CurRect.x + point_x * 2;
    int d_y = CurRect.y + point_y;

    for (int i = CurRect.x; i < CurRect.x + CurRect.height; ++i)
    {
        for (int j = CurRect.y; j < CurRect.y + CurRect.width; ++j)
        {
            if (
                (i * (b_y - a_y) < ((b_x - a_x) * (j - b_y) + b_x * (b_y - a_y))) || (i * (b_y - c_y) > ((b_x - c_x) * (j - b_y) + b_x * (b_y - c_y))) ||
                (i * (d_y - a_y) > ((d_x - a_x) * (j - d_y) + d_x * (d_y - a_y))) || (i * (d_y - c_y) < ((d_x - c_x) * (j - d_y) + d_x * (d_y - c_y)))
                )
            {
                if (base_map[i][j] == 0)
                {
                    base_map[i][j] = 1;
                }
            }
        }
    }
}

3. 룸을 연결하는 통로 생성

 

이 때 leaf 노드가 항상 인접한 노드가 아니기 때문에 divide하는 순서를 먼저 바꾸어 줬다.

 

before

return DivideNode(tree->leftNode, leftnodecnt, _size, nrate) && 
DivideNode(tree->rightNode, rightnodecnt, _size, nrate);

 

after

    if (true == is_height)
    {
        return DivideNode(tree->leftNode, leftnodecnt, _size, nrate) && 
        DivideNode(tree->rightNode, rightnodecnt, _size, nrate); //왼쪽, 오른쪽 자식 노드들도 나눠준다.
    }
    else
    {
        return DivideNode(tree->rightNode, rightnodecnt, _size, nrate) && 
        DivideNode(tree->leftNode, leftnodecnt, _size, nrate); //왼쪽, 오른쪽 자식 노드들도 나눠준다.
    }

 

두 노드를 연결한다.

bool URectRoomMapGenerator::MakeRoad(const RectInt f_rect, const RectInt s_rect)
{
    // x축(세로)가 공통인지 확인
    if ((f_rect.x <= s_rect.x && s_rect.x < f_rect.x + f_rect.height) || (f_rect.x >= s_rect.x && s_rect.x + s_rect.height > f_rect.x))
    {
		int midx = (f_rect.GetMidx() + s_rect.GetMidx() - 1) / 2;
        int miny, maxy;

 
        if (s_rect.GetMidy() > f_rect.GetMidy())
        {
            miny = f_rect.GetMidy()-1;
			maxy = s_rect.GetMidy() + 1;
        }
        else
        {
            maxy = f_rect.GetMidy()+1;
            miny = s_rect.GetMidy()-1;
        }
        for (int j = miny; j < maxy; ++j)
        {
            if (base_map[midx][j] == 1)
            {
                if (base_map[midx][j - 1] == 0 || base_map[midx][j + 1] == 0)
                {
                    base_map[midx][j] = 2;
                }
                else
                {
                    base_map[midx][j] = 3;
                }
            }
        }
        return true;
    }
    // y 축(가로)가 공통인지 확인
    else if ((f_rect.y <= s_rect.y && s_rect.y < f_rect.y + f_rect.width) || (f_rect.y > s_rect.y && s_rect.y + s_rect.width > f_rect.y))
    {
		int midy = (f_rect.GetMidy() + s_rect.GetMidy() - 1) / 2;
        int minx, maxx;


        if (s_rect.GetMidx() > f_rect.GetMidx())
        {
			minx = f_rect.GetMidx() - 1;
			maxx = s_rect.GetMidx() + 1;
        }
        else
        {
			maxx = f_rect.GetMidx() + 1;
			minx = s_rect.GetMidx() - 1;
        }
        for (int j = minx; j < maxx; ++j)
        {
            if (base_map[j][midy] == 1)
            {
				if (base_map[j - 1][midy] == 0 || base_map[j + 1][midy] == 0)
                {
                    base_map[j][midy] = 2;
                }
                else
                {
                    base_map[j][midy] = 3;
                }
            }
        }
        return true;
    }
    else
    {
        int a = 0;
        return false;
        // err
    }
}

4. 결과

 

통로 연결이 문제가 있어 노드를 재 탐색할지 고민중이다.