Class GameDev* SheepAdult

[Unity2D] 내 마음대로 만들어 보는 아이작식 랜덤 맵 생성 (2) - 문 생성 본문

Unity

[Unity2D] 내 마음대로 만들어 보는 아이작식 랜덤 맵 생성 (2) - 문 생성

SheepAdult 2024. 3. 13. 19:45

깃허브 : https://github.com/devminjae97/GP1/tree/GP1-2_MapGeneratorTest
 

문 생성

문은 서로 다른 방 사이에 하나만 존재한다. 문은 각 셀에서 만들지 말지 결정하는데, 만약 서로 다른 방끼리 셀 2개가 맞닿아 있다 해도 문은 하나만 생성된다. 이를 위해선 생성할 수 있는 방이 정해지면 이 방의 각 타일을 순회하며 주변 셀을 검사한다. 이미 생성된 방이어야 하며, 방 ID가 다르다면 문을 생성할 것이다.
코드는 아래와 같다.

// 생성할 수 있는 방
foreach (Cell cell in checkRoomResult.Item2)
{
    // 4방향 체크
    for (int i = 0; i < 4; i++)
    {
        nx = cell.pos.x + xdir[i];
        ny = cell.pos.y + ydir[i];
        if (0 < nx && nx <= cellList.GetLength(1) && 0 < ny && ny <= cellList.GetLength( 0 ) && cellList[nx, ny].id != cell.id && cellList[nx, ny].id != 0)
        {
            // 이미 인접한지 확인한 방이라면 패스
            if (IsAdjacent( cell, cellList[nx, ny] ))
                continue;
                
            // 인접한 방 목록에 추가
            AddAdjacentID( cell, cellList[nx, ny] );

            // 문 생성
            if (i == 0 || i == 2) GenerateDoor( cell, cellList[nx, ny], i );
            else if (i == 1 || i == 3) GenerateDoor( cellList[nx, ny], cell, i );
        }
    }
}

인접 여부는 Dictionary<int, HashSet<int>>에 id를 저장하면서 관리한다. 키는 현재 방 id이며 값은 인접한 방의 id 들이다. IsAdjacent함수와 AddAdjacentID는 아래와 같다.

bool IsAdjacent( Cell prevCell, Cell postCell )
{
    return roomIDHash.ContainsKey( prevCell.id ) && roomIDHash[prevCell.id].Contains( postCell.id );
}

void AddAdjacentID(Cell prevCell, Cell postCell)
{
    if (roomIDHash.ContainsKey( prevCell.id ))
    {
        roomIDHash[prevCell.id].Add( postCell.id );
    }
    else
    {
        roomIDHash.Add( prevCell.id, new HashSet<int>() { postCell.id } );
    }
    if (roomIDHash.ContainsKey( postCell.id ))
    {
        roomIDHash[postCell.id].Add( prevCell.id );
    }
    else
    {
        roomIDHash.Add( postCell.id, new HashSet<int>() { prevCell.id } );
    }
}

특별한 것 없이 이미 같은 ID를 포함하는지 체크하는 bool 함수와 인접 방 ID를 추가하는 void 함수이다.
문 생성은 아래와 같다. 벽에 문이 생성되므로 방향에 따른 벽의 중앙에 문을 만든다.

void GenerateDoor( Cell prevCell, Cell postCell, EDir dir )
{
    Door prevDoor, postDoor;
    // 문 방향, 위치 정책
    if (dir == EDir.eRight || dir == EDir.eLeft)
    {
        prevDoor = DrawDoor( prevCell, EDir.eRight );
        postDoor = DrawDoor( postCell, EDir.eLeft );
    }
    else
    {
        prevDoor = DrawDoor( prevCell, EDir.eDown );
        postDoor = DrawDoor( postCell, EDir.eUp );
    }

	// 서로의 문 저장
    prevDoor.NextDoor = postDoor;
    postDoor.NextDoor = prevDoor;
    prevDoor.NextDoor.OwnerCell = postCell;
    postDoor.NextDoor.OwnerCell = prevCell;
}

public Door DrawDoor( Cell cell, EDir dir )
{
    Vector3Int pos;
    Door doorInstance = Instantiate( doorObj ).GetComponent<Door>();
    // 문이 존재하는 Cell 저장
    doorInstance.OwnerCell = cell;
    switch (dir)
    {
        case EDir.eLeft:
            pos = new Vector3Int( cell.tilemapLocalPos.x + tileNumPerCell / 2, cell.tilemapLocalPos.y );
            doorInstance.NextDoorPos = pos + new Vector3Int( 0, -1, 0 );
            break;
        case EDir.eRight:
            pos = new Vector3Int( cell.tilemapLocalPos.x + tileNumPerCell / 2, cell.tilemapLocalPos.y + tileNumPerCell - 1 );
            doorInstance.NextDoorPos = pos + new Vector3Int( 0, 1, 0 );
            break;
        ...
    }
    
    // 벽을 담당하는 Tilemap의 해당 칸을 삭제한다. 문을 그리기 위함이다.
    wallTilemap.SetTile( pos, null );
    // 추후에 문의 모든 요소를 관리하기 위해 id에 해당하는 door 객체를 넣는다.
    DungeonManager.GetInstance().AddToDoorDic( cell.id, doorInstance );
    doorInstance.transform.position = groundTilemap.CellToWorld( pos );
    return doorInstance;
}

저렇게 dir에 따라 두가지 갈래로 나뉜 이유는 타일의 어느 것을 문으로 바꿀지 결정함에 있어 타일의 위 혹은 아래인지, 오른쪽 혹은 왼쪽인지 정해주기 위함이다. GenerateDoor 호출부에서 위, 아래 방향으로 문이 생성된다면 아래에 있는 문을 prevCell로 설정하면서 if문 분기를 4개에서 2개로 줄였다. 그리고 선언부에서는 문은 임의로 cell 크기의 2분의 1 지점의 타일을 문으로 바꿔주었다.
DrawDoor은 tile(기존에는 무조건 벽이다)의 sprite를 문으로 바꿔주고 isTrigger을 true로 바꿔주면서 TriggerEnter 이벤트를 활성화시켜준다. 이는 Door 클래스의 Awake함수에서 호출하여 DrawDoor에서 작업을 따로 추가하지 않고 Door 클래스에서 알아서 실행되도록 처리했다. 한 번에 마주보고 있는 문 2개를 그려준다.

동시에 2개 생성

플레이어가 문에 충돌하면 위에서 저장한 NextDoor의 위치로 이동시켜주면 된다.
 

결과

결과는 아래와 같다.(구버전)

 

TODO

이제 탐색한 방만들 보여주는 미니맵을 만들어볼 것이다. 그리고 타일 오브젝트가 너무 많이 생성되므로 바닥타일 한 개, 한 방향 벽들 한 개로 줄여보는 작업도 할 것이다.