Files
maplecontest/docs/superpowers/specs/2026-06-11-combat-feel-design.md
gahusb 858f9727dd docs(combat-feel): P3 전투 연출 설계+계획 (드래그 타겟·공격 이펙트·개별 차례·팝업)
probe 완료: ScreenTouchEvent/ScreenToUIPosition 실측, UITouchReceiveComponent 드래그 이벤트 확인.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-11 03:18:10 +09:00

64 lines
5.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 전투 연출 (P3) — 설계
- 날짜: 2026-06-11
- 대상: `tools/deck/gen-slaydeck.mjs`(카드 드래그·연출·적 턴 시퀀스), 생성물
- 상태: 승인됨(사용자 사전 위임 — P2~P5 일괄 진행 지시). 로드맵 P3/5.
## 1. 목표 (사용자 요구)
1. **카드 드래그→몬스터 지정**: 카드를 끌어 특정 몬스터에 놓아 사용(클릭 대체).
2. **공격 모션 후 데미지**: 공격 카드 사용 시 연출(스킬 이펙트) 후 몬스터가 피해.
3. **몬스터 개별 차례**: 적 턴에 몬스터가 한 마리씩 순서대로 행동(행동자 표시).
4. 데미지 숫자 표시·사망 연출 등 게임필 보강.
## 2. 타당성 (probe 완료)
- `maker_mouse_input` down → `ScreenTouchEvent` 발화 + `ScreenToUIPosition` 변환 실측 일치(카드2 위치).
- **`MOD.Core.UITouchReceiveComponent`**: UI 엔티티에 부착 시 `UITouchBeginDragEvent`/`UITouchDragEvent`/`UITouchEndDragEvent`/`UITouchDownEvent`/`UITouchUpEvent` 제공(공식, Client). 드래그는 이걸 사용.
- 몬스터 world→screen(`_UILogic:WorldToScreenPosition`)은 P1에서 검증됨 — 드롭 판정에 재사용.
## 3. 설계
### 3.1 카드 드래그 타겟팅
- 손패 Card1~5 엔티티에 `MOD.Core.UITouchReceiveComponent` 추가(생성기 componentNames+컴포넌트).
- 컨트롤러 상태: `DragSlot`(0=없음), `DragOrigin`(원위치 Vector2), `DragMoved`(boolean).
- `BindButtons`에서 카드별로 connect:
- `UITouchBeginDragEvent` → CombatOver 아니고 손패에 카드 있으면 `DragSlot=i`, 원위치 저장(`CARD_XS[i]` 상수로 복원 가능하므로 저장은 단순화 가능 — 원위치 = (CARD_XS[i], 0)).
- `UITouchDragEvent` → 카드 `anchoredPosition = ScreenToUIPosition(TouchPoint) - CardHandOffset` (CardHand 부모 중심의 UI 좌표 보정값은 런타임 계산: 카드 부모 CardHand의 화면상 중심 = UI(0, -360) → 보정 상수로 굽기).
- `UITouchEndDragEvent``ResolveCardDrop(i, TouchPoint)` 후 카드 위치 복원.
- `ResolveCardDrop(slot, screenPoint)`:
- 카드 kind 조회. **Attack**: 생존 몬스터 중 화면 거리(몬스터 world→screen vs screenPoint) 최소이고 임계(예: 200px) 이내인 몬스터 → `SetTarget(그 몬스터)``PlayCard(slot)`. 임계 밖이면 취소(복귀만).
- **Skill**: 드롭 위치가 손패 위(화면 y 기준 카드 영역 위쪽, 예: screen y > 화면 40%)면 `PlayCard(slot)`, 아니면 취소.
- 기존 카드 ButtonComponent 클릭 `PlayCard` 바인딩 **제거**(드래그와 충돌 방지, 사용은 드래그로 일원화 — STS 방식). 몬스터 슬롯 클릭 SetTarget은 유지(타겟만 바꾸는 보조 수단).
### 3.2 공격 연출 → 데미지 (PlayCard Attack 시퀀스)
- `CombatHud/SkillFx` 엔티티 1개(96×96 이미지 스프라이트, 평소 숨김).
- PlayCard(Attack) 흐름 변경: 에너지 차감·손패 제거·렌더는 즉시, **데미지는 지연**:
1. `ShowSkillFx(targetIndex, c.image)`: 타겟 몬스터 world→screen 위치에 SkillFx 표시(ImageRUID=카드 이미지).
2. 0.35s 타이머 → SkillFx 숨김 + `DealDamageToTarget(damage)` + 데미지 팝업 + RenderCombat + CheckCombatEnd.
- 연출 중 입력 보호: `FxBusy=true` 동안 PlayCard/EndPlayerTurn 무시(0.35s).
### 3.3 데미지 숫자 팝업
- `MonsterSlot{i}/DmgPop`(텍스트, 숨김 기본): `ShowDmgPop(slot, amount)` — "-N" 표시 → 0.6s 후 숨김(타이머; 위치 고정 단순화).
- `PlayerPanel/DmgPop` 동일(적 공격 시 "-N", 방어 흡수로 0이면 "막음").
### 3.4 적 개별 차례 (EnemyTurn 시퀀스화)
- `EnemyTurn` → 비동기 체인으로 재작성:
- `EnemyActIndex=0`; `EnemyActStep()`: 다음 생존 몬스터 찾기 → 없으면 `FinishEnemyTurn()`.
- 행동 몬스터 슬롯에 `ActFrame`(적색 하이라이트 — TargetFrame과 별도 자식, 156×108 적색 a0.3) 표시 → 0.45s 타이머 → 의도 적용(Attack: DealDamageToPlayer+플레이어 DmgPop / Defend: block+슬롯 의도 갱신) → ActFrame 숨김 → 다음 `EnemyActStep()` (0.15s 간격).
- 플레이어 사망 시 즉시 `FinishEnemyTurn()`.
- `FinishEnemyTurn()`: `CheckCombatEnd` 후 미종료면 0.45s 뒤 `StartPlayerTurn`(기존 EndPlayerTurn 후반부 이동).
- `EndPlayerTurn`: 손패 버림+렌더 후 `EnemyTurn()` 호출로 종료(후속 로직은 FinishEnemyTurn으로 이동). `TurnBusy=true`로 적 턴 중 입력 차단(FxBusy와 함께 가드).
### 3.5 사망 연출
- `KillMonster`: 즉시 SetVisible(false) → **0.4s 지연**으로 변경(DmgPop과 겹쳐 보이게), 슬롯 비활성은 즉시 유지.
## 4. 검증
- 생성 결정성·dup 0·sim 14/14(규칙 불변 — 연출 지연만 추가, 데미지 계산 동일).
- 메이커: ①카드를 몬스터2에 드래그→타겟 변경+이펙트→데미지 팝업→HP 감소 ②Skill 카드 위로 드래그→방어 ③드롭 취소(빈 곳) ④턴 종료→적들이 한 마리씩 순차 행동(ActFrame 이동)+플레이어 팝업 ⑤전체 처치 승리 정상.
## 5. 리스크
- UITouchReceiveComponent와 ButtonComponent 공존(슬롯 클릭/드래그 간섭) — 카드에서 Button 제거하므로 카드는 안전; 몬스터 슬롯은 Button 유지(드래그 없음).
- UITouchDragEvent 빈도/좌표계 — 구현 후 메이커 검증(§4①). 드래그 좌표 보정 상수는 실측 튜닝.
- 비동기 체인 중 상태 변화(연출 중 사망 등) — FxBusy/TurnBusy 가드 + 각 스텝에서 alive/CombatOver 재확인.