스토어에서 어떻게 검색하면 좋을까?

이 질문을 보낼 때 「호구마트」의 화면은 흰 사각형 열두 줄이었다. 줄마다 재고 숫자, 가격 슬라이더, 레벨업 버튼. 게임이라기보다 엑셀 시트였다. 로드맵의 다음 단계 이름은 「아트 & UX」였고, 기획할 때 적어 둔 줄은 "에셋스토어 판타지 상점 팩 구매 → 색보정으로 톤 통일 (직접 그리지 말 것)"이었다. 그림은 사면 된다. 무엇을 살지는 물어보면 된다. 그 정도로 가볍게 생각했다.

장바구니 현황

Claude Code 는 검색어 몇 줄과 함께 고르는 기준부터 줬다. 걷기·대기 애니메이션이 있는가. 같은 작가의 시리즈인가. 픽셀 밀도가 다른 그림끼리는 색보정으로도 못 살린다고 했다. 진열대·카운터 같은 오브젝트가 들어 있는가. 기획 문서의 톤, 따뜻한 목재 상점과 차가운 청록 던전에 가까운가.

그다음부터는 쇼핑이었다. 마음에 드는 페이지 링크를 붙여 넣으면 판정이 돌아왔다. 리뷰 없는 캐릭터 팩은 보류, 음식 아이콘 팩은 필요한 다섯 계열(포션·무기·방어구·재료·장신구)이 아니라 패스, 벡터풍 UI 팩은 픽셀 방향과 부딪혀 제외. 판정 끝에는 매번 "장바구니 현황"이 붙었다. 필요한 그림을 손님 6종, 진열대 12개, 아이콘 40개, UI·말풍선으로 나누고 칸마다 채워졌는지 표시한 표였다. 칸이 하나씩 채워지는 게 묘하게 즐거웠다.

Tiny Swords 영웅은 이게 다인 것 같은데, 다른 제일 필요한 에셋 추천 부탁할게

이 질문에 나온 답이 Kenmi 의 Cute Fantasy 시리즈였다. 추천 이유는 "전부 한 작가, 같은 16px 밀도, 같은 팔레트라 색보정 통일 작업이 거의 불필요". 작가 페이지를 보내자 본팩·Characters·Dungeon 세 개짜리 구매안이 왔고, 결제하는 김에 후보에 있던 5600+ 아이콘 팩도 담았다. 그다음 내가 보낸 말은 "구매 완료 했어", 그리고 "임포트 했어"였다.

돈을 주고 산 것은 Kenmi 3종과 5600+ Ultimate Pixel Art Fantasy RPG Icon Pack 이다. 여기에 앞서 받아 둔 Pixel HUD UI FreeKit 과 Speech Bubble, 무료로 받아 예비로 넣어 둔 Tiny Swords 까지 임포트하니 PNG 가 6,303장이었다.

6,303장을 커밋하는 법

이미지 6천 장을 그냥 커밋하면 저장소가 망가진다. PNG 는 이미 압축된 바이너리라 Git 의 델타 압축이 듣지 않고, 한 번 들어가면 지워도 히스토리에 남는다. Claude Code 는 에셋을 넣는 커밋에서 .gitattributes 부터 고쳤다.

# Unity YAML 은 텍스트로 (스마트 머지 대비)
*.meta text eol=lf
*.unity text eol=lf
*.prefab text eol=lf

# 바이너리 — Git LFS
*.png filter=lfs diff=lfs merge=lfs -text
*.wav filter=lfs diff=lfs merge=lfs -text
*.ttf filter=lfs diff=lfs merge=lfs -text

이미지·사운드·폰트는 LFS 로 보내고, 히스토리에는 버전·해시·크기 세 줄짜리 포인터만 남긴다. 반대로 .unity·.prefab·.meta 는 일부러 텍스트로 남기고 줄바꿈만 고정했다. 곧 씬을 코드로 찍어 낼 텐데, 다시 찍었을 때 무엇이 바뀌었는지 diff 로 봐야 하기 때문이다. 이미지마다 .meta 가 따라붙어 그 커밋 하나에서 바뀐 파일이 20,158개였다. 지금 재 보면 Git 팩은 약 7.7MB, LFS 객체는 약 27MB다. 안 쓴 그림이 히스토리를 무겁게 만들지는 않았다는 것, 이건 나중에 위안이 됐다.

메뉴 한 번에 상점 하나

기획 문서의 화면 구성은 "메인 뷰는 항상 상점, 조작은 하단 시트"였다. 흰 사각형은 그걸 미뤄 둔 임시 화면이었다.

Claude Code 는 상점을 손으로 배치하지 않았다. Hogumart/Generate Shop Scene 이라는 에디터 메뉴를 만들고, 누르면 ShopSceneBuilder 가 Kenmi 스프라이트로 바닥·벽·진열대·카운터·던전 아치를 조립해 Shop.unity 를 새로 저장하게 했다. 세로 12 x 16타일에 진열대 12칸은 3열 x 4행으로 x = 2.5 + col * 3.5, y = Height - 3.5 - row * 3.0 에 깔린다. 배치가 어색하면 숫자를 고치고 메뉴를 다시 누르면 됐다. "씬을 손으로 안 고치고 코드로 재생성하는 구조라 반복이 싸다"는 게 AI 의 설명이었다.

그리고 처음 그 씬을 띄웠을 때. 흰 사각형이 있던 자리에 나무 바닥이 깔리고, 벽이 서고, 진열대가 줄을 맞췄다. 숫자를 만지던 게임이 갑자기 가게가 됐다. 이 프로젝트에서 가장 들떴던 순간을 하나 고르라면 이 장면이다.

들뜬 눈에는 안 보이는 것도 있었다. 진열대로 쓸 가구는 Indoor_Decor 시트의 번호로 골랐는데, 처음 들어간 41~44번은 확대해 보니 의자와 화분이었다. 한참 뒤 다른 문제를 고치던 AI 가 시트를 다시 들여다보고 알아챘고, 32번 책장과 27번 캐비닛으로 바꿨다. 나는 의자 진열대를 보면서도 그냥 "상점 같다"고 생각했다.

손님도 들어왔다. Kenmi Characters 의 검사, 창병, 천사, 템플러, 고블린 도둑, 오크 일꾼이 배역을 받았고, CustomerWalker 가 입장 → 구경(대사) → 퇴장을 걷게 했다. 스폰 간격은 기준 5초를 시뮬레이션의 유입률로 나눠서, 평판이 떨어지면 손님이 실제로 뜸해진다. 방치형은 켜 두는 시간이 길어 퇴장한 손님을 Destroy 하는 대신 프리팹별 풀로 돌려보냈다.

private readonly Dictionary<GameObject, Stack<CustomerWalker>> _pool = new();

물건이 팔리면 진열대 위로 +N G 가 튀어 올랐다. 시뮬레이션은 손님을 한 명씩 판정하지 않으니 "하나 팔렸다"는 이벤트가 없다. 그래서 0.55초마다 누적 매출의 증가분을 재서, 재고가 남은 진열대 하나 위에 팝업을 띄운다. 팝업은 12개를 돌려 쓰고, 다 떠 있으면 그 샘플은 건너뛴다. 화면을 덮으면 오히려 타격감이 죽기 때문이다.

이 모든 걸 붙이는 동안 시뮬레이션 코드는 한 줄도 바뀌지 않았다. "손님은 순수 연출"이라는 원칙과 Simulation 레이어가 UnityEngine 을 모르게 한 규칙 덕분이다. 그 원칙은 동시에, 화면 속 손님이 게임에 아무 영향도 주지 않는다는 뜻이기도 했다.

튜토리얼 "진열대를 탭해 보세요" — 나무 바닥과 책장 진열대, 기사 손님, 진열대 위 "+4 G" 팝업과 하단 시트

5,600장에서 마흔 개

상품은 40티어다. 포션·무기·방어구·재료·장신구 다섯 카테고리를 티어 순서대로 돌아서 카테고리당 여덟 개씩. 여기에 아이콘이 필요했다. 문제는 아이콘 팩이 1.png 부터 5600.png 까지 번호만 붙은 평평한 폴더였다는 것이다. 이름으로는 무엇이 칼이고 무엇이 물약인지 알 수 없다.

AI 는 번호 구간별로 아이콘을 모아 몬타주 이미지를 만들어 훑었다. 그리고 "재료 → 음식/병 → 무기 → 방어구 → 투구/장신구 순"이라는 배열을 읽어 낸 뒤 포션과 장신구 구간을 다시 좁혔다. 그 결과가 ItemIconAssigner 의 표와 한 줄짜리 공식이다.

[ItemCategory.Material] = (800, 1600),
[ItemCategory.Potion]   = (2060, 2210),
[ItemCategory.Weapon]   = (2400, 3300),
[ItemCategory.Armor]    = (4000, 4700),
[ItemCategory.Trinket]  = (5300, 5600),

int target = range.from + (range.to - range.from) * index / System.Math.Max(1, total - 1);

구간을 여덟 조각으로 균등하게 나눈다. 포션이면 첫 번째가 2060, 두 번째가 2060 + 150 x 1 / 7 = 2081, 마지막이 2210이다. 이 팩은 같은 종류 안에서 뒤로 갈수록 화려해지는 순서라, 높은 티어가 구간의 뒤쪽을 받으면 "비싼 물건일수록 화려하다"가 저절로 맞는다. 그 번호에 파일이 없으면 앞뒤로 한 칸씩 넓혀 최대 40칸까지 찾고, 이미 아이콘이 있는 상품은 건드리지 않아 손으로 바꾼 아이콘이 재실행에 지워지지 않는다. 그런데 아이콘이 진열대 위에 점만 하게 떴다. 이 팩의 임포트 설정이 PPU 100이라 16px 그림이 월드에서 0.16유닛이었다. Claude Code 는 임포트 설정 대신 ShelfView 에서 스프라이트의 긴 변을 0.9유닛에 맞추는 스케일을 걸었다. 크기는 해결됐다. 필터는 그대로였다. 나중에 .meta 를 세어 보니 아이콘 5,617장 중 5,613장이 Bilinear 였다. 16px 픽셀아트를 크게 키우면서 보간을 켜 둔 셈이라 가장자리가 번진다. 임포트 규칙을 강제하는 AssetPostprocessor 는 없었고, 팩이 준 설정을 아무도 들여다보지 않았다. 그 사이 정리 문서에는 "Kenmi 단일 작가 시리즈라 색보정 없이 통일됨"이라고 적혔다. Kenmi 는 맞았다. 그 옆에 다른 작가의 아이콘이 다른 설정으로 앉아 있었을 뿐이다.

흰 사각형으로 돌아간 말풍선

움직이는 화면을 플레이해 보고 보낸 말은 이랬다.

움직이니 조금 나은 것 같아. 그런데 대사며 글씨가 잘 안 보이는 것 같아

손님 여섯 종에 열두 줄씩, 대사 72줄이 머리 위 말풍선으로 뜨는데 배경이 안 보였다. 내장 UISprite 가 런타임에 비는 환경이 있었다. 해결책은 Texture2D.whiteTexture 로 흰 스프라이트를 직접 만들어 배경에 까는 것이었다. 글자는 폰트 크기를 48에서 96으로 올리고 오브젝트를 줄여 선명하게 했고, 배경 크기는 글자 수로 추정하는 대신 렌더러의 실제 bounds 를 재서 맞췄다.

말풍선은 잘 보이게 됐다. 그리고 그 말풍선은 흰 사각형이었다. 받아 둔 Speech Bubble 팩은 그렇게 쓰일 기회를 잃었다. 흰 사각형을 걷어 내려고 시작한 일이 흰 사각형 말풍선으로 끝난 셈이다.

51장

이 글을 쓰면서 씬·프리팹·애니메이터·ScriptableObject 가 참조하는 파일을 GUID 로 전부 대조하고, 에디터 스크립트의 경로 문자열까지 찾았다.

에셋 구분 PNG 수 실제로 참조되는 PNG
Kenmi Cute Fantasy RPG 유료 196 4 (바닥, 벽, 가구 시트, 사장)
Kenmi Cute Fantasy Characters 유료 14 6 (손님 6종)
Kenmi Cute Fantasy Dungeon 유료 32 1 (던전 아치)
5600+ 아이콘 팩 유료 5,617 40
Tiny Swords 무료 414 0
Pixel HUD UI FreeKit 무료 11 0
Speech Bubble 무료 19 0

돈을 주고 산 5,859장 중 화면에 나온 건 51장, 받아 둔 것까지 합치면 6,303장 중 51장이다. "던전 앞 잡화점"의 던전은 32장짜리 팩에서 아치 하나로 남았다. HUD 는 끝까지 단색 패널이었고, 말풍선은 흰 스프라이트였고, 예비는 끝까지 예비였다.

AI 의 추천이 틀렸던 건 아니다. 기준은 꼼꼼했고, Kenmi 는 정말로 한 화면에서 어긋나지 않았다. 그 기준은 제작 적합성이었다. 밀도가 맞는가, 애니메이션이 있는가, 장바구니의 칸을 채우는가. 그런데 그 칸은 기획 문서의 화면 구성에서 나온 목록이었다. "플레이어가 무엇을 봐야 이 게임의 재미가 전달되나"에서 나온 목록이 아니었다. 그 질문은 AI 도 하지 않았고 나도 하지 않았다.

그래서 그다음에 일어난 일도 자연스러웠다. 상점이 생긴 화면을 보고 나는 "지금 상태로는 무슨 재미가 있는 건지 잘 모르겠어"라고 했고, 1편에 쓴 대로 그 말은 골드 팝업과 튜토리얼과 아이콘이 됐다. 그 튜토리얼의 네 번째 단계가 플레이어에게 하는 말은 이것이었다. "가격 슬라이더를 움직여 보세요. 올리면 개당 이익 ↑, 사는 손님 ↓ — 이게 이 게임의 전부입니다." 나무 바닥을 깔고 손님을 걷게 해도 플레이어가 하는 일은 흰 사각형 시절과 같았다. 상품에는 아이콘이 생겼지만 이름은 끝까지 "티어 8 상품"이었다.

에셋은 화면을 그럴듯하게 만들었다. 그 그럴듯함이 "거의 다 됐다"는 착각을 줬다. 들떠 있던 나는 곧바로 다음 질문을 보냈다. "웹에서도 플레이 가능하도록 할 수 있어?" 마지막 편은 그 질문에서 시작한다.