
이 글은 애플 파운데이션 프로그램 멘토와 러너들에게 프로그램 수료 후 만든 저의 첫 앱 프로젝트를 소개하기 위해 정리한 짧은 노트 모음입니다. 이 과정에서 아카데미에서 CBL로 다진 기본기가 어느 때보다 단단히 받쳐 주었습니다. 특히 막혔을 때 다음 개발 마일스톤까지 앞으로 나아가기 위해 exploratory cycle을 반복한것이 도움이 많이 되었습니다. 현재 1차 프로토타입이 완성되었습니다.

이 앱은 iOS SpriteKit으로 개발한 한국어 테일러샵 모바일 게임입니다. 전반적인 분위기는 옛날 게임 느낌의 동화책 스타일입니다. 영어 제목 The Purrfect Stitch는 "완벽한"이라는 뜻의 Perfect에 고양이의 그르렁 소리 purr를 겹친 말장난입니다. Stitch(바느질 한 땀)와 더해 "완벽한 한 땀"이라는 의미가 되고, 주인공이 고양이 재봉사라서 붙인 이름입니다.

이 모든 것이 코드가 되기 전에, 이 게임은 Freeform을 이용해서 손으로 그린 와이어프레임이었습니다. 저의 첫 클라이언트는 초등학교 3학년인 둘째 딸이었고, 위 디자인을 직접 건네받았습니다. 옷감상점, 가구상점(가격: 5원), 공방, 아이템 개수까지 적힌 옷장, 그리고 지붕 위 고양이 한 마리(협상 불가) 등 사양이 꽤 구체적이었습니다. 기획서에는 무한한 아이디어와, 그것을 표현할 제한된 비개발자의 어휘, 그리고 24시간 지원 요청이 함께 딸려 왔습니다. 아래 내용의 대부분은 그 기획서에 맞춰 앱을 구현해 보려는 저의 시도입니다.
이 프로젝트는 Claude와 협업하여 개발했으며, 역할이 비교적 명확하게 나뉘었습니다.
제가 담당한 일: 게임 디자인과 POV 구조 결정, 스프라이트 아트를 ChatGPT로 생성하고 사운드 에셋과 함께 Xcode에 임포트, 디자인 방향 전환 판단, 리팩터링 방향 결정, 실기기 테스트, 메모리·컨텍스트·토큰 사용량 관리, 그리고 git 커밋과 GitHub 백업(Cowork 샌드박스에는 깃 자격증명이 없어 푸시는 제 터미널에서 수행).
Claude가 담당한 일: 코드 작성, 씬 리팩터링, 레이아웃 수치 계산, 버그 원인 추적, 그리고 매 세션 종료 시 다음 세션을 위한 핸드오프 노트 작성.
다만 프롬프트만으로 지시하기 어려운 미세한 변경은 제가 IDE에서 직접 손으로 수정하기도 했습니다.
저는 Claude를 두 가지 형태로 함께 썼습니다. 하나는 Cowork(에이전틱 데스크톱 앱)이고, 다른 하나는 터미널 기반의 Claude Code입니다. 처음에는 이 둘을 언제 어떻게 나눠 써야 할지 — 특정 작업을 위해 Claude Code나 서브 에이전트로 전환할 시점은 언제이고, 권한은 어디까지 위임해도 되는지 — 에 대한 감이 없었습니다. 그래서 Claude Code 101 과정을 수강하고, Cowork에서 이 프로젝트를 연습 문제 풀듯 진행하며 그 감각과 자신감을 키웠습니다.
한 가지 인상 깊었던 점은, AI와의 이른바 '바이브 코딩'이 이전에 우아한테크코스 프리코스에서 객체지향 프로그래밍을 배우던 경험과 사뭇 달랐다는 것입니다. 프리코스에서는 작은 요구사항 하나하나를 직접 손으로 구현하며 단일 책임·캡슐화 같은 원칙을 몸에 새기는, 원칙에서 코드로 내려가는 방식이었습니다. 반면 이번에 제가 한 AI와의 작업은 먼저 동작하는 결과를 빠르게 세워 두고 거기서부터 구조를 다듬어 가는, 코드에서 원칙으로 거슬러 올라가는 정반대의 리듬이었습니다. 속도와 탐색에는 후자가 강했지만, "왜 이렇게 짜야 하는가"를 스스로 붙들고 있지 않으면 구조가 쉽게 흐트러진다는 점에서, 프리코스에서 다진 기본기가 오히려 더 중요해졌다는 것이 흥미로웠습니다.
세션이 끊기더라도 맥락이 사라지지 않도록 하는 것이 핵심이었습니다.
레포 루트의 CLAUDE.md는 일종의 살아 있는 아키텍처 문서입니다. 빌드 방법, 씬 흐름도, HUD 레이아웃 규약(작업실은 재봉사 요소가 좌상단, 손님 요소가 우상단), 스프라이트 투명 패딩 보정 규칙, zPosition 컨벤션, 모델 레이어 표까지 포함하고 있어, Claude가 새 세션에서 이 파일만 읽으면 코드베이스의 상황을 즉시 파악합니다.
여기에 매 세션 종료 시 날짜를 bash date로 기록한 핸드오프 문서(로컬프로젝트명_handoff.md)를 더해, 무엇이 완료되었고 무엇이 미커밋 상태이며 다음에 무엇을 해야 하는지를 전달합니다.
SKILL.md는 문서·이미지 생성과 같은 반복 작업의 출력 형식을 표준화하는 용도로 활용했습니다.
결국 이 프로젝트에서 배운 것은 코드 못지않게 "AI가 다음에도 매끄럽게 작업을 이어받을 수 있도록 문서를 설계하는 방법"이었습니다.
SpriteKit은 이번 프로젝트에서 처음 사용해 보았습니다. 첫 프레임워크를 AI와 함께 익히는 경험을 통해 많은 것을 배웠습니다. 그 흔적 중 하나가 1차 완성된 현 프로토타입 단계에서 의도적으로 남겨 둔 씬 사이 불일치입니다. FrontShopScene은 GameScene.sks(비주얼 에디터)로 노드 레이아웃을 구성하고, BackRoomScene은 전부 코드로 빌드합니다. 두 방식이 한 코드베이스에 공존하는 셈입니다. 완전 코드 기반으로 통일하는 안을 옷장 및 영속성 작업을 마친 직후에 검토했으나, 해당 작업이 아직 안정되기 전이었기에 회귀 위험이 일관성에서 얻는 이득보다 크다고 판단하여 구조적으로 손을 보는 것은 미루었습니다. 첫 프레임워크에서 가장 크게 배운 점은 문법이 아니라, 지금 리팩터링하지 않는 것이 옳은 시점을 판단하는 일이었습니다.
게임은 다섯 단계를 거쳐 마일스톤을 찍으며 성장했습니다.
Phase 1(Hi-Fi 프로토타입) — 핑크 드레스 한 벌짜리 고정 흐름이었습니다. 원단과 단추가 하드코딩되어 있었으며, 죽은 코드와 디버그 오버레이를 정리했습니다.
Phase 2(게임 루프 확장) — NPC 가게 주인이 옷 종류 → 원단 색 → 보증금의 세 단계 주문을 안내하고,Order가 세 값을 모두 실어 작업실로 전달합니다. 초안에 있던 2c 단추 선택은 플레이어의 주도성이 약하여 취소했습니다.
Phase 3(스테이션 미니게임) — 원단장·재봉틀·단추·마네킹 보스로 구성된 네 개의 마리오풍 플랫포머입니다. 스테이션 1–3은 MinigameNode + MinigameConfig로, 보스는 별도의 BossMinigameNode로 구현했습니다.
Phase 4(사운드 패스) — 28개의 SFX 중 23개를 연결했습니다. SKAction 방식의 오디오 누수 문제를 AVAudioPlayer 풀과 stop() API로 교체했으며, v1 출시 게이트에 해당합니다.
Phase 5(유물 퀘스트) — 던전 루프 위에 얹은 메타 퀘스트입니다. 유물 4개 수집, 네 개의 내러티브 씬(TailorChoiceScene, AuroraChamberScene, PrincessAnaScene, DaphneBecomesTailorScene), 스토리북, 그리고 공유 컴포넌트 NarrativeHUD를 포함합니다.
게임은 하나의 가게를 세 개의 공간으로 나누어 순환합니다. 가게 앞(손님 시점)에서 옷을 주문받고 보증금을 받은 뒤, 작업실(재봉사 시점)에서 옷을 제작하고, 다시 가게 앞(손님 시점)으로 완성된 옷을 받는 돌아오는 순환 구조입니다. 시작점은 GameViewController이며, 여기서 손님이 선택되어 있는지(Store.loadSelectedCustomer())를 확인하여 첫 실행이면 설정 씬의 손님 선택 화면으로, 그렇지 않으면 곧장 FrontShopScene으로 이동시킵니다. 가게 앞에서 톱니바퀴·지갑·옷장 아이콘은 각각 설정·수수께끼·드레스룸 씬으로 크로스페이드 후 복귀하며, 보증금을 지불하면 페이드 전환으로 BackRoomScene(작업실)으로 넘어갑니다.
별도의 코디네이터나 전역 상태 매니저를 두지 않았습니다. 씬을 띄우기 직전에 목적지 씬의 프로퍼티를 직접 설정하는 가장 단순한 방식을 택했습니다. 예를 들어 작업실로 이동할 때는 order 프로퍼티에 주문 정보를 담아 전달하고, 완성 후 가게로 돌아올 때는 shouldShowFinishedGarment = true를 설정합니다. 사이드 씬들(드레스룸·수수께끼·설정)은 복귀 시 suppressEntryBell = true를 설정하여, 실제 입장(앱 실행, 작업 완료)에서만 가게 종소리가 울리고 단순 복귀 시에는 울리지 않도록 했습니다. 통화(냥·마력)와 같은 영속 상태는 프로퍼티로 전달하지 않고 Wallet.shared / Magic.shared 싱글톤에서 어느 씬이든 직접 읽습니다. 프로젝트 규모에 맞는 가장 가벼운 통신을 택한 결정이었습니다.
데이터와 씬을 분리하기 위해 Model/ 폴더를 별도로 두었으며, 역할에 따라 표현 방식을 구분했습니다. 순수 값 타입은 주문 데이터를 담당합니다. Order 구조체와 ClothingType·FabricColor 열거형은 Codable·CaseIterable이며, 한국어 raw 값을 기존 문자열 모델과 바이트 단위로 동일하게 유지하여 저장 포맷 호환성을 보존했습니다. 싱글톤은 씬을 가로지르는 상태를 담당합니다. Wallet(손님 냥), Magic(재봉사 마력), SoundManager, ProfileManager는 어느 씬에서든 .shared로 직접 읽습니다. 통화를 둘로 분리한 점이 모델 설계의 핵심이었는데, Wallet은 보증금으로 감소하고 환불로 증가하는 순환 경제인 반면, Magic은 던전 보상으로 단조 증가만 하는 확장 경제로 그 경제적 성격이 서로 다릅니다. 영속화는 전부 평면적인 UserDefaults 키로 처리했습니다.
가게 주인은 손님에게 초등학생 레벨의 수학문제와 상식 수수께끼(퀴즈)를 냅니다. 맞히면 보상으로 냥을 받습니다. Riddle 구조체는 질문, 4지선다 보기, 정답, 그리고 보상(기본 15냥)을 담습니다. 핵심은 데이터를 불러오는 방식에 있습니다. RiddleBank.load()는 먼저 Documents/riddles.json을 찾아보고, 없으면 하드코딩된 기본 문제 15개로 폴백합니다.
지금은 임시 하드코딩되어 있지만, 로더가 처음부터 부모가 직접 편집할 수 있는 JSON 파일을 우선하도록 설계해 두었습니다. 향후 앱을 다시 빌드하지 않고도 아이의 연령이나 학습 주제에 맞춰 문제를 바꿔 끼울 수 있는 문이 열려 있습니다. 첫 클라이언트가 초등학생이었던 만큼, 확장 가능성을 확보해 두었습니다.
씬은 고유한 상태 머신을 가집니다.
| 씬 | 상태 enum | 진행 흐름 |
|---|---|---|
FrontShopScene | FrontShopState | greeting → choosingClothing → choosingFabricColor → reviewingOrder → awaitingPayment → sendingOrder |
BackRoomScene (작업실) | BackRoomState | waitingForCabinetTap → … → completed (선형 진행) |
가장 만족스러운 부분은 입력 게이팅입니다. 이전에는 if currentState == .x 가드가 곳곳에 흩어져 있었으나, 이제는 FrontShopState.accepts(_:) 내부의 default 없는 단일 switch self로 통합했습니다.
후반부 내러티브 씬이 세 개로 늘어나면서, 대사 UI를 매번 새로 구성하는 대신 NarrativeHUD라는 SKNode 서브클래스 하나로 통합했습니다. 흉상 초상화(좌·우·중앙상단 슬롯), 반투명 대사 패널, 깜빡이는 ▶ 진행 표시, 그리고 패널을 그대로 변형하여 보여주는 선택지 패널까지 한 컴포넌트에 담았습니다. 호스트 씬은 configure(speakers:...)로 캐릭터를 등록하고 revealSpeaker / show(speaker:text:) / showChoices 같은 공개 API만 호출합니다. 터치 처리는 씬이 담당하고 HUD는 표시에만 집중하도록 책임을 분리했습니다. revealedNames 가드를 두어, 씬 시작 시 등록만 해 둔 뒤늦게 등장하는 캐릭터(요정 대모)가 의도치 않게 화면에 나타나는 버그도 방지했습니다.



몇 달 전만 해도 게임 한 편을 끝까지 만들어 본 적이 없었습니다. 처음 써보는 프레임워크를 AI와 함께 익히고, 가장 까다로운 클라이언트(초등학교 3학년)의 밤마다 바뀌는 요구사항을 쳐내며, 결국 손에 잡히는 앱 프로토타입을 완성했습니다. 완벽하진 않지만'Purrfect'하게 플레이할 수 있는 게임을 만들 수 있도록 시작을 만들어 주신 멘토와 러너들에게 감사드립니다.