# 09. 카드시스템 아키텍처 v1 - 작성일: 2026-04-14 - 작성자: 개발실장 (Explore 에이전트 분석 위임) - 목적: 기획실이 관리하는 카드(G1~G5 총 311장) 이 **코드상 어떻게 표현·실행·획득되는지** 확정 - 스코프: `Assets/Script/Table/Tables/table_cardlist.cs`, `Assets/Script/InGame/Actor/Actor.cs`, `Assets/Script/My/MyClass.cs` - 선행 문서: `08_전투시스템_SOT_v1.md` --- ## 1. 카드 데이터 모델 ### 1.1 eCardType Enum `table_cardlist.cs:123~450` 에 `G[1-5]_<효과명>` 형태로 311개 정의. | 등급 | 개수 | 대표 | |---|---|---| | G1 (Common) | 112 | G1_AddPotion ~ G1_ShieldIncreaseAttackPower | | G2 (Uncommon) | 73 | G2_CriUpAfterCri ~ G2_RangeDodgeUp | | G3 (Rare) | 51 | G3_CritDmgUp ~ | | G4 (Hero) | 43 | G4_ZeroShieldCritRate ~ | | G5 (Legend) | 32 | G5_RemoveNormalSkills ~ G5_RandomLightning_N | | **합계** | **311** | `eCardType.Max` sentinel | 명명 규칙: `G[1-5]_`. 기획실 SOT 엑셀 행과 1:1 매핑. ### 1.2 CardListTableData 필드 (`table_cardlist.cs:7~120`) | 필드 | 타입 | 암호화 | 용도 | |---|---|---|---| | `e_CardType` | enum | ✗ | 식별자 | | `n_ID` | int | **ObscuredInt** | 고유 ID | | `e_CardGrade` | eGrade | ✗ | 등급 | | `s_Value1~3` / `f_Value1~3` | string / float | float만 **Obscured** | 수치 (문자열/런타임 이원) | | `s_LvUpValue1~2` / `f_LvUpValue1~2` | string / float | float만 **Obscured** | 레벨업 가중치 | | `e_LvUpType` | enum | ✗ | 레벨업 적용 방식 | | `b_Percent`, `b_LvUpValue*Percent` | bool | ✗ | % 해석 플래그 | | `s_Projectile` / `e_ProjectileAttackType` / `n_ProjectileCount` / `f_ProjectileDelayTime` | — | Obscured(int/float) | 투사체 | | `n_StatusOptionSetID` | int | **Obscured** | `table_StatusOptionSet` 조인 | | `n_UseDmg` | int | **Obscured** | 피해 계산 방식 (0=고정, 1=%비율, 2=배수) **(확인 필요)** | | `n_Desc`, `n_LvUpDesc` | int | ✗ | 텍스트 테이블 키 | **핵심 관찰**: 수치는 `ObscuredFloat/Int`로 안티치트 적용. 메타(enum, 플래그)는 평문. **수치 변경은 테이블만 수정하면 코드 무수정** — 기획실 밸런싱 규칙("효과 종류 변경 금지, 수치만 조정")과 구조적으로 일치. ### 1.3 eLvUpType (`table_cardlist.cs:110~122`) ```csharp Use_LvUpValue1_To_Value1 // f_Value1 누적 Use_LvUpValue1_To_Value2 Use_LvUpValue1_To_Value3 Use_LvUpValue1_To_ProjectileCount Heal_Multi / Heal_Mul // 즉시 회복 효과 GetGold // 즉시 골드 ``` 레벨업 수치 적용 (`CardListTableData.cs:96~100`): ```csharp int Get_CardLv() => Mathf.Max(0, MyValue.sdata.Get_CardLv(n_ID) - 1); float Get_LvUpValue1(eLvUpType t) => e_LvUpType == t ? f_LvUpValue1 * Get_CardLv() : 0; ``` → **선형 누적**: `Value_final = Value_base + Level * Coefficient`. ### 1.4 CardSkillData 런타임 인스턴스 (`MyClass.cs:147~250`) ```csharp public class CardSkillData { public CardListTableData m_Data; Actor m_Actor; public bool StartBattle; public int TargetIdentity; public int UseCount; public int UseCount_Acc; public float m_LifeTime; // 주기 쿨타임 public Dictionary dic_identity; // 대상별 추적 } ``` --- ## 2. 카드 효과 실행 구조 ### 2.1 OnEvent_* 진입점 맵 (~258개 분기) `Actor.cs` 전반: | 메서드 | 라인 | 발동 | 분기 수 | |---|---|---|---| | `OnEvent_AddSkillCard()` | 1799 | 카드 획득 직후 1회 | ~80 | | `OnEvent_RunSkillCard()` | 1655 | 획득/라인변경 반복 | ~50 | | `OnEvent_Kill_Hitter()` | 2244 | 적 처치 | ~20 | | `OnEvent_Cri_Hitter()` | 2616 | 크리 발동 | ~15 | | `OnEvent_Avoid()` | 3001 | 회피 성공 | ~12 | | `OnEvent_NoMiss_Hitter()` | 2442 | 회피 실패 | ~8 | | `OnEvent_MyShield_Hit()` | 3131 | 쉴드 피해 | ~10 | | `OnEvent_MyShield_Break()` | 3145 | 쉴드 소진 | ~5 | | `OnEvent_GetDmg()` | 3202 | 피해 수신 | ~15 | | `OnEvent_PlayAttack()` | 2022 | 공격 발동 | ~20 | | `OnEvent_LvUp()` | 2917 | 레벨업 | ~25 | | `OnEvent_MeetNode()` | 2746 | 노드 진입 | ~5 | | `OnEvent_MakeMob()` | 3408 | 몬스터 생성 | ~3 | | `OnEvent_Hit()` | 3218 | 피해 적용 후 | ~10 | ### 2.2 효과 적용 3가지 패턴 - **A. 조건 분기 (if-switch)** — 카드 1장당 1개 분기 (`Actor.cs:1801~1830` 등). 신규 추가 시 **코드 수정 필요**. - **B. 데이터 주도 (테이블 참조)** — `CardSkillData.Add(...)` 에서 `Set_Buff(eStat, value)` 로 일반화 (`MyClass.cs:161~180`). 이 경로는 enum 케이스만 추가하면 됨. - **C. 주기형 (m_LifeTime)** — `G1_MagicMissile` 처럼 쿨타임 기반 (`MyClass.cs:165~166`). `Update()`에서 감소. ### 2.3 트리거 분류표 | 트리거 | 예시 카드 | 위치 | |---|---|---| | 즉시 (획득 시) | G1_FireMagicMissiles, G1_InstantFullHeal | OnEvent_AddSkillCard | | 온 공격 | G1_FirstAttack, G1_FifthAttackBack | OnEvent_PlayAttack | | 온 피해 | G1_HealOnRangedHit | OnEvent_GetDmg | | 온 킬 | G1_CastMagicMissilesOnBackKill | OnEvent_Kill_Hitter | | 온 크리 | G1_ThirdCritDamage | OnEvent_Cri_Hitter | | 온 회피 | G1_LifeStealOnDodge | OnEvent_Avoid | | 온 노드시작 | G2_HeroSkillFullHeal | OnEvent_RunSkillCard | | 영구 스탯 | G1_AddMaxAttack | CardSkillData.Add → Set_Buff | | 1회성 버프 | G1_DodgeNextNAttacks | OnEvent_AddSkillCard | | 주기 효과 | G1_MagicMissile | m_LifeTime | --- ## 3. 카드 획득 · 보유 · 제거 ### 3.1 획득 경로 ``` 노드 클리어 → Co_AllKill() → act_EndNode() → SelectCardUI.Set_Cards() [BattleCard.cs:37] → BattleCard.OnClick() → InGameInfo.Ins.Add_Card(CardListTableData) → Actor.Add_CardSkill() [Actor.cs:337] → dic_Card[eCardType] = CardSkillData → OnEvent_AddSkillCard() ``` 추가 경로: - 성소: `Actor.Add_Card_Random()` (`Actor.cs:4474`) - 장비 특수효과: `Set_Equipment()` → `list_SkillId` (`Actor.cs:3773`) - 상태이상 연쇄: `GetSkillCard_N` (`Actor.cs:3866~3871`) ### 3.2 보유 구조 - 저장소: `Dictionary dic_Card` — **카드 타입별 1장** - 조회: `IsObtainCardSkill(eCardType)` O(1) - 슬롯 상한: **코드상 무제한** — **(확인 필요)** 기획 UI 상한 존재 여부 - 중복 방지: `if (!dic_Card.ContainsKey(...))` (`Actor.cs:4487`) ### 3.3 제거 경로 **현재 코드상 카드 제거 경로 없음** — `Actor.Set()` (`Actor.cs:148`) 에서 런 리셋 시 `dic_Card.Clear()` 만 존재. --- ## 4. 카드 효과 카탈로그 (대표 샘플) ### 4.1 카테고리별 | 카테고리 | 수량 추정 | 대표 | |---|---|---| | 공격력 증강 | ~25 | G1_AddMaxAttack, G3_BacklineDoubleDmg, G5_FlatDamageMinMaxEqual | | 피해 감소/무효화 | ~18 | G2_ReduceMeeleEnemyDamage, G2_ActivateInvincibleShieldOnKill, G1_ReflectOnRangedDodge | | 회피/회피율 | ~12 | G1_DodgeNextNAttacks, G2_FirstMeleeAlwaysEvade, G1_MaxDodgeWhenHpBelow | | 크리 | ~15 | G2_CriUpAfterCri, G1_ThirdCritDamage, G2_NextSevenHitsCritical, G5_EvadeCrit | | 쉴드 | ~20 | G1_ShieldOnRangedDodge, G1_MaxShieldUpOnLevelUp, G1_SpringShieldToHP | | 회복/생명력 | ~22 | G1_HealOnRangedHit, G1_AngelFeatherHeal, G1_StopExploreHealHP | | 상태이상 부여 | ~15 | G1_EnemySpawnStunChance, G1_StunAllMeleeEnemies, G5_EvadeStun_N | | 특수 투사체 | ~35 | G1_MagicMissile, G1_ThunderOnFifthHit, G4_StartArrowRainRangedImmune, G5_CritTriggerMeteor | | 스킬 카드/특수 | ~18 | G1_GetRandomRareOrEpicSkill, G1_NextLevelChooseTwoSkillCards, G1_CastReaperOnLevelUp | | 수집/보상 | ~12 | G1_GainGoldOnSanctuaryFind, G1_XpGainOnCritKill, G1_CampRechargePotion | | 방어/면역 | ~10 | G2_ActivateInvincibleShieldOnKill, G5_PotionDamageImmunity | ### 4.2 등급별 경향 - **G1 112장 (36%)** — 기초재. 모든 런에서 자주 획득. - **G2 73장 (23%)** — 조건부/조합 효과. - **G3 51장 (16%)** — 라인·배수·크리 특수. - **G4 43장 (14%)** — 실드·동일대상·면역 연계. - **G5 32장 (10%)** — 투사체·무적·즉사 최상위 시너지. G1 비중이 높은 것은 기획 의도(풀초기/보편 효과)로 해석 가능. --- ## 5. 카드시스템과 밸런싱 훅 ### 5.1 데이터 입력 흐름 ``` 기획실 .xlsm → (익스포트 도구) → JSON/CSV → (코드 생성) → table_cardlist.cs (자동) → (Awake) → CardListTableData 싱글톤 → (전투 중) → CardSkillData.m_Data → Get_*Value*() ``` **코드상 수치 하드코딩은 확인되지 않음** — 모든 수치는 테이블 필드. → 밸런싱은 테이블만 수정하면 된다는 구조적 전제 성립. ### 5.2 핫 리로드 - **현재 불가** — 싱글톤 Awake에서 초기화, 런 중 반영 안 됨. 재시작 필요. - 개선안: ScriptableObject / JSON 원격 동기화 / 기획실 대시보드 연동 — **Phase 1+ 고려**. ### 5.3 기획실 워크플로 접촉점 | 단계 | 파일/도구 | 비고 | |---|---|---| | 효과 설계 | 기획 엑셀 | 효과 종류 변경 금지 (규칙) | | 수치 입력 | `f_Value1~3`, `f_LvUpValue1~2` | | | 익스포트 | 도구 경로 | **(확인 필요)** 문서화 필요 | | 코드 생성 | `table_cardlist.cs` 자동 | | | QA | Unity 런타임 | | | 시뮬레이터 검증 | 기획실 매크로 | **코드와 공식 일치 여부 (확인 필요)** | --- ## 6. 리스크 · 이슈 ### 6.1 조건 분기 폭발 (~258 위치) - 단일 카드 효과가 여러 `OnEvent_*`에 분산 → 신규 추가 시 수정 누락 위험. - 개선안: `interface ICardEffect` + `CardEffectRegistry` 디스패처 패턴으로 효과별 객체 분리. **(장기 리팩토링 과제)** ### 6.2 하드코딩 상수 (08 문서와 중복) - `3.69` (회피 분모) `Actor.cs:623` - `0.9f` (크리·회피 상한) `MyValue.cs:23` - `eCardType.Max` sentinel — 선택적 테이블화 대상 ### 6.3 구현 누락 후보 - G5 32장 중 일부 미구현/주석 처리 가능성 — **(확인 필요)** TODO/FIXME/주석 grep - `G1_NullifyDamageEveryNHits` — `Dmg_Immune` 스탯 순서 버그 가능성 ### 6.4 시뮬레이터 동기화 | 항목 | 코드 | 시뮬 | 상태 | |---|---|---|---| | 피해 15단계 | Actor.Get_Dmg | 엑셀 매크로 | 대조 필요 | | 크리 확률 | RandomTrue + 강제 | 고정% | 대조 필요 | | 회피 상한 | PC 0.9f | ? | **(확인 필요)** | | 카드 효과 트리거 | 분산 | 테이블 | 큰 차이 | ### 6.5 성능 - `ObscuredInt/Float` 복호화·재암호화 비용이 **Get_Damage 호출마다** 발생. 크리 판정/피해 계산에서 프레임당 수십 회. → Profiler 측정 필요. ### 6.6 버그 냄새 1. 중복 획득 방지 — 특수 아이템 `list_SkillId` 경로 **(확인 필요)** 2. OnEvent 순서 의존성 — `AddSkillCard` ↔ `RunSkillCard` 순서 3. G5 카드 스킵 로직 — 이미 획득 상태 체크 누락 가능성 --- ## 7. B-2 완료 조건 체크리스트 **필수** - [ ] 311개 전체 eCardType 하드카피 리스트 (문자열 ID) - [ ] 각 카드의 대표 분기점 매핑 (OnEvent_*) - [ ] G5 미구현/주석 목록 확보 - [ ] `table_cardlist` 필드 ↔ 엑셀 컬럼 1:1 매핑 확정 - [ ] eLvUpType 전 케이스 동작 테스트 - [ ] IsObtainCardSkill 258 호출 위치 전수 지도 (누락 확인) **선택** - [ ] 기획 엑셀 매크로 방정식 덤프 (코드 수식과 대조) - [ ] 카드 의존성 그래프 - [ ] ObscuredFloat 프로파일링 --- ## 8. 변경 이력 | 버전 | 일자 | 작성자 | 내용 | |---|---|---|---| | v1 | 2026-04-14 | 개발실장 (Explore 위임) | Phase 0-B-2 초안 |