11 KiB
11 KiB
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]_<CamelCase>. 기획실 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)
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):
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)
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<int, int> 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<eCardType, CardSkillData> 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:6230.9f(크리·회피 상한)MyValue.cs:23eCardType.Maxsentinel — 선택적 테이블화 대상
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 버그 냄새
- 중복 획득 방지 — 특수 아이템
list_SkillId경로 (확인 필요) - OnEvent 순서 의존성 —
AddSkillCard↔RunSkillCard순서 - 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 초안 |