wan의 개발일기

RoomCraft 개발일지 #3. 가구 드래그 앤 드롭 — 선택, 이동, 회전, 삭제 본문

1인 게임개발 활동/가구배치 시뮬레이터

RoomCraft 개발일지 #3. 가구 드래그 앤 드롭 — 선택, 이동, 회전, 삭제

wan_ 2026. 6. 26. 22:58
소스코드 → github.com/Jywan/RoomCraft

들어가며

방이 생기고 카메라도 돌아가니까, 이제 드디어 가구를 넣을 차례다.

이번 편에서는 가구 데이터 구조를 만들고, 실제로 씬에 배치한 뒤 클릭으로 선택, 드래그로 이동, R키로 회전, Delete로 삭제하는 인터랙션을 구현한다.


가구 데이터 설계

가구 하나를 표현하려면 뭐가 필요할까? 이름, 카테고리, 치수, 색상 정도면 될 것 같다.

FurnitureData — 가구 정보 클래스

치수는 사용자가 cm로 입력하지만, Unity 내부에서는 m로 변환해서 쓴다. 1 Unity unit = 1m이기 때문.

using System;
using UnityEngine;

namespace RoomCraft.Data
{
    [Serializable]
    public class FurnitureData
    {
        public string id;
        public string furnitureName;
        public FurnitureCategory category;

        public float widthCm;    // 가로 (cm)
        public float depthCm;    // 세로 (cm)
        public float heightCm;   // 높이 (cm)

        public Color color;

        public FurnitureData(string name, FurnitureCategory category,
            float widthCm, float depthCm, float heightCm)
        {
            this.id = Guid.NewGuid().ToString();
            this.furnitureName = name;
            this.category = category;
            this.widthCm = widthCm;
            this.depthCm = depthCm;
            this.heightCm = heightCm;
            this.color = Color.white;
        }

        /// cm를 m로 변환. Unity에서 1 unit = 1m.
        public Vector3 GetSizeInMeters()
        {
            return new Vector3(widthCm / 100f, heightCm / 100f, depthCm / 100f);
        }
    }
}
Guid.NewGuid()로 고유 ID를 붙여둔다. 나중에 저장/불러오기 할 때 같은 가구인지 식별할 수 있게.

FurnitureCategory

카테고리는 enum으로 정의했다. 나중에 카테고리별로 다른 형태의 3D 모델을 생성할 때 쓸 예정.

public enum FurnitureCategory
{
    Bed, Desk, Chair, Wardrobe, Sofa,
    Bookshelf, TV, Fridge, Other
}

씬에 가구 배치하기

FurnitureObject — 개별 가구 컴포넌트

씬에 놓인 각 가구에 붙는 스크립트다. 데이터를 받아서 크기/색상을 적용하고, 선택/이동/회전 기능을 제공한다.

public void Initialize(FurnitureData furnitureData)
{
    data = furnitureData;
    furnitureRenderer = GetComponent<Renderer>();

    // 치수 적용 (cm → m 변환)
    Vector3 size = data.GetSizeInMeters();
    transform.localScale = size;

    // 바닥에 붙이기: 피벗이 중앙이니까 높이의 절반만큼 올려야 바닥에 안착
    transform.position = new Vector3(
        transform.position.x,
        size.y / 2f,
        transform.position.z
    );

    // 색상 적용
    if (furnitureRenderer != null)
    {
        furnitureRenderer.material.color = data.color;
        originalColor = data.color;
    }

    gameObject.name = $"Furniture_{data.furnitureName}";
    gameObject.tag = "Furniture";
}
Unity에서 Cube의 피벗이 중심에 있기 때문에, Y 위치를 높이/2만큼 올려야 바닥에 딱 붙는다. 안 그러면 가구 절반이 바닥 아래로 빠진다.

인터랙션 구현

FurnitureInteraction이 모든 인터랙션을 총괄한다. Update에서 매 프레임 입력을 체크.

선택 — Raycast로 가구 클릭 감지

좌클릭하면 카메라에서 마우스 위치로 Ray를 쏜다. 맞은 오브젝트에 FurnitureObject 컴포넌트가 있으면 선택, 없으면 선택 해제.

private void HandleSelection()
{
    if (!Input.GetMouseButtonDown(0)) return;

    Ray ray = mainCamera.ScreenPointToRay(Input.mousePosition);
    RaycastHit hit;

    if (Physics.Raycast(ray, out hit, 100f))
    {
        FurnitureObject clicked = hit.collider.GetComponent<FurnitureObject>();

        if (clicked != null)
        {
            if (selectedFurniture != null && selectedFurniture != clicked)
                selectedFurniture.Deselect();

            selectedFurniture = clicked;
            selectedFurniture.Select();
            isDragging = true;
        }
        else
        {
            DeselectCurrent();
        }
    }
}

드래그 이동 — 바닥에 Raycast

선택된 상태에서 마우스를 움직이면, 바닥(Floor)에 Ray를 쏴서 맞은 지점으로 가구를 옮긴다.

private void HandleDrag()
{
    if (selectedFurniture == null || !isDragging) return;

    if (Input.GetMouseButtonUp(0))
    {
        isDragging = false;
        return;
    }

    Ray ray = mainCamera.ScreenPointToRay(Input.mousePosition);
    RaycastHit hit;

    if (Physics.Raycast(ray, out hit, 100f, floorLayer))
    {
        selectedFurniture.MoveTo(hit.point);
    }
}
floorLayer를 지정해서 바닥에만 Raycast가 맞도록 했다. 안 그러면 다른 가구 위에 Ray가 맞아서 가구가 공중으로 뜨는 문제가 생긴다.

회전 / 삭제

R키로 90도 회전, Delete/Backspace로 삭제. 간단하다.

// R키 → 90도 회전
if (Input.GetKeyDown(KeyCode.R))
    selectedFurniture.Rotate90();

// Delete → 삭제
if (Input.GetKeyDown(KeyCode.Delete))
{
    Destroy(selectedFurniture.gameObject);
    selectedFurniture = null;
}

실행 결과

테스트로 책상(120×60×75cm, 갈색)과 침대(200×150×40cm, 연보라)를 생성해서 확인했다.

가구 클릭 — 선택되면 색이 밝아진다
드래그로 이동 — 바닥 위에서 자유롭게 움직인다
R키로 90도 회전

삽질 기록

Update()에 핸들러 호출을 안 넣음

메서드는 다 만들어놨는데 Update() 안에서 호출하는 걸 깜빡했다. Play 했는데 아무 반응이 없어서 한참 헤맸다.

교훈: 메서드를 만들었으면 어딘가에서 불러야 한다. 당연한 건데 놓치기 쉽다.

카메라 드래그와 가구 드래그 충돌

3D 뷰에서 좌클릭 드래그가 카메라 회전이기도 하고, 가구 이동이기도 해서 동시에 반응하는 문제가 있다. 지금은 탑뷰에서 가구를 배치하고, 3D 뷰에서는 확인만 하는 식으로 쓰고 있다. 나중에 입력 분리를 제대로 해야 할 것 같다.


정리

기능핵심 구현
가구 데이터FurnitureData — cm 치수 + GetSizeInMeters()로 m 변환
가구 생성CreatePrimitive(Cube) + FurnitureObject 컴포넌트 부착
선택Physics.RaycastGetComponent<FurnitureObject>
드래그 이동바닥 레이어에 Raycast → MoveTo(hit.point)
회전Rotate90() — Y축 90도
삭제Destroy(gameObject)

다음 편에서는 벽 밖으로 못 나가게 하는 충돌 감지와, 가구끼리 겹쳤을 때 경고를 표시하는 기능을 추가할 예정이다.