wan의 개발일기
RoomCraft #14. 방을 다각형으로 - 사각형을 넘어서기 위한 첫걸음 본문
소스코드 → github.com/Jywan/RoomCraft
들어가며
지난 편에서 버그 두 개 잡고 가구 정보 패널을 통합했다. 이번엔 로드맵 순서를 좀 바꿨다 — 원래 다음 순서는 WebGL/모바일 배포였는데, 아직 최적화도 안 끝난 상태에서 배포부터 준비하는 게 이상해서 그건 맨 뒤로 미루고, 대신 L자형 방 같은 다양한 방 모양 지원을 먼저 하기로 했다.
근데 이게 생각보다 큰 작업이라, 세 단계로 나눴다.
- 다각형 기반 방 생성 (이번 편) — 바닥/벽을 사각형 전용이 아니라 어떤 모양이든 처리할 수 있게 구조 변경
- 프리셋 모양 선택 — 사각형/L자형 같은 미리 정의된 모양을 UI에서 고르기
- 커스텀 편집 — 사용자가 직접 꼭짓점을 찍어서 모양 만들기
이번 편은 1번만 다룬다. 겉으로는 사각형 방이 예전이랑 똑같이 나오는데, 속은 완전히 갈아엎었다.
사각형 전용 구조의 한계
원래 RoomData는 가로/세로만 갖고 있고, RoomBuilder는 그 값으로 바닥 큐브 하나 + 벽 4개를 축에 맞춰 딱 찍어내는 구조였다. L자형이나 커스텀 모양을 넣으려면 이 자체를 바꿔야 했다 — "사각형"도 "L자형"도 결국 꼭짓점 리스트 하나 던져주면 바닥+벽을 만들어주는 함수 위에서 처리하는 게 맞다.
RoomData — 꼭짓점 리스트 추가
기존 저장 파일이랑 호환되게, vertices가 없으면 width/depth로 사각형을 자동 생성하는 GetVertices()를 만들었다.
public List<Vector2> GetVertices()
{
if (vertices != null && vertices.Count >= 3)
return vertices;
float halfW = width / 2f;
float halfD = depth / 2f;
return new List<Vector2>
{
new Vector2(-halfW, -halfD),
new Vector2(halfW, -halfD),
new Vector2(halfW, halfD),
new Vector2(-halfW, halfD)
};
}
Ear Clipping — 다각형을 삼각형으로 쪼개기
바닥을 렌더링하려면 다각형을 삼각형들로 쪼개야 한다(GPU는 삼각형만 그릴 수 있으니까). 사각형이야 대각선 하나 그으면 끝이지만, L자형 같은 오목한 모양은 그렇게 간단하지 않다. Ear Clipping이라는 표준 알고리즘을 직접 구현했다 — 원리는 "다른 꼭짓점을 포함하지 않는 볼록한 삼각형(귀, ear)을 하나씩 잘라내면서 다각형을 줄여나간다"는 것.
public static List<int> Triangulate(List<Vector2> polygon)
{
List<int> indices = new List<int>();
List<Vector2> points = new List<Vector2>(polygon);
// Ear Clipping은 반시계방향(CCW) 기준이라, 시계방향이면 뒤집는다
if (SignedArea(points) < 0f)
points.Reverse();
List<int> remaining = new List<int>();
for (int i = 0; i < points.Count; i++)
remaining.Add(i);
while (remaining.Count > 3)
{
// 볼록하고, 다른 점을 안 품고 있는 삼각형(귀)을 찾아서 잘라낸다
// (본문 생략, 전체 코드는 PolygonUtils.cs 참고)
}
return indices;
}
삼각형 판정에 외적(cross product)을 두 군데 쓴다 — 세 점이 볼록한지 확인할 때, 그리고 어떤 점이 삼각형 안에 있는지 확인할 때. 둘 다 같은 Cross() 함수 하나로 처리했다.
RoomBuilder — 바닥은 메쉬, 벽은 변마다 하나씩
바닥은 삼각분할한 결과로 커스텀 Mesh를 만든다. 벽은 예전처럼 앞/뒤/좌/우 4개를 하드코딩하는 대신, 꼭짓점을 순서대로 돌면서 변(edge)마다 벽 하나씩 자동으로 생성한다.
private void CreateWalls(RoomData data, List<Vector2> outline)
{
float wallThickness = 0.1f;
for (int i = 0; i < outline.Count; i++)
{
Vector2 start = outline[i];
Vector2 end = outline[(i + 1) % outline.Count];
CreateWallSegment($"Wall_{i}", data, start, end, wallThickness);
}
}
사각형이면 꼭짓점 4개라 벽 4개, L자형이면 꼭짓점 6개라 벽 6개가 자동으로 나온다. 벽 하나의 회전각은 시작점→끝점 방향 벡터로 계산한다.
float dx = end.x - start.x;
float dz = end.y - start.y;
float angle = Mathf.Atan2(-dz, dx) * Mathf.Rad2Deg;
Unity의 Y축 회전은 로컬 X축을 월드 (cosθ, 0, -sinθ)로 매핑한다. 벽의 "긴 방향"(로컬 X)을 변의 방향과 맞추려고 이 관계를 거꾸로 풀어서 각도를 구했다.
삽질 기록
엉뚱한 Vector2를 참조해서 난 컴파일 에러
PolygonUtils.cs 맨 위에 using System.Numerics;를 썼는데, 이러면 Vector2가 Unity 타입이 아니라 .NET의 System.Numerics.Vector2로 잡힌다. 그 타입은 필드가 X/Y(대문자)라서, 코드에 있던 .x/.y가 전부 존재하지 않는 멤버가 돼서 컴파일이 안 됐다.
해결: using UnityEngine;으로 수정. using문 하나 잘못 쓴 걸로 타입 전체가 바뀌어버리는 걸 직접 겪어봤다.
사각형이 나비 모양으로 꼬임
GetVertices()의 사각형 기본값을 만들 때 꼭짓점 순서를 잘못 넣어서, 테두리를 따라 도는 게 아니라 대각선으로 교차하는 형태가 됐다. Ear Clipping은 자기교차 없는 단순 다각형을 전제로 하는 알고리즘이라 이대로면 바닥이 이상하게 나온다.
해결: 꼭짓점을 테두리 순서(좌하→우하→우상→좌상)대로 나열하도록 수정.
바닥이 마젠타로 렌더링됨
컴파일 에러 다 잡고 실행해봤더니 바닥이 새빨간 마젠타색으로 나왔다. Unity에서 마젠타는 "머티리얼(셰이더)이 없다"는 에러 표시다.
알고 보니 Floor Material/Wall Material이 애초에 할당돼 있지 않았다. 예전엔 GameObject.CreatePrimitive(PrimitiveType.Cube)로 바닥/벽을 만들어서, Unity가 자동으로 기본 머티리얼을 붙여줬던 것 — 그래서 머티리얼을 안 넣어도 티가 안 났다. 이번에 바닥을 커스텀 Mesh + AddComponent<MeshRenderer>() 방식으로 바꾸면서, 그 자동 할당이 없어져서 진짜 "머티리얼 없음" 상태가 그대로 드러난 거였다.
해결: URP Lit 셰이더로 머티리얼을 새로 만들어서 Floor Material/Wall Material에 연결. 벽도 사실 지금까지 "우연히" 기본 머티리얼로 굴러가고 있었던 거라, 이 참에 제대로 된 머티리얼로 교체했다.
바닥과 가구가 겹치는 곳에서 깜빡임
머티리얼까지 고치고 나니 이번엔 바닥 위에 가구를 올려둔 자리가 지지직거리며 깜빡였다. Z-파이팅이라는 현상인데, 바닥 메쉬와 가구 밑면이 둘 다 Y=0에 정확히 겹쳐서 렌더러가 어느 게 앞인지 매 프레임 헷갈려하며 생기는 깜빡임이다. 예전 큐브 바닥은 두께(0.1m)가 있어서 잘 안 드러났는데, 지금은 두께 없는 평면이라 바로 티가 났다.
해결: 바닥을 Y = -0.01로 살짝 내려서 가구 밑면이랑 안 겹치게 했다.
정리
| 기능 | 핵심 API / 구조 |
|---|---|
| 꼭짓점 기반 방 데이터 | RoomData.vertices + GetVertices() (하위호환용 사각형 fallback) |
| 다각형 삼각분할 | PolygonUtils.Triangulate() — Ear Clipping |
| 바닥 메쉬 생성 | Mesh + MeshFilter/MeshRenderer/MeshCollider |
| 벽 자동 생성 | 꼭짓점 순회하며 변마다 CreateWallSegment() 호출, Atan2로 회전각 계산 |
| 마젠타 버그 수정 | URP Lit 머티리얼 생성 후 Floor/Wall Material 연결 |
| Z-파이팅 수정 | 바닥 Y 위치를 -0.01로 오프셋 |
'1인 게임개발 활동 > 가구배치 시뮬레이터' 카테고리의 다른 글
| RoomCraft #16. 방 모양 편집을 일반/커스텀 탭으로 - 그리고 버그 네 마리 (0) | 2026.07.07 |
|---|---|
| RoomCraft #15. 방 모양 프리셋 — 사각형 넘어 L자형까지 (0) | 2026.07.05 |
| RoomCraft #13. 버그 두 개 잡고 가구 정보 패널 통합하기 (0) | 2026.07.05 |
| RoomCraft #12. 룸/가구 툴바 분리 + 색상 피커 만들기 (0) | 2026.07.05 |
| RoomCraft #11. 에디터 툴바 — 키보드에서 버튼으로 (0) | 2026.07.02 |