wan의 개발일기

RoomCraft #21. 저장 목록에 미리보기 넣기 - 레이아웃 비교의 첫걸음 본문

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

RoomCraft #21. 저장 목록에 미리보기 넣기 - 레이아웃 비교의 첫걸음

wan_ 2026. 7. 10. 10:45
소스코드 → github.com/Jywan/RoomCraft
플레이 → jywan.github.io/RoomCraft

들어가며

가구 프리셋을 만들면서 README를 다시 훑다가, "여러 레이아웃 비교"도 아직 없다는 걸 확인했었다. 구현 방식을 세 가지로 놓고 저울질했다 — 화면을 분할해서 두 방을 동시에 렌더링하는 방식(제일 그럴듯하지만 카메라 2대에 공간 분리까지 손볼 게 많음), 단축키로 저장된 레이아웃을 빠르게 넘겨보는 방식(간단하지만 그때그때 다시 불러와야 함), 그리고 저장 목록에 미리보기 썸네일을 넣는 방식. 구현 난이도 대비 효과가 제일 좋아 보이는 세 번째로 정했다.


저장할 때 스크린샷도 같이

저장 버튼을 누르면 이름 입력 팝업이 뜨고, 확인을 누르면 JSON으로 저장되는 게 기존 흐름이었다. 여기에 스크린샷 캡처를 끼워 넣었는데, 타이밍이 생각보다 까다로웠다.

확인 버튼을 누른 그 즉시 ScreenCapture.CaptureScreenshotAsTexture()를 부르면, 방금 SetActive(false)로 꺼버린 저장 팝업이 스크린샷에 그대로 찍힌다. Unity의 화면 캡처는 "마지막으로 완전히 렌더링된 프레임"을 가져오는데, 오브젝트를 비활성화한 시점과 그 결과가 실제로 화면에 그려지는 시점 사이에 프레임 경계가 하나 끼어있기 때문이다.

private void OnConfirmSave()
{
    // ... 이름/방 데이터 확인 ...

    savePanel.SetActive(false);   // 팝업부터 숨기고
    StartCoroutine(CaptureAndSave(projectName, roomData));
}

private IEnumerator CaptureAndSave(string projectName, RoomData roomData)
{
    yield return new WaitForEndOfFrame();   // 팝업이 사라진 프레임이 실제로 그려질 때까지 대기

    Texture2D screenshot = ScreenCapture.CaptureScreenshotAsTexture();
    byte[] thumbnailPng = screenshot.EncodeToPNG();
    Destroy(screenshot);

    projectManager.SaveProject(projectName, roomData, thumbnailPng);
}

WaitForEndOfFrame() 한 프레임만 기다렸다가 캡처하면, 팝업이 사라진 깨끗한 화면이 찍힌다.


썸네일 파일은 어떻게 관리할까

기존 저장 로직은 {프로젝트이름}.json 하나만 썼는데, 여기에 같은 이름의 .png를 나란히 저장하기로 했다. 파일명 규칙만 맞추면 JSON을 찾을 때 옆에 있는 PNG도 바로 유추할 수 있어서 별도 매핑 테이블이 필요 없다.

public void SaveProject(string projectName, RoomData roomData, byte[] thumbnailPng = null)
{
    // ... 기존 JSON 저장 로직 ...

    if (thumbnailPng != null)
    {
        string thumbPath = Path.Combine(saveDirectory, SanitizeFileName(projectName) + ".png");
        File.WriteAllBytes(thumbPath, thumbnailPng);
    }
}

public static Texture2D LoadThumbnail(string jsonFileName)
{
    string dir = Path.Combine(Application.persistentDataPath, "Projects");
    string baseName = Path.GetFileNameWithoutExtension(jsonFileName);
    string filePath = Path.Combine(dir, baseName + ".png");

    if (!File.Exists(filePath)) return null;

    byte[] data = File.ReadAllBytes(filePath);
    Texture2D tex = new Texture2D(2, 2);
    tex.LoadImage(data);
    return tex;
}

LoadThumbnail()을 static으로 만든 게 포인트다. 썸네일을 보여줘야 하는 곳은 StartScene의 불러오기 화면인데, 거기엔 ProjectManager 인스턴스가 없다 — ProjectManager는 EditorScene에만 있다. 이미 GetSavedProjectFiles()가 인스턴스 없이 static으로 동작하고 있어서, 같은 패턴을 그대로 따라갔다.


목록에 붙이기

불러오기 목록의 각 항목 프리팹(FileItemButton)에 Thumbnail이라는 이름의 Image 자식을 추가하고, 코드에서 이름으로 찾아서 채워 넣었다.

Image thumbnailImage = item.transform.Find("Thumbnail")?.GetComponent<Image>();
if (thumbnailImage != null)
{
    Texture2D thumb = ProjectManager.LoadThumbnail(fileName);
    if (thumb != null)
        thumbnailImage.sprite = Sprite.Create(thumb, new Rect(0, 0, thumb.width, thumb.height), new Vector2(0.5f, 0.5f));
}
불러오기 목록 — 가운데 항목만 실제 썸네일이 찍혀있다

스크린샷을 보면 항목 하나만 진짜 썸네일이 있고 나머지 둘은 흰 사각형이다. 이 기능을 붙이기 전에 이미 저장해뒀던 프로젝트들이라 대응하는 PNG가 없어서 그런 건데, 일부러 손대지 않았다 — 예전 저장 파일도 여전히 문제없이 불러와지는 게 중요하지, 오래된 파일에 썸네일이 없다고 에러를 낼 이유는 없다.


삽질 기록

또, Layout Group

항목 프리팹 높이를 썸네일이 들어갈 만큼 키웠더니(40 → 70) 목록이 지저분해졌다. 이번엔 원인이 두 개가 겹쳐 있었다.

첫째, Content 오브젝트에 Content Size Fitter가 없어서 부모(스크롤 영역)가 자식들의 늘어난 높이를 반영 못 하고 있었다. 이건 이 프로젝트에서 셀 수 없이 반복된 패턴이라 바로 눈에 들어왔다.

둘째는 처음 보는 조합이었다. Vertical Layout GroupControl Child SizeWidth가 꺼져있고, Child Force ExpandHeight가 켜져있었다. Control Child Size가 꺼진 축은 자식이 자기 프리팹 크기를 그대로 유지하고, Force Expand는 남는 공간을 자식들에게 나눠주는 것까지는 하는데 — 이 둘이 어긋난 축에 걸려있으니 가로는 리스트 폭에 안 맞고 세로는 스크롤 영역의 남는 공간만큼 제멋대로 늘어났다.

Control Child SizeChild Force Expand는 비슷해 보이지만 하는 일이 다르다. 전자는 "자식 크기를 내가 강제로 정한다", 후자는 "남는 공간이 있으면 나눠준다"에 가깝다. 리스트 형태로 고정 높이 항목을 나열하고 싶으면 Control Child Size Width만 켜고 Height 쪽은 Control/Force Expand 둘 다 꺼야 프리팹 높이가 그대로 유지된다.

정리

기능핵심 API / 구조
저장 시 스크린샷 캡처ScreenCapture.CaptureScreenshotAsTexture() + WaitForEndOfFrame()
썸네일 파일 관리JSON과 동일한 파일명의 .png, ProjectManager.LoadThumbnail()(static)
목록에 썸네일 표시FileItemButton 프리팹의 Thumbnail 자식 + Sprite.Create()
Layout Group 정리Content Size Fitter 추가, Control Child Size/Force Expand 축 정리
다음 단계WebGL 한글 입력 버그, Phase 6-2 모바일 배포(미정)