■ 방 생성
BSP 알고리즘을 통해 공갈이 분할되면, 자식이 없는 단말(리프) 노드에 실제 방을 생성해야 한다. 단말 노드들은 더 이상 분할되지 않는 영역이기 때문이다.
private List<RoomNode> GetAllLeafNode()
{
var leafNodes = new List<RoomNode>();
var queue = new Queue<RoomNode>(new[] { _roomNodeList[0] });
while (queue.Count > 0)
{
var node = queue.Dequeue();
if (node.ChildNode.Count <= 0)
{
leafNodes.Add(node);
continue;
}
foreach (var chile in node.ChildNode)
{
queue.Enqueue((RoomNode)chile);
}
}
return leafNodes;
}
너비 우선 탐색(BFS) 방식으로 트리를 순회하며, 자식 노드가 없는 경우(단말 노드)만 리스트에 추가한다.
■ 방 생성 구현
public class RoomGenerator
{
private Vector2Int _offset;
private float _minWeight;
private float _maxWeight;
public void GenerateRoom(List<RoomNode> leafNode, DungeonData data)
{
_offset = data.Offset;
_minWeight = data.BottomLeftWeight;
_maxWeight = data.TopRightWeight;
foreach (var node in leafNode)
{
CreateRoomSpace(node);
}
}
}
방 생성을 위해 다음과 같이 RoomGenerator 클래스를 구현하였다.
- BSP 이진트리에서 단말 노드만 저장한 리스트, 방 생성에 필요한 정보를 파라미터로 받는다.
- 필요한 변수를 초기화 한 뒤, 단말 노드 안에 방을 생성한다.
1. 방의 좌하단(BL) 위치 구하기
private void CreateRoomSpace(RoomNode node)
{
var curPos = node.Pos;
// 최소, 최대 범위 계산
var min = new Vector2Int(curPos.BL.x + _offset.x, curPos.BL.y + _offset.y);
var max = new Vector2Int(curPos.TR.x - _offset.x, curPos.TR.y - _offset.y);
}
노드 경계에서 일정 거리만큼 떨어진 좌표를 시작점으로 사용하여, 새롭게 생성되는 방이 기존 노드와 겹치지 않도록 만든다.
private void CreateRoomSpace(RoomNode node)
{
// offset 계산 생략..
// 방이 생성될 수 있는 길이 구하기
var roomWidth = max.x - min.x;
var roomHeight = max.y - min.y;
var roomBL = new Vector2Int(
Random.Range(min.x, min.x + (int)(roomWidth * _minWeight)),
Random.Range(min.y, min.y + (int)(roomHeight * _minWeight)));
}
방이 생성될 수 있는 최대 범위는 min~max사이이다. 하지만 이 범위를 그대로 사용하면 우상단과 좌표가 겹칠 수 있으므로 일정 비율(Weight : 0.1~0.4)을 곱한 값을 최종 범위로 사용한다.
2. 방의 우상단(TR) 위치 구하기
// BL보다 무조건 커야함.
var minTRX = roomBL.x + (int)(roomWidth * _maxWeight);
var minTRY = roomBL.y + (int)(roomHeight * _maxWeight);
var roomTR = new Vector2Int(
Random.Range(minTRX, max.x),
Random.Range(minTRY, max.y));
node.AddRoomPosition(new NodePosition(roomBL, roomTR));
생성되는 방의 우상단 위치는 반드시 좌하단 좌표보다 커야 한다. 따라서 다음과 같이 우상단 좌표의 랜덤 범위를 구한다.
- 최소 범위 : 좌하단 좌표 + 최대 크기 * Weight(0.6~0.9)
- 최대 범위 : 기존 노드의 좌상단 좌표에서 -offset 만큼 이동한 좌표.
3. 방 위치 추가
public class RoomNode : BSP_Node
{
public int Width => Mathf.Abs(Pos.BR.x - Pos.TL.x);
public int Height => Mathf.Abs(Pos.TL.y - Pos.BL.y);
public NodePosition RoomPosition { get; private set; }
public RoomNode(BSP_Node parent, NodePosition position, int index) : base(parent)
{
Pos = position;
Index = index;
RoomPosition = null;
}
public void AddRoomPosition(NodePosition position)
{
RoomPosition = position;
}
}
노드에 방 위치 정보를 저장하기 위해 RoomPosition변수를 추가한다.
- NodePosition의 생성자는 좌상단, 우하단 좌표를 기준으로 나머지 좌표를 계산하므로 방의 좌표를 넘길 때도 동일하게 처리해주어야 한다.
■ 바닥 메시 생성
using System;
using System.Collections.Generic;
using UnityEngine;
using Random = UnityEngine.Random;
public class MeshGenerator : MonoBehaviour
{
[Header("Component")]
[SerializeField] private Material[] floorMat;
public void CreateMesh(NodePosition position)
{
var vertices = new Vector3[]
{
ConvertNodePositionToVector3(position.TL),
ConvertNodePositionToVector3(position.TR),
ConvertNodePositionToVector3(position.BL),
ConvertNodePositionToVector3(position.BR)
};
var uvs = new Vector2[vertices.Length];
for (var i = 0; i < uvs.Length; ++i)
{
uvs[i] = new Vector2(vertices[i].x, vertices[i].z);
}
var triangles = new int[]
{
0,1,2,2,1,3
};
var mesh = new Mesh
{
vertices = vertices,
uv = uvs,
triangles = triangles
};
var floor = new GameObject(position.BL + "Mesh",
typeof(MeshFilter), typeof(MeshRenderer));
floor.transform.SetParent(transform);
floor.GetComponent<MeshFilter>().mesh = mesh;
floor.GetComponent<MeshRenderer>().material = floorMat[Random.Range(0, floorMat.Length)];
}
private Vector3 ConvertNodePositionToVector3(Vector2Int pos)
{
return new Vector3(pos.x, 0, pos.y);
}
}
좌상단(TL), 우상단(TR), 좌하단(BL), 우하단(BR) 좌표를 기준으로 사각형 형태의 정점을 설정한다.
- 각 정점의 x,z 좌표를 기준으로 UV좌표를 설정하여 텍스쳐가 매핑되도록 한다.
정점 0-1-2, 2-1-3을 기준으로 두 개의 삼각형을 구성한 뒤, 생성한 메쉬를 GameObject에 적용한다.
- 바닥 머터리얼을 하나로 통일하고 싶다면, 마지막 floorMat[Random.Range(0, floorMat.Length)];를 수정하면 된다.
■ 결과
이렇게 분할된 공간 안에 방이 생성되고 방바닥이 위치한 결과를 확인할 수 있다. 다음에는 BSP알고리즘을 조금 개선해 보자.
'Unity,C# > 절차적생성(PCG)' 카테고리의 다른 글
[C#, Unity, 절차적 생성] 절차적 던전 생성 - 4. 복도 생성 (1) | 2025.05.21 |
---|---|
[C#, Unity, 절차적 생성] 절차적 던전 생성 - 3. BSP 알고리즘 개선 (1) | 2025.05.12 |
[C#, Unity, 절차적 생성] 절차적 던전 생성 - 1. Binary Space Partitioning (1) | 2025.05.07 |
[C#, Unity, 절차적 생성] 절차적 지형 생성 - 3.터레인 생성 (0) | 2025.04.28 |
[C#, Unity, 절차적 생성] 절차적 지형 생성 - 2.프랙탈 합 (2) | 2025.04.24 |