https://sharp2studio.tistory.com/45
이전 포스팅으로 BSP알고리즘을 통해서 맵을 나누고, 그 안에서 방을 생성하며 각 방끼리 연결을 어떤식으로 할 지에 대해서 알아봤다.
이번에는, 이전에 나눴던 영역에 타일을 깔고 collider를 넣는 작업을 해볼것이다.
우선, 적당한 타일 이미지를 받아온다.
https://assetstore.unity.com/packages/2d/environments/backyard-top-down-tileset-53854
나는 유니티 에셋스토어의 무료 에셋을 사용하였다.
먼저, Hierarchy창에 들어가 우클릭-2D Object-Tilemap 을 눌러서 tilemap을 생성해준다.
tilemap이 생성되면, Scene뷰에서 그리드와 같은 모습이 됨을 볼 수 있다.
그 후, Window-2D-Tile palette 을 눌러준다.
Create new palette를 눌러 경로를 지정해준 후,받아왔던 이미지를 드래그앤 드롭 해준다.
그러면 Project창에서 tile형태의 파일이 생기게 된다.
이제 코드로 넘어가면,
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Tilemaps;
public class MapGenerator :MonoBehaviour
{
[SerializeField] Vector2Int mapSize;
[SerializeField] float minimumDevideRate; //공간이 나눠지는 최소 비율
[SerializeField] float maximumDivideRate; //공간이 나눠지는 최대 비율
[SerializeField] private GameObject line; //lineRenderer를 사용해서 공간이 나눠진걸 시작적으로 보여주기 위함
[SerializeField] private GameObject map; //lineRenderer를 사용해서 첫 맵의 사이즈를 보여주기 위함
[SerializeField] private GameObject roomLine; //lineRenderer를 사용해서 방의 사이즈를 보여주기 위함
[SerializeField] private int maximumDepth; //트리의 높이, 높을 수록 방을 더 자세히 나누게 됨
[SerializeField] Tilemap tileMap;
[SerializeField] Tile roomTile; //방을 구성하는 타일
[SerializeField] Tile wallTile; //방과 외부를 구분지어줄 벽 타일
[SerializeField] Tile outTile; //방 외부의 타일
void Start()
{
FillBackground();//신 로드 시 전부다 바깥타일로 덮음
Node root = new Node(new RectInt(0, 0, mapSize.x, mapSize.y));
Divide(root, 0);
GenerateRoom(root, 0);
GenerateLoad(root, 0);
FillWall(); //바깥과 방이 만나는 지점을 벽으로 칠해주는 함수
}
void Divide(Node tree,int n)
{
if (n == maximumDepth) return; //내가 원하는 높이에 도달하면 더 나눠주지 않는다.
//그 외의 경우에는
int maxLength = Mathf.Max(tree.nodeRect.width, tree.nodeRect.height);
//가로와 세로중 더 긴것을 구한후, 가로가 길다면 위 좌, 우로 세로가 더 길다면 위, 아래로 나눠주게 될 것이다.
int split = Mathf.RoundToInt(Random.Range(maxLength * minimumDevideRate, maxLength * maximumDivideRate));
//나올 수 있는 최대 길이와 최소 길이중에서 랜덤으로 한 값을 선택
if (tree.nodeRect.width >= tree.nodeRect.height) //가로가 더 길었던 경우에는 좌 우로 나누게 될 것이며, 이 경우에는 세로 길이는 변하지 않는다.
{
tree.leftNode = new Node(new RectInt(tree.nodeRect.x,tree.nodeRect.y,split,tree.nodeRect.height));
//왼쪽 노드에 대한 정보다
//위치는 좌측 하단 기준이므로 변하지 않으며, 가로 길이는 위에서 구한 랜덤값을 넣어준다.
tree.rightNode= new Node(new RectInt(tree.nodeRect.x+split, tree.nodeRect.y, tree.nodeRect.width-split, tree.nodeRect.height));
//우측 노드에 대한 정보다
//위치는 좌측 하단에서 오른쪽으로 가로 길이만큼 이동한 위치이며, 가로 길이는 기존 가로길이에서 새로 구한 가로값을 뺀 나머지 부분이 된다.
}
else
{
tree.leftNode = new Node(new RectInt(tree.nodeRect.x, tree.nodeRect.y, tree.nodeRect.width,split));
tree.rightNode = new Node(new RectInt(tree.nodeRect.x, tree.nodeRect.y + split, tree.nodeRect.width , tree.nodeRect.height-split));
//DrawLine(new Vector2(tree.nodeRect.x , tree.nodeRect.y+ split), new Vector2(tree.nodeRect.x + tree.nodeRect.width, tree.nodeRect.y + split));
}
tree.leftNode.parNode = tree; //자식노드들의 부모노드를 나누기전 노드로 설정
tree.rightNode.parNode = tree;
Divide(tree.leftNode, n + 1); //왼쪽, 오른쪽 자식 노드들도 나눠준다.
Divide(tree.rightNode, n + 1);//왼쪽, 오른쪽 자식 노드들도 나눠준다.
}
private RectInt GenerateRoom(Node tree,int n)
{
RectInt rect;
if (n == maximumDepth) //해당 노드가 리프노드라면 방을 만들어 줄 것이다.
{
rect = tree.nodeRect;
int width = Random.Range(rect.width/2,rect.width-1);
//방의 가로 최소 크기는 노드의 가로길이의 절반, 최대 크기는 가로길이보다 1 작게 설정한 후 그 사이 값중 랜덤한 값을 구해준다.
int height=Random.Range(rect.height / 2,rect.height - 1);
//높이도 위와 같다.
int x = rect.x + Random.Range(1, rect.width - width);
//방의 x좌표이다. 만약 0이 된다면 붙어 있는 방과 합쳐지기 때문에,최솟값은 1로 해주고, 최댓값은 기존 노드의 가로에서 방의 가로길이를 빼 준 값이다.
int y = rect.y + Random.Range(1, rect.height - height);
//y좌표도 위와 같다.
rect = new RectInt(x, y, width, height);
FillRoom(rect);
}
else
{
tree.leftNode.roomRect = GenerateRoom(tree.leftNode,n+1);
tree.rightNode.roomRect = GenerateRoom(tree.rightNode, n + 1);
rect = tree.leftNode.roomRect;
}
return rect;
}
private void GenerateLoad(Node tree,int n)
{
if (n == maximumDepth) //리프 노드라면 이을 자식이 없다.
return;
Vector2Int leftNodeCenter = tree.leftNode.center;
Vector2Int rightNodeCenter = tree.rightNode.center;
for (int i=Mathf.Min(leftNodeCenter.x, rightNodeCenter.x);i<=Mathf.Max(leftNodeCenter.x, rightNodeCenter.x); i++)
{
tileMap.SetTile(new Vector3Int(i - mapSize.x / 2, leftNodeCenter.y - mapSize.y / 2, 0), roomTile);
}
for (int j = Mathf.Min(leftNodeCenter.y, rightNodeCenter.y); j <= Mathf.Max(leftNodeCenter.y, rightNodeCenter.y); j++)
{
tileMap.SetTile(new Vector3Int(rightNodeCenter.x - mapSize.x / 2, j - mapSize.y / 2, 0), roomTile);
}
//이전 포스팅에서 선으로 만들었던 부분을 room tile로 채우는 과정
GenerateLoad(tree.leftNode, n + 1); //자식 노드들도 탐색
GenerateLoad(tree.rightNode, n + 1);
}
void FillBackground() //배경을 채우는 함수, 씬 load시 가장 먼저 해준다.
{
for(int i = -10; i < mapSize.x+10; i++) //바깥타일은 맵 가장자리에 가도 어색하지 않게
//맵 크기보다 넓게 채워준다.
{
for(int j = -10; j < mapSize.y+10; j++)
{
tileMap.SetTile(new Vector3Int(i - mapSize.x / 2, j - mapSize.y / 2, 0), outTile);
}
}
}
void FillWall() //룸 타일과 바깥 타일이 만나는 부분
{
for (int i = 0; i < mapSize.x; i++) //타일 전체를 순회
{
for (int j = 0; j < mapSize.y; j++)
{
if(tileMap.GetTile(new Vector3Int(i - mapSize.x / 2, j - mapSize.y / 2, 0)) == outTile)
{
//바깥타일 일 경우
for(int x = -1; x <= 1; x++)
{
for(int y = -1; y <= 1; y++)
{
if (x == 0 && y == 0) continue;//바깥 타일 기준 8방향을 탐색해서 room tile이 있다면 wall tile로 바꿔준다.
if(tileMap.GetTile(new Vector3Int(i - mapSize.x / 2+x, j - mapSize.y / 2+y, 0)) == roomTile)
{
tileMap.SetTile(new Vector3Int(i - mapSize.x / 2, j - mapSize.y / 2, 0) , wallTile);
break;
}
}
}
}
}
}
}
private void FillRoom(RectInt rect) { //room의 rect정보를 받아서 tile을 set해주는 함수
for(int i = rect.x; i< rect.x + rect.width; i++)
{
for(int j = rect.y; j < rect.y + rect.height; j++)
{
tileMap.SetTile(new Vector3Int(i - mapSize.x / 2, j - mapSize.y / 2, 0), roomTile);
}
}
}
}
이전 포스팅의 Draw부분을, tile을 채우는 함수들로 변경한 부분이다.
MapGenerator.cs를 위와 같이 하고 실행을 하게 되면,
우리가 원하는 랜덤한 형태의 맵이 나오게 된다 !
추가적으로, 지나갈 수 없는 길과 지나갈 수 있는 길을 구분하고 싶다면,
처음에 만들었던 tilemap에, tilemap collider2d 컴포넌트를 추가해준다.
충돌이 되야 하는 타일의 collider type은 Sprite(형태대로) or Grid(그리드 대로) 로 해주고,
길로 만들 예정이여서 충돌이 없어야 되는 타일의 collider type은 None으로 해주면, 같은 타일이여도 collider가 있고 없고가 바뀌게 된다.
완성
위처럼 같은 코드여도, 타일에 따라서 다른 느낌의 맵 생성또한 가능하다 !!
https://github.com/raetic/BSP-algorhitm-With-Unity
프로젝트는 깃허브에 업로드 해 놨습니다!
'알고리즘 > 알고리즘 In game' 카테고리의 다른 글
[유니티] BSP 알고리즘을 이용해서 랜덤한 게임 맵 생성하기 [구현(1)] (1) | 2022.09.22 |
---|---|
[유니티] BSP 알고리즘을 이용해서 랜덤한 게임 맵 생성하기 [이론] (0) | 2022.09.22 |
[유니티]A* 알고리즘(에이스타 알고리즘)을 통해서 길찾기[구현] (1) | 2022.09.19 |
A* 알고리즘(에이스타 알고리즘)을 통해서 길찾기 구현[이론] (1) | 2022.09.19 |