wan의 개발일기

RoomCraft #10. 씬 분리 — 시작 화면 만들기 본문

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

RoomCraft #10. 씬 분리 — 시작 화면 만들기

wan_ 2026. 7. 1. 22:37
소스코드 → github.com/Jywan/RoomCraft

들어가며

지난 편에서 저장/불러오기 UI를 팝업으로 만들려고 시도했었다. 근데 패널이 겹치고, Inspector 연결이 꼬이고, NullReferenceException이 터지고… 문제가 끝도 없이 나왔다.

결국 한 씬 안에 모든 걸 욱여넣는 건 무리라는 결론에 도달했다. 그래서 이번에는 씬을 아예 분리하기로 했다. 시작 화면에서 프로젝트를 선택하고, 에디터 씬으로 넘어가는 구조다.


왜 씬을 분리했나

모든 UI가 한 씬에 있으면 이런 문제들이 생긴다:

  • 패널 겹침/순서 문제 — Canvas 안에서 UI 요소끼리 가려지거나 클릭이 안 먹는 상황
  • NullReferenceException — Start() 시점에 아직 비활성화된 오브젝트를 참조하려다 터짐
  • 관리 복잡성 — 하나의 씬에 너무 많은 책임이 몰림

그래서 StartScene(시작 메뉴)과 EditorScene(가구 배치 에디터), 두 씬으로 나눴다. 역할이 명확해지니까 각 씬의 UI 로직이 단순해졌다.


SessionData — 씬 간 데이터 전달

씬이 분리되면 가장 먼저 부딪히는 문제가 “데이터를 어떻게 넘기지?”다. Unity에서 SceneManager.LoadScene()을 호출하면 이전 씬의 오브젝트가 전부 파괴되니까, MonoBehaviour에 데이터를 담아두는 건 안 된다.

해결법은 간단하다. static 클래스를 하나 만들어서 StartScene에서 설정한 정보를 EditorScene에서 읽어가게 했다.

namespace RoomCraft.Data
{
    /// <summary>
    /// 씬간 데이터 전달용 static 클래스
    /// StartScene에서 설정 -> EditorScene에서 읽어감
    /// </summary>
    public static class SessionData
    {
        public enum Mode { NewProject, LoadProject }

        public static Mode CurrentMode { get; set; }

        // 새 프로젝트일 때 사용
        public static string RoomName { get; set; }
        public static float RoomWidth { get; set; }
        public static float RoomDepth { get; set; }
        public static float RoomHeight { get; set; }

        // 불러오기일 때 사용
        public static string LoadFileName { get; set; }

        public static void SetNewProject(string name, float width, float depth, float height)
        {
            CurrentMode = Mode.NewProject;
            RoomName = name;
            RoomWidth = width;
            RoomDepth = depth;
            RoomHeight = height;
            LoadFileName = null;
        }

        public static void SetLoadProject(string fileName)
        {
            CurrentMode = Mode.LoadProject;
            LoadFileName = fileName;
            RoomName = null;
        }
    }
}

핵심은 SetNewProject()SetLoadProject() 두 메서드다. StartScene에서 이 중 하나를 호출하고, EditorScene에서 CurrentMode를 확인해서 분기하면 된다.


StartSceneUI — 시작 화면

StartScene은 단순한 구조다. 타이틀이 있고, 아래에 버튼 두 개 — “새 프로젝트”와 “불러오기”.

StartScene 시작 화면
StartScene — 새 프로젝트 버튼과 불러오기 버튼
새 프로젝트 패널
새 프로젝트 클릭 시 — 방 이름, 가로, 세로, 높이 입력 패널

EditorSceneLoader — 에디터 진입

EditorScene에 진입하면 EditorSceneLoaderSessionData를 읽어서 분기한다.

SessionData.CurrentMode가 NewProject면 방을 새로 생성하고, LoadProject면 저장된 프로젝트를 복원한다.
private void HandleNewProject()
{
    RoomData roomData = new RoomData(
        SessionData.RoomWidth,
        SessionData.RoomDepth,
        SessionData.RoomHeight,
        SessionData.RoomName);

    roomBuilder.BuildRoom(roomData);

    CameraController cam = FindAnyObjectByType<CameraController>();
    if (cam != null)
    {
        cam.SetTargetPoint(Vector3.zero);
        cam.SwitchMode(CameraMode.TopView);
    }
}

StartScene에서 입력한 방 크기로 RoomData를 만들고, BuildRoom()으로 방을 생성한다. 그 다음 카메라를 탑뷰로 전환해서 바로 배치 화면을 보여준다.

EditorScene 전환 후
EditorScene 전환 후 — 입력한 크기대로 방이 생성된 탑뷰

삽질 기록

SavePanel이 시작부터 열려있다

Start()에서 패널을 SetActive(false)로 꺼야 하는데, 그 전에 NullReferenceException이 터져서 패널이 안 꺼지는 문제가 있었다. 원인은 Inspector에서 필드 연결을 빠뜨린 것. SerializeField로 선언한 변수에 오브젝트를 드래그해서 연결하지 않으면, Start() 첫 줄에서 바로 터진다.

해결: Inspector에서 필드 연결 확인. 빈 칸 없이 전부 채워야 한다.

버튼 클릭이 안 된다

Start()가 비어있거나, 메서드 이름에 오타가 있어서(OpenNNewProjectPanel처럼 N이 두 번) 리스너가 등록되지 않았던 문제.

해결: 오타 수정하고 Start() 내용을 제대로 작성.

방이 400m로 만들어진다

InputField에 Placeholder를 “400”으로 해놓고, 단위를 cm로 생각했는데 RoomData는 m 단위였다. 그러니까 가로 400m짜리 방이 만들어진 거다.

해결: Placeholder를 m 단위로 바꾸고(4, 3, 2.5), 기본값도 4f, 3f, 2.5f로 설정.

정리

기능핵심 구현
씬 분리StartScene(index 0) + EditorScene(index 1), SceneManager.LoadScene()
데이터 전달SessionData static 클래스, SetNewProject() / SetLoadProject()
시작 화면StartSceneUI — 새 프로젝트 패널 + 불러오기 패널
에디터 진입EditorSceneLoader.HandleNewProject()RoomBuilder.BuildRoom()
카메라 초기화CameraController.SetTargetPoint() + SwitchMode(TopView)

다음 편에서는 EditorScene에 저장 기능을 붙이고, 불러오기 흐름을 완성해보려고 한다.