docs(map-monster-combat): 맵 몬스터 카드 전투 설계
추상 단일 적 → 맵 실제 몬스터 멀티 전투(클릭 타겟·각자 HP/의도·전체 처치 시 승리). 컨트롤러 단일 소유 + script.CombatMonster(EnemyId) 매핑 + 월드 HP바 슬롯. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
118
docs/superpowers/specs/2026-06-10-map-monster-combat-design.md
Normal file
118
docs/superpowers/specs/2026-06-10-map-monster-combat-design.md
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
# 맵 몬스터 카드 전투 — 설계
|
||||||
|
|
||||||
|
- 날짜: 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 감소·사망 애니 → 전체 처치 시 승리 → 기존 보상/노드 흐름 진입. 적 턴에 생존 몬스터가 플레이어 공격.
|
||||||
Reference in New Issue
Block a user