본문 바로가기
# 공부/# 알고리즘

Procedural Dungeon Generation in Unity #2

by 쁘레레레레레 2020. 7. 24.

이전 글 : https://atli-yeondi.tistory.com/29

 

Procedural Dungeon Generation in Unity #1

https://atli-yeondi.tistory.com/28 [완성] Procedural Dungeon Generation 참고 : https://github.com/a327ex/blog/issues/7 Procedural Dungeon Generation #2 · Issue #7 · a327ex/blog 2015-08-30 22:29 Th..

atli-yeondi.tistory.com

 

오늘 해야 할 작업은

벽과 바닥 타일을 얇게 깔아준 후  홀의 쓰임새를 나누고 사다리 타일을 깔아줄 생각이다.

 

모든 타일은 임시로 집어넣을 생각이고 실제로 적용 시엔 제대로 계산을 하고 제대로 된 타일을 알맞게 배치할 예정이다.


1. 바닥 깔기

    private void setTilesOnMainRooms()
    {
        foreach (Room main in mainRooms)
        {
            int nRandom = Random.Range(0, floorTiles.Length);
            float xSize = ((main.pos.x + main.pos.width) - main.pos.x) + 1;
            float ySize = ((main.pos.y + main.pos.height) - main.pos.y) + 1;

            for (int i = 0; i < xSize; i++)
            {
                for (int j = 0; j < ySize; j++)
                {
                    GameObject toInstantiate = floorTiles[nRandom];
                    outLineDetected = false;
                    if (i == 0 || i == main.pos.width || j == 0 || j == main.pos.height)
                    {
                        toInstantiate = outLineWalls[Random.Range(0, outLineWalls.Length)];
                        outLineDetected = true;
                    }

                    if (j != 1 && !outLineDetected)
                        continue;

                    GameObject instance = Instantiate(toInstantiate, new Vector2(main.pos.x + i, main.pos.y + j), Quaternion.identity) as GameObject;


                }
            }
        }
    }

바뀐코드는 별로 없다.

그래도 대략적인 설명을 다시 하자면

범위 안에 들어가야 할 타일의 수를 xSize, ySize로 계산을 한 후 x, y가 0이거나 맨 끝일 때 경계 타일을 깔아준다.

 

이후에 짧은 코드가 추가 됐는데 y값(여기선 j)이 1이 아니거나 outLineDetected가 거짓이면 이후의 코드를 스킵한 후 j값을 증감한다.

 

코드를 보면 outLineDetected = 경계선 감지

라는 코드라서  일단 얇게 깔아줄 생각으로 y의 0번지엔 경계 타일이 깔리니 y가 1이 됐을 때 1층으로만 얇게 깔아준 후 나머지는 빈칸으로 둔다는 뜻이다. 

경계 감지 코드를 넣지 않으면 침범할 수 있으니 j가 1이여도 경계선이 감지되면 깔지 않는다.

 


2. 벽깔기

 

  private void setTilesOnMaps()
    {
        foreach (Room main in mainRooms)
        {
            int nRandom = Random.Range(0, floorTiles.Length);
            float xSize = ((main.pos.x + main.pos.width) - main.pos.x) + 1;
            float ySize = ((main.pos.y + main.pos.height) - main.pos.y) + 1;

            for (int i = 0; i < xSize; i++)
            {
                for (int j = 0; j < ySize; j++)
                {
                    GameObject toInstantiate = floorTiles[nRandom];
                    outLineDetected = false;
                    if (i == 0 || i == main.pos.width || j == 0 || j == main.pos.height)
                    {
                        toInstantiate = outLineWalls[Random.Range(0, outLineWalls.Length)];
                        outLineDetected = true;
                    }
                    else if((i==1 || i == main.pos.width -1) && j != 1)
                    {
                        toInstantiate = sideWallTiles[Random.Range(0, sideWallTiles.Length)];
                    }
                    else if (j != 1 && !outLineDetected)
                        continue;

                    GameObject instance = Instantiate(toInstantiate, new Vector2(main.pos.x + i, main.pos.y + j), Quaternion.identity) as GameObject;


                }
            }
        }
    }

코드 작성 중 조금 난잡해져서 중간에 정리를 살짝 했는데.. 여전히 맘에 안 든다

 

먼저 설명 전 

 

변수를 선언한 부분은

    public GameObject[] floorTiles;  // 바닥 타일들
    public GameObject[] outLineWalls;  // 경계 타일들
    public GameObject[] cellingTiles;  // 천장 타일들 ( 현재는 안쓰임 )
    public GameObject[] sideWallTiles;  // 벽타일들
    public GameObject ladderTile;  // 단일 사다리 타일
    public GameObject ColorTileOnlyForDebug; // 디버그용 타일 ( 현재는 가로 홀 범위 인식용으로 쓰이는 중 글 하단에 작성 )

 

이런 식으로 선언했다.

 

경계 타일이 0번 지거나 끝 번지이니 벽타일은 간단하게 i == 1이거나 끝 -1번지에 깔라고 해주면 간단하다.

if문 끝에 j!=1을 넣어준 이유는 단순 취향이다.

저렇게 해주지 않을 경우 바닥 타일을 뒤늦게 벽타일이 덮어버리기 때문에 보기 싫어서 저렇게 해줬을 뿐 안 그래도 무방하다.


3. 홀 나누기 및 사다리 작업

 

    private void setTilesOnHalls()
    {
        foreach (Room hall in halls)
        {
            float xSize = ((hall.pos.x + hall.pos.width) - hall.pos.x) + 1;
            float ySize = ((hall.pos.y + hall.pos.height) - hall.pos.y) + 1;

            for (int i = 0; i < xSize; i++)
            {
                for (int j = 0; j < ySize; j++)
                {
                    GameObject toInstantiate = ladderTile;
                    if (hall.isHorizontal)
                    {
                        if (i == 0 || j == 0 || i == hall.pos.width || j == hall.pos.height)
                        {
                            //toInstantiate = outLineWalls[Random.Range(0, outLineWalls.Length)];
                            toInstantiate = ColorTileOnlyForDebug;
                        }
                    }
                    Instantiate(toInstantiate, new Vector2(hall.pos.x + i, hall.pos.y + j), Quaternion.identity);
                }
            }
        }
    }



    private void decisionHorizontalOrVertical(Room r)
    {
        //홀이 세로홀인지 가로홀인지 판단하는 함수 decision whether it is Horizontal or Vertical?
        float width = r.pos.width;
        float height = r.pos.height;
        if (width > height)
            r.isHorizontal = true;
        else if (height > width)
            r.isVertical = true;
        else if (height == width && height == 1 && width == 1)
        {
            Debug.Log("(1,1)인 스몰 브릿지, 룸의 가운데에 있는지 확인 " + r.name);
        }
        else
            Debug.Log("판단 오류입니다. 판단 룸 번호 : " + r.name);
    }

앵커 포인트가 중앙에 있어서 그런가 저런 식으로 사다리가 그려졌는데  어차피 테스트이니 상관하지 않기로 했다.

함수 명대로 기존엔 메인룸에 타일을 까는 용도로 사용했다면 이번엔 홀을 까는 용도로 이름을 살짝 변경해서 만들었다.

 

기존 함수 사용법은 비슷하고 toInstantiate에 ladderTile을 넣어준 후 경계 타일인 경우와 Horizontal인 경우를 제외하고 깔도록 했고

가로 홀인 경우 푸른색 반투명한 타일을 깔도록 했다. 위에 선언했던 colorTileOnlyForDebug이다.

 

밑에 있는 함수는 만들면서 새로 추가한 함수인데 주석대로 세로 홀/가로 홀을 판단해주는 함수이다.

decisionWhetherItisHorizontalOrVertical로 하려다가 대충 줄여서 decisionHorizontalOrVertical로 만들었다.

 

기존에 만들었던 makeBridge함수 *방과 방사 이를 이어주는 홀을 만드는 함수*에서 Room hall;로 따로 불러서 r.isHall = true라는 구문을 이용해 따로 함수를 만들어 꽂아준 Room을 이용해 참/거짓을 꽂아주는? 함수이다.

 

참고로 Room에도 따로

	public bool isHorizontal = false;
	public bool isVertical = false;

이런 식으로 새로 만들어주었다.

 

가져온 코드이지만 내 스타일로 조금씩 변경하고 있다.

 

문제는 이제부터다.

 

문제는 대충 그려놓았지만 몇 가지가 있다.

1. 붉은색 사각형을 보면 사다리가 중간에 끊겨있다.

2. 녹색 타원은 원본 게임의 캐릭터 크기인데, 가운데 방에 있는 파란선은 캐릭터의 맥시멈 점프 높이이다.

그래서 방이 저 세 개 방식의 방으로 나올 경우 사다리는커녕 플레이어는 저기서 평생 머물게 된다.

3. 좌측 방을 보면 가로 홀 두 개가 방 가운데서 만나 짧은 사다리로 이어진다. 

사실 저 정도야 지형으로 커버 친다 쳐도 그렇게 되면 캐릭터의 크기가 문제가 된다.

 

여기서 붉은 사각형의 원인은 기존에 debugDrawLine으로 선을 그어준 후 홀을 만들어 이어준 건데

그 코드를 보면

 neighborGraph[RoomA].Add(new LineSegment(RoomA.pos.center, RoomB.pos.center));

이런 식인데 RoomA와 RoomB사이를 이어줄 때 각 방의 가운데 지점부터 이어주었기 때문에 저런 상황이 벌어졌다.

 

이때 해결할 수 있는 상황은 일단 생각나는 방법으로는

1. 바닥 타일을 깔아줄 때 일정 값을 가져와서 그 바닥과 2타일 이상 차이 날 경우 밑으로 더 이어준다거나 하는 방법

2. 각 방의 규칙 된 룰을 설정해서 각 방에 닿을 경우 사다리를 룰에 따라 어느 타일까지 내리는 방법

등이 있다고 생각은 한다.

 

이쯤 되면 사이드 스크롤링 방식에 회의감을 살짝 느낀다.

 


+) 추가

4. 사다리 문제 임시 수정

 

기존에 있던 함수들을 살짝 임시로 수정했다. 더 수정해야하는지 여부는 콜라이더와 리지드바디를 추가한뒤 캐릭터로 움직여보고 판단해야할듯 하다.

 

기존에 drawLine을 룸(센터) - 룸(센터)로 이어줬던 방식을 조금 수정해

bottomCenter와 연결하도록 설정했다.

 

대신 기존엔 Rect클래스에서 지원하던 center함수를 썻던 반면  bottomCenter는 따로 지원을 안하기때문에

Room클래스로 가서

	public Vector2 GetBottomCenter()
    {
		float x = this.pos.center.x;
		float y = this.GetBottom() + 2.0f;
		return new Vector2(x, y);
    }

이와 같이 만들어주었다.

 

x값은 가운데를 유지를 하면서 y값만 bottom으로 주었는데

사다리가 밑으로 향하지도 않으면서 바닥을 뚫어버리는것 또한 맘에 들지 않아서

임의로 2타일 위로 밀어주었다.

 

아직 많이 이상한 부분이 많아서 어색하지만 차차 수정할 문제이니 현재는 이정도에서 만족하고

 

이후 캐릭터까지 추가해 테스트를 할때 수정할 목록을 한번에 만들어서 고치는게 편할것 같다!

 

앞으로 해야할 과제 :

1. 콜라이더와 바디 추가

2. 캐릭터 추가

3. 벽을 조금 뚫어서 캐릭터가 이동가능하게 설정

4. 이동 후 문제점을 파악해서 차례로 수정