wan의 개발일기
RoomCraft 개발일지 #2. 카메라 시스템 — 탑뷰 ↔ 3D 뷰 전환 본문
소스코드 → github.com/Jywan/RoomCraft
들어가며
지난 편에서 방 생성까지 해봤다. 바닥이랑 벽이 생기긴 하는데, 카메라가 비스듬한 각도에 고정돼 있어서 방 전체를 제대로 볼 수가 없었다.
이번에는 카메라 시스템을 구현한다. 위에서 내려다보는 탑뷰와, 자유롭게 돌려볼 수 있는 3D 뷰를 만들고 T키로 전환하는 기능.
카메라 모드 설계
두 가지 모드가 필요하다:
| 모드 | 용도 | 조작 |
|---|---|---|
| 탑뷰 | 평면도처럼 위에서 내려다보기 | 스크롤로 줌만 |
| 3D 뷰 | 실제 방 안 느낌 확인 | 드래그로 회전 + 스크롤 줌 |
전환할 때 뚝 끊기면 어색하니까 부드러운 애니메이션도 넣기로 했다.
구조는 간단하다. 카메라가 targetPoint(방 중심)를 항상 바라보게 하고, 모드에 따라 위치만 바꿔주면 된다. 탑뷰는 바로 위에서, 3D 뷰는 구면 좌표계로 공전하는 위치에서.
CameraController 구현
구조 잡기
하나의 스크립트에서 두 모드를 모두 처리한다. enum으로 모드를 나누고, Update에서 현재 모드에 따라 다르게 동작하게 했다.
public enum CameraMode
{
TopView,
FreeView
}
탑뷰 — 위에서 내려다보기
탑뷰는 단순하다. 카메라를 targetPoint 바로 위에 놓고, 아래를 바라보게 회전하면 끝이다.
private void ApplyTopView()
{
transform.position = targetPoint + Vector3.up * topViewHeight;
transform.rotation = Quaternion.Euler(90f, 0f, 0f);
}
Quaternion.Euler(90, 0, 0)이 X축으로 90도 회전 = 아래를 바라보는 거다. 처음엔 왜 90도인지 감이 안 왔는데, Unity에서 카메라 기본 방향이 Z+ 정면이라서 X축으로 돌리면 아래를 보게 된다.
3D 뷰 — 구면 좌표계로 공전
3D 뷰는 좀 더 복잡하다. 수평 각도(horizontalAngle)와 수직 각도(verticalAngle)로 카메라 위치를 계산한다.
private void ApplyFreeView()
{
float rad_h = horizontalAngle * Mathf.Deg2Rad;
float rad_v = verticalAngle * Mathf.Deg2Rad;
Vector3 offset = new Vector3(
distance * Mathf.Cos(rad_v) * Mathf.Sin(rad_h),
distance * Mathf.Sin(rad_v),
distance * Mathf.Cos(rad_v) * Mathf.Cos(rad_h)
);
transform.position = targetPoint + offset;
transform.LookAt(targetPoint);
}
수학적으로는 구면 좌표계(Spherical Coordinates)다. 수평 각도로 좌우 공전, 수직 각도로 위아래 각도 조절, distance로 줌. LookAt(targetPoint)가 항상 방 중심을 바라보게 해준다.
드래그로 회전
3D 뷰에서 좌클릭 드래그하면 카메라가 회전한다. 마우스 이동량(delta)을 각도에 더해주는 방식.
private void HandleFreeViewInput()
{
// 좌클릭 드래그로 회전
if (Input.GetMouseButtonDown(0))
{
isDragging = true;
lastMousePosition = Input.mousePosition;
}
if (Input.GetMouseButtonUp(0))
{
isDragging = false;
}
if (isDragging)
{
Vector3 delta = Input.mousePosition - lastMousePosition;
horizontalAngle += delta.x * rotationSpeed * 0.1f;
verticalAngle -= delta.y * rotationSpeed * 0.1f;
verticalAngle = Mathf.Clamp(verticalAngle, minVerticalAngle, maxVerticalAngle);
lastMousePosition = Input.mousePosition;
ApplyFreeView();
}
}
원래는 마우스 우클릭 드래그로 만들었는데, 맥북 트랙패드에서 우클릭 드래그가 너무 불편해서 좌클릭으로 바꿨다.
모드 전환 애니메이션
T키를 누르면 현재 위치에서 목표 위치까지 부드럽게 이동한다. SmoothStep으로 보간하면 시작과 끝이 천천히, 중간이 빠르게 움직여서 자연스럽다.
private void UpdateTransition()
{
transitionProgress += Time.deltaTime / transitionDuration;
if (transitionProgress >= 1f)
{
transitionProgress = 1f;
isTransitioning = false;
}
// SmoothStep으로 부드러운 전환
float t = Mathf.SmoothStep(0f, 1f, transitionProgress);
transform.position = Vector3.Lerp(transitionStartPos, transitionEndPos, t);
transform.rotation = Quaternion.Slerp(transitionStartRot, transitionEndRot, t);
}
위치는 Vector3.Lerp, 회전은 Quaternion.Slerp로 보간한다. 회전에 Lerp를 쓰면 짐벌락 문제가 생길 수 있어서 Slerp를 쓴다고 한다. (솔직히 아직 체감은 못 했다)
실행 결과
Main Camera에 스크립트를 붙이고 Play.
T키를 누르면 부드럽게 3D 뷰로 전환된다.
좌클릭 드래그로 대각선으로 돌려봤다.
삽질 기록
Input System 충돌
New Input System 패키지를 설치한 상태에서 Input.GetKeyDown()을 쓰니까 InvalidOperationException이 터졌다. 레거시 Input API가 비활성화된 거였다.
해결: Project Settings → Player → Other Settings → Active Input Handling을 Both로 변경. 이러면 New Input System과 레거시 API를 동시에 쓸 수 있다.
카메라 회전 감도가 너무 낮음
처음에 Time.deltaTime을 곱했더니 드래그를 엄청 길게 해야 겨우 조금 움직였다. 마우스 delta 값 자체가 픽셀 단위라서 Time.deltaTime까지 곱하면 값이 너무 작아지는 거였다.
해결: Time.deltaTime 대신 고정 배수 0.1f를 사용. Inspector에서 rotationSpeed 값으로 감도 조절 가능.
정리
| 기능 | 핵심 구현 |
|---|---|
| 탑뷰 | Vector3.up * height + Euler(90,0,0) |
| 3D 뷰 | 구면 좌표계 — Sin/Cos로 offset 계산 + LookAt |
| 드래그 회전 | 마우스 delta → 수평/수직 각도 변경 |
| 줌 | mouseScrollDelta → distance/height 조절 |
| 전환 애니메이션 | SmoothStep + Lerp/Slerp |
다음 편에서는 가구를 만들어서 방 안에 드래그로 배치하는 인터랙션을 구현해보려고 한다.
'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 개발일지 #1 — 프로젝트 설정 + 방 생성 시스템 (0) | 2026.06.25 |