wan의 개발일기
RoomCraft 개발일지 #1 — 프로젝트 설정 + 방 생성 시스템 본문
소스코드 → github.com/Jywan/RoomCraft
들어가며
C# 공부하면서 네트워크까지 찍먹하고 나니까, 이제 뭔가 만들어보고 싶어졌다. 그래서 Unity를 처음 열어봤다.
뭘 만들까 고민하다가, 이사할 때마다 겪는 문제가 떠올랐다. “이 책상이 내 방에 들어가긴 하는 건가?” 줄자로 재고 머릿속으로 그려보는데, 막상 옮기면 안 맞아서 짜증났던 기억.
그래서 실제 치수를 입력하면 3D로 배치를 확인할 수 있는 시뮬레이터를 만들어보기로 했다. 이름은 RoomCraft.
Unity 완전 처음이라 삽질도 많이 할 것 같은데, 그 과정을 기록으로 남겨두려 한다.
뭘 만들 건지
핵심은 간단하다. “내 가구가 이 방에 들어갈까?”를 3D로 확인하는 도구.
- 방 크기를 m 단위로 입력하면 3D 방이 생성됨
- 가구를 cm 단위로 만들어서 방 안에 드래그로 배치
- 위에서 내려다보는 탑뷰 ↔ 3D 자유시점 전환
- 예쁜 그래픽이 목적이 아니라, 치수 정확성이 핵심
기술 스택
| 구분 | 기술 |
|---|---|
| 엔진 | Unity 6 (6000.3.x) |
| 언어 | C# |
| 렌더링 | URP |
| 3D 모델링 | ProBuilder |
| 카메라 | Cinemachine |
| 입력 | New Input System |
| 저장 | JSON |
프로젝트 세팅
Unity Hub에서 3D (URP) 템플릿으로 생성했다. 나중에 모바일이랑 WebGL로 빌드할 생각이라 URP로 갔다.
프로젝트 만들고 나서 Package Manager에서 패키지 3개를 추가로 설치했다.
| 패키지 | 왜 필요한지 |
|---|---|
| ProBuilder | 가구 모양을 코드로 만들려고 |
| Cinemachine | 카메라 시점 전환용 |
| Input System | 터치/마우스 입력을 하나로 처리하려고 |
그리고 폴더 구조를 미리 잡아뒀다. 나중에 파일 많아지면 찾기 힘드니까.
Assets/
├── Scripts/
│ ├── Room/ ← 방 생성
│ ├── Furniture/ ← 가구
│ ├── Camera/ ← 카메라
│ ├── UI/ ← UI
│ ├── Data/ ← 데이터 모델
│ └── Input/ ← 입력 처리
├── Prefabs/
├── Materials/
└── Scenes/
방 생성 시스템
일단 방부터 만들어야 가구를 넣든 말든 하니까, 방 생성을 먼저 구현했다.
구조는 단순하다. 가로/세로/높이를 받아서 바닥 하나 + 벽 4개를 생성하는 거다.
RoomData — 방 정보 클래스
일단 방 치수를 담을 데이터 클래스부터 만들었다. [Serializable] 붙여놓으면 나중에 JSON 저장할 때 편하다길래 미리 붙여둠.
using UnityEngine;
using System;
namespace RoomCraft.Data
{
[Serializable]
public class RoomData
{
public float width; // 가로 (m)
public float depth; // 세로 (m)
public float height; // 높이 (m)
public string roomName;
public RoomData(float width, float depth, float height, string roomName = "새 방")
{
this.width = width;
this.depth = depth;
this.height = height;
this.roomName = roomName;
}
}
}
RoomBuilder — 실제로 방을 만드는 놈
이게 핵심이다. BuildRoom()을 호출하면 바닥이랑 벽 4면이 뿅 하고 생긴다.
접근 방식은 간단한데, Unity의 CreatePrimitive(Cube)로 큐브를 하나 만들고, scale을 방 치수에 맞게 늘리는 거다. 정교한 모델링이 아니라 치수 확인이 목적이니까 이 정도면 충분하다.
using RoomCraft.Data;
using UnityEngine;
namespace RoomCraft.Room
{
public class RoomBuilder : MonoBehaviour
{
[Header("Room Settings")]
[SerializeField] private Material floorMaterial;
[SerializeField] private Material wallMaterial;
private GameObject currentRoom;
public void BuildRoom(RoomData data)
{
// 기존 방이 있으면 날리고 새로 만듦
if (currentRoom != null)
Destroy(currentRoom);
currentRoom = new GameObject($"Room_{data.roomName}");
CreateFloor(data);
CreateWalls(data);
}
private void CreateFloor(RoomData data)
{
GameObject floor = GameObject.CreatePrimitive(PrimitiveType.Cube);
floor.name = "Floor";
floor.transform.parent = currentRoom.transform;
// 바닥: 가로 x 세로, 두께는 0.1m로 얇게
floor.transform.localScale = new Vector3(data.width, 0.1f, data.depth);
floor.transform.localPosition = new Vector3(0f, -0.05f, 0f);
if (floorMaterial != null)
floor.GetComponent<Renderer>().material = floorMaterial;
floor.tag = "Floor";
}
private void CreateWalls(RoomData data)
{
float wallThickness = 0.1f;
CreateWall("Wall_Back", data,
new Vector3(0f, data.height / 2f, data.depth / 2f),
new Vector3(data.width, data.height, wallThickness));
CreateWall("Wall_Front", data,
new Vector3(0f, data.height / 2f, -data.depth / 2f),
new Vector3(data.width, data.height, wallThickness));
CreateWall("Wall_Left", data,
new Vector3(-data.width / 2f, data.height / 2f, 0f),
new Vector3(wallThickness, data.height, data.depth));
CreateWall("Wall_Right", data,
new Vector3(data.width / 2f, data.height / 2f, 0f),
new Vector3(wallThickness, data.height, data.depth));
}
private void CreateWall(string name, RoomData data, Vector3 position, Vector3 scale)
{
GameObject wall = GameObject.CreatePrimitive(PrimitiveType.Cube);
wall.name = name;
wall.transform.parent = currentRoom.transform;
wall.transform.localPosition = position;
wall.transform.localScale = scale;
if (wallMaterial != null)
wall.GetComponent<Renderer>().material = wallMaterial;
wall.tag = "Wall";
}
}
}
바닥은 Y를 -0.05로 내려서 윗면이 딱 Y=0에 오게 했고, 벽은 Y를 height/2로 올려서 바닥 위에 서게 했다. 처음에 이거 안 맞아서 벽이 바닥 밑으로 빠지는 걸 보고 당황했었다.
실행 결과
테스트로 4m × 3m × 2.5m 방을 생성해봤다.
RoomData testRoom = new RoomData(4f, 3f, 2.5f, "테스트방");
builder.BuildRoom(testRoom);
Play 누르니까 방이 생겼다.
아직 Material을 안 넣어서 못생기긴 한데, 일단 구조는 맞다. 벽 4면 + 바닥 1개. 나중에 색 입히면 그럴싸해질 거다.
삽질 기록
Rider 자동완성이 안 됨
코드 치는데 자동완성이 하나도 안 떴다. MonoBehaviour를 치는데 빨간줄이 뜨길래 한참 헤맸다.
원인: Rider를 먼저 켜놓고 Unity에서 스크립트를 열면 .sln 연결 없이 파일만 열린다.
해결: Rider에서 RoomCraft_Unity.sln을 직접 열거나, Rider 닫고 Unity에서 더블클릭으로 열면 된다.
Tag 등록 안 하면 에러
floor.tag = "Floor" 했더니 “Tag Floor is not defined” 에러가 떴다. 코드에서 태그를 쓰려면 Unity에서 먼저 등록해야 한다는 걸 몰랐다.
해결: Edit → Project Settings → Tags and Layers에서 수동 추가. Unity 초보 실수 1호.
정리
| 기능 | 핵심 구현 |
|---|---|
| 방 데이터 | RoomData — [Serializable] 클래스로 치수 저장 |
| 바닥 생성 | CreatePrimitive(Cube) + localScale로 크기 조절 |
| 벽 4면 | CreateWall() — 위치/크기 파라미터화해서 반복 호출 |
| 방 리셋 | Destroy(currentRoom) 후 새로 생성 |
다음 편에서는 카메라 시스템을 만든다. 위에서 내려다보는 탑뷰랑, 자유롭게 돌려볼 수 있는 3D 뷰를 전환하는 기능을 구현해보려고 한다.
'1인 게임개발 활동 > 가구배치 시뮬레이터' 카테고리의 다른 글
| RoomCraft #6. 카테고리별 동적 모델 생성 — 팩토리 패턴으로 가구 형태 조합 (0) | 2026.06.28 |
|---|---|
| RoomCraft #5. 가구 생성 UI — 카테고리/치수/색상 입력 팝업 (0) | 2026.06.28 |
| RoomCraft 개발일지 #4. 충돌 감지 — 벽 밖 제한 + 겹침 경고 (0) | 2026.06.27 |
| RoomCraft 개발일지 #3. 가구 드래그 앤 드롭 — 선택, 이동, 회전, 삭제 (0) | 2026.06.26 |
| RoomCraft 개발일지 #2. 카메라 시스템 — 탑뷰 ↔ 3D 뷰 전환 (0) | 2026.06.25 |