HongSJ — 카드 레지스트리 도입 스냅샷
1. 주요 변경사항
CardData에cardId:int필드 추가 — 네트워크 전송 시 카드 종류를 정수로 식별하기 위한 기반 마련.- 30장의 CardData 에셋을
ScriptableObjects/Cards/에서Resources/Cards/로 이전하고 각 에셋에cardId: 1~30을 수동 부여. CardRegistry정적 클래스 신설 —Resources.LoadAll<CardData>("Cards")로 로드 후Dictionary<int, CardData>로 O(1) 조회 제공, 중복 cardId 경고 포함.CardDataGenerator에디터 메뉴에Assign Card IDs추가, 생성 경로를Assets/Resources/Cards로 변경.DraftTestHUD가Resources.LoadAll직접 호출 대신CardRegistry.Initialize()+CardRegistry.All로 교체.CLAUDE.md의 폴더 구조 / CardData 스키마 설명을 새 구조에 맞게 업데이트 (cardId,effectDescription추가).
2. 코드 품질 리뷰
이번 커밋의 코어는 “네트워크로 카드를 주고받을 때 ScriptableObject 참조를 직렬화할 수 없으니, 정수 ID 로 대체한다” 는 표준적인 패턴이다. 방향은 정확하다. CardRegistry 를 별도 정적 클래스로 분리해 DraftTestHUD 가 Resources 호출을 직접 들고 있던 관심사를 걷어낸 것도 단일 책임 측면에서 옳은 수술이다. 중복 cardId 에 대한 LogWarning, 미초기화 상태에서의 LogError 도 최소한의 방어선으로 들어가 있어 기본기는 지켜졌다.
다만 설계 세부에서 조용히 쌓이는 부채가 있다. 첫째, CardRegistry 가 public static 이다. 이 프로젝트가 Mirror 기반 멀티플레이로 간다는 점을 감안하면, 서버/클라이언트 양쪽에서 동일한 싱글톤을 건드리게 되고 테스트 격리도 어렵다. 현 규모에선 문제가 없지만, 이후 “서버는 풀셋, 클라이언트는 언락셋만” 같은 요구가 생기면 정적 상태가 걸림돌이 된다. 둘째, Get(int) 가 실패 시 null 을 조용히 반환한다 — 호출측이 null 체크를 누락하면 Draft/Battle 로직에서 NullReferenceException 이 런타임에만 드러나는 형태로 새어나간다. TryGet 패턴이나 “미등록 ID 는 예외” 중 하나로 계약을 분명히 하는 편이 낫다. 셋째, DraftTestHUD.Start() 의 !CardRegistry.All.GetEnumerator().MoveNext() 는 IEnumerator 를 소비만 하고 Dispose 하지 않는 어색한 패턴이다. CardRegistry.All.Any() 혹은 Count 프로퍼티를 노출하는 편이 의도가 분명하다.
카드 ID 할당 전략도 약하다. AssignCardIds 는 매 실행 시 id = 1 부터 다시 부여한다 — 이미 ID 가 박힌 상태에서 카드 하나를 추가·삭제하면 기존 ID 가 전부 밀린다. 네트워크 프로토콜·저장 데이터·데크 목록이 cardId 에 의존하는 순간 이건 호환성을 깨는 함정이 된다. “없는 것만 채우기” 혹은 “최대값+1 부터 부여” 로 멱등성을 확보해야 한다. 현재 ID 는 알파벳 정렬에 우연히 묶인 숫자일 뿐, 설계적 의미(코스트/희귀도/세트별 블록)는 없다. 숫자 블록을 1000번대=코스트1, 2000번대=특수 처럼 의미 단위로 나눌지 아니면 완전 무의미한 해시로 갈지, 지금 결정하고 문서에 박아두어라. 나중에 바꾸면 세이브·리플레이 호환성이 깨진다.
Unity 관용 관점에선 크게 거슬리는 건 없다. Update 남발, GetComponent 루프, GameObject.Find 같은 적신호는 이번 범위에 없다. Resources 폴더 사용은 Addressables 대비 규모가 커질 때 메모리/릴리스 타이밍을 잃기 쉬운 선택이지만, 카드 30~40장 규모에선 충분히 합리적 선택이니 당분간 유지해도 된다.
3. 진행도 평가
첫 리포트다. 현재 repo 상태는 “카드 드래프트 골격 + 네트워크 전송을 위한 ID 배관 마련” 단계로 보인다. 카드 30장의 수치·효과 설명이 에셋에 박혀 있고 DraftSession 이 존재한다는 점에서 드래프트 루프 프로토타입은 돌아가는 것으로 추정되지만, 실제 게임 루프(구역 배치, 턴, 공개, 점수 판정) 나 네트워크 동기화 코드는 이번 diff 에선 보이지 않는다. Mirror 기반 멀티플레이 카드게임의 완성까지 갈 길은 확실히 멀다.
커밋 메시지(“카드 ID 시스템 추가 및 에셋 폴더 구조 개선”)는 변경을 정확히 요약하고 있고, 이번 커밋은 분명한 목적(네트워크 직렬화 가능화) 을 가진 단일 주제 커밋이라 구조적으론 깔끔하다. 다만 리포트 기간 내 커밋이 1개뿐이라 속도 자체는 빠르다고 보기 어렵다. 남은 기간 대비 스코프(드래프트 + 전투 + 효과 시스템 + 네트워킹) 를 보면 효과 시스템(effectType/effectVal) 을 단순 enum+int 에서 벗어나지 않는 선까지만 구현하고 스코프를 좁히는 판단이 필요할 수 있다.
4. 다음 권장사항
- CardRegistry 초기화 단일화: 현재
DraftTestHUD.Start에서Initialize()를 호출한다. 게임 시작 지점(Bootstrap/NetworkManager) 에서 한 번만 초기화하도록 옮겨라. 씬마다 재초기화되면 런타임에 Resources 재로드 비용이 중복 발생한다. - AssignCardIds 멱등화: 기존 cardId 가 0(미할당) 인 에셋에만 다음 번호를 부여하도록 변경. 카드 추가/삭제 시 호환성 폭탄을 지금 막아둬야 한다. 함께 “cardId 중복/0 검사” validator 메뉴를 추가하면 에셋 리뷰 시 즉시 발견 가능하다.
- CardRegistry.Get 계약 명시:
TryGet(int, out CardData)를 기본 API 로 두고,Get은 “반드시 존재한다” 는 전제에서 실패 시 예외를 던지는 쪽으로 분리. 네트워크로 받은 ID 가 레지스트리에 없을 때 조용히 null 이 되면 디버깅이 매우 어렵다. - 문서 공백 해소:
docs/design/docs/technical/docs/spec/세 디렉토리 초안을 이번 주 안에 만들어라. 특히 기술문서에 “Mirror 권한 모델 — 서버가 가진 권위는 무엇, 클라이언트가 예측 가능한 건 무엇” 을 명시. 이걸 미루면 RPC 코드 들어가는 순간 디버깅 지옥이 시작된다. - 네트워크 실사용 검증: cardId 를 도입했으니 실제로
[Command]/[ClientRpc]로 cardId 를 주고받아CardRegistry.Get으로 복원하는 플로우를 한 번 돌려보고 기록 남겨라. 설계만 해놓고 실사용 검증이 빠지면 초기 가정이 어긋났을 때 알기 어렵다.
5. 문서화 상태
design (2/10): repo 에 별도의 GDD/컨셉 문서가 없다. CLAUDE.md 에 카드 수량·데이터 구조 정도가 단편적으로 기술돼 있고 코어 게임 루프, 승리 조건, 왜 이 코스트/파워 커브를 택했는지 같은 설계 근거가 전무하다. _sample/docs 수준의 “월드·루프·학습 목표” 를 갖추려면 최소한 Marvel Snap 스타일 규칙·3구역·6턴 구조·드래프트 규칙의 문서화가 필요하다.
technical (3/10): CLAUDE.md 에 폴더 구조와 CardData/DraftSession 스텁이 있어 완전한 0은 아니지만, 네트워크 권한 모델·턴 상태머신·씬 전환·데이터 흐름 다이어그램이 전혀 없다. 이번 커밋에서 cardId + CardRegistry 를 추가했으면서도 그 의도(“RPC 직렬화 가능 카드 식별자”) 가 문서 어디에도 남지 않았다 — 코드 리뷰어가 git log 를 뒤져야 이해할 수 있다.
spec (1/10): 카드 30장의 수치·효과 설명은 에셋 YAML 에만 존재하고 사양서 형태의 테이블이 없다. 밸런싱 논의·테스트 체크리스트·레시피 격 사양(효과 타입 정의, 효과 적용 우선순위)도 없어 QA 기준으로 쓸 수 없다. _sample/docs/사양서_레시피.md 의 형식을 본떠 “카드 카탈로그 + 효과 공정” 문서를 하나 만들면 단숨에 4~5점으로 오를 여지가 있다.
6. Backlog
- 프로젝트 문서(GDD/기술/사양)가 repo 에 존재하지 않아 신규 유입자가 코드만으로 설계 의도를 복원해야 한다. 네트워킹이 붙기 전에 반드시 기술문서를 세워야 하는 시점이다.
CardRegistry가 static + Resources.LoadAll 기반이라 테스트 격리/런타임 교체가 어렵다. 현 규모에선 용납되지만 규모가 커지면 DI 또는ScriptableObject카탈로그로 옮길 필요가 있다.cardId가 자동 증가 순번이라 에셋 정렬 순서에 의존한다. 카드 추가/삭제 시 ID 가 밀리면 네트워크 호환성과 세이브가 깨질 위험이 있다.- Mirror 기반 네트워킹 코드의 권한 모델(draft/배치/점수 계산의 서버 권위) 이 코드·문서 어디에도 명시되지 않아, RPC 작성 시 클라이언트 권위 실수를 범할 여지가 크다.
CardData.effectType/effectVal로 효과를 enum + 단일 int 에 욱여넣는 설계는 “인접 구역 +2” 같은 현 효과는 커버하지만, 조건부/복합 효과 도입 시 한계에 부딪힐 가능성이 있다.