# 맵 몬스터 카드 전투 — 설계 - 날짜: 2026-06-10 - 대상: `tools/deck/gen-slaydeck.mjs`(SlayDeckController + UI), `data/enemies.json`, 신규 `tools/monster/gen-combat-monster.mjs`(+`CombatMonster.codeblock`), 맵 몬스터 엔티티, `tools/balance/sim-balance.mjs` - 상태: 승인됨 (브레인스토밍 → 본 스펙) ## 1. 배경 현재 카드 전투는 `SlayDeckController` 내부의 **추상 단일 적**으로 동작한다. - 상태: `EnemyHp`/`EnemyMaxHp`/`EnemyBlock`/`EnemyName`/`EnemyIntents`/`EnemyIntentIndex` (단일 적 1체). - 공격 카드 → `DealDamageToEnemy(damage)` → `EnemyHp` 차감. - `CheckCombatEnd`: `EnemyHp<=0` → 승리(골드·보상·노드/막 진행), `PlayerHp<=0` → 패배. - 적 데이터는 `data/enemies.json`(slime/elite/boss), 노드의 `CurrentEnemyId`가 어떤 적인지 결정, `Floor` 배율 적용. - CombatHud는 단일 적 패널(EnemyName/EnemyHp/EnemyBlock/EnemyIntent)을 텍스트로 표시. 맵에는 실제 몬스터 엔티티(`script.Monster` + `script.MonsterAttack`, 예: map01의 주황버섯)가 있으나, 이는 물리 액션 전투용이라 **카드 전투와 완전히 분리**되어 있다. ## 2. 목표 카드 공격이 추상 슬라임이 아니라 **맵의 실제 몬스터**에 적용되고, **맵의 모든 몬스터가 쓰러지면 전투 승리**가 되도록 한다. 승리 이후 흐름(보상·노드·상점·휴식·막/보스)은 기존 런 시스템을 그대로 재사용한다. ## 3. 확정 요구사항 (브레인스토밍 결과) 1. **타겟팅**: 몬스터를 클릭하면 그 몬스터가 "현재 타겟"이 되고, 이후 공격 카드가 그 타겟에 적용된다. 2. **전투원 모델**: 각 몬스터가 개별 HP와 의도(공격/방어)를 가지며, 적 턴에 생존 몬스터가 각자 플레이어를 공격한다(멀티 적). 3. **런 연동**: 전투 노드 = 현재 물리 맵의 몬스터들. 전부 처치 시 노드 클리어 → 기존 보상/맵/상점/막 흐름 유지. 4. **스탯 소스**: 각 맵 몬스터가 적 타입 id를 보유하고 HP/의도를 `data/enemies.json`에서 읽는다. 배율은 **막 배율(필수, 기존 `1+(Floor-1)*0.6` 재사용)** + **노드 타입 배율(선택, 기본 1; 엘리트/보스만 가산)**. 5. **상태 표시**: 각 몬스터 머리 위에 월드(화면) HP바 + 의도 표시. 6. **아키텍처**: 컨트롤러 중심. 전투 상태는 `SlayDeckController`가 단일 소유, 몬스터 엔티티는 타겟·시각(애니메이션) 역할만. 사망 연출은 기존 `script.Monster` 자산 재사용. 7. **몬스터↔적ID**: 전용 경량 스크립트 `script.CombatMonster`의 `EnemyId`(string) 속성으로 명시. ## 4. 데이터 모델 ### 4.1 `data/enemies.json` 맵 몬스터용 적 타입을 추가한다(기존 slime/elite/boss는 유지). 예: ```json "orange_mushroom": { "name": "주황버섯", "maxHp": 16, "intents": [ { "kind": "Attack", "value": 5 }, { "kind": "Defend", "value": 4 } ] } ``` 스탯 수치는 placeholder이며 `sim-balance.mjs`로 추후 튜닝한다. ### 4.2 `script.CombatMonster` (신규 코드블록) 맵 몬스터 엔티티에 부착하는 경량 마커. 속성 `EnemyId`(string) 1개. 런타임 로직 없음(컨트롤러가 읽기만). `script.Monster`를 보유한 엔티티에 함께 부착한다. ### 4.3 런타임 전투 상태 (SlayDeckController) 단일 `Enemy*` 속성군을 제거하고 리스트로 교체한다. - `Monsters`(any 리스트). 원소: `{ path, enemyId, name, hp, maxHp, block, intents, intentIndex, alive }`. - `TargetIndex`(number). 현재 선택된 타겟의 `Monsters` 인덱스. - 상수 `MAX_MONSTERS`(예: 4). UI 슬롯 수 = 이 값. 맵 몬스터가 더 많으면 앞에서 `MAX_MONSTERS`마리만 전투에 참여(초과분은 미지원, 로그 경고). ## 5. 전투 흐름 (SlayDeckController 메서드 변경) - **StartCombat**: 1. 현재 맵에서 `script.CombatMonster`를 가진 몬스터 엔티티를 스캔(맵 루트 하위, 최대 `MAX_MONSTERS`). 2. 각 몬스터의 `EnemyId`로 `enemies.json` 스탯을 읽어 배율(막 배율 필수 + 노드 타입 배율 선택, §3-4)을 적용해 `Monsters` 리스트 구성. 3. 몬스터 부활: 가시성 on, `StateComponent` IDLE, HP 리셋. 4. 각 몬스터 화면 위치에 UI 슬롯(HP바·의도·타겟버튼) 배치·활성화. 5. `TargetIndex` = 첫 생존자. 손패/턴 시작. - **SetTarget(i)**: `TargetIndex` 갱신 + 타겟 하이라이트 갱신. - **PlayCard(Attack)**: `Monsters[TargetIndex]`에 방어도→HP 차감. HP≤0 → 해당 몬스터 사망 처리(§6), UI 슬롯 숨김, 자동으로 다음 생존 타겟 선택. 이후 `CheckCombatEnd`. - **PlayCard(Skill)**: 기존대로 플레이어 방어도 증가(변경 없음). - **EnemyTurn**: 생존 몬스터 각자 `intentIndex` 진행 — Attack→`DealDamageToPlayer`, Defend→자기 `block` 증가. (각 몬스터는 독립 의도 사이클) - **CheckCombatEnd**: 생존 몬스터 0 → 승리(기존 보상/노드/막 분기 재사용). `PlayerHp<=0` → 패배. - **RenderCombat**: 각 생존 몬스터 UI 슬롯의 HP바·의도 갱신, 플레이어 패널 갱신. 기존 단일 적 패널(EnemyName/EnemyHp/EnemyIntent)은 제거 또는 숨김. ## 6. 사망 / 부활 연출 컨트롤러가 직접 관리하여 **노드 간 몬스터 영속**(엔티티 Destroy 안 함): - 사망: 타겟 몬스터의 `StateComponent`를 DEAD로 전환(die 애니메이션) 후 짧은 지연 뒤 가시성 off. `alive=false`. - 부활: `StartCombat`에서 가시성 on, IDLE 상태, HP 리셋. 기존 `Monster.codeblock`의 hit/die 애니메이션 자산을 활용하되, Destroy/Respawn 타이머에 의존하지 않고 컨트롤러가 생사 시점을 통제한다. ## 7. UI — 몬스터 슬롯 (DefaultGroup.ui) 카메라가 고정(MapCamera)이라 몬스터의 화면상 위치가 불변 → UI 슬롯을 전투 시작 시 한 번 배치하면 된다. - `gen-slaydeck.mjs`가 `CombatHud` 아래 **MonsterSlot ×MAX_MONSTERS**를 사전 생성(평소 비활성): - HP바 스프라이트(배경+채움), 의도 텍스트, 투명 타겟 버튼(클릭→`SetTarget`). - **위치 결정**: 런타임 world→screen 변환을 우선 시도(카메라 고정이므로 전투 시작 시 1회 계산). 변환 API가 여의치 않으면 **`data`에 슬롯 화면좌표를 명시**(현재 map01 몬스터 배치 기준)하는 폴백을 사용한다. → 구현 단계에서 변환 가용성 검증. ## 8. 변경 파일 요약 | 파일 | 변경 | |------|------| | `data/enemies.json` | 맵 몬스터 적 타입 추가(orange_mushroom 등) | | 신규 `tools/monster/gen-combat-monster.mjs` | `CombatMonster.codeblock` 생성 + 11개 맵 몬스터 엔티티에 `script.CombatMonster`(EnemyId) 부착(idempotent) | | `RootDesk/MyDesk/CombatMonster.codeblock` | 신규 생성물 | | `tools/deck/gen-slaydeck.mjs` | 전투 멀티 몬스터화(상태·PlayCard·EnemyTurn·CheckCombatEnd·RenderCombat·StartCombat·타겟 바인딩) + UI 몬스터 슬롯 생성 | | `tools/balance/sim-balance.mjs` | 멀티 몬스터 규칙으로 동기화 | | `map/map01.map`~`map11.map` | 몬스터 엔티티에 `script.CombatMonster` 부착(생성기 재실행 산출) | | `ui/DefaultGroup.ui` | 몬스터 슬롯 추가(생성기 산출) | ## 9. 알려진 한계 (MVP) - 모든 전투 노드가 같은 물리 맵 몬스터를 재사용한다(막 배율로 난이도 차등). 노드별 다른 적 구성/맵 이동은 후속 과제. - `MAX_MONSTERS` 초과 몬스터는 전투에 미참여. - 보스 노드도 동일 맵 몬스터를 사용(테마 불일치)는 후속 콘텐츠 확장에서 해결. ## 10. 리스크 - **world→screen 변환 가용성**: 미지원 시 슬롯 좌표 데이터 폴백으로 대응(§7). - **외부 엔티티 스크립트 메서드/상태 접근**: 컨트롤러가 몬스터 엔티티의 `StateComponent`·가시성을 제어할 수 있어야 함(구현 단계 검증). - **생성물 단일 소스 유지**: 전투/HUD 산출물은 `gen-slaydeck.mjs`에서만 생성(직접 편집 금지) 규칙 유지. ## 11. 검증 - `node tools/balance/sim-balance.test.mjs` 통과 + 멀티 몬스터 규칙 반영. - 생성기 2회 실행 결과 동일(결정적). - 메이커 플레이: 카드로 특정 몬스터 타겟 공격 → HP 감소·사망 애니 → 전체 처치 시 승리 → 기존 보상/노드 흐름 진입. 적 턴에 생존 몬스터가 플레이어 공격.