184 lines
10 KiB
Markdown
184 lines
10 KiB
Markdown
# ArenaScene 모듈화 작업지시서
|
|
|
|
작성일: 2026-05-23
|
|
|
|
## 목적
|
|
|
|
`src/game/ArenaScene.js`가 매치 생명주기, Phaser scene hook, HUD DOM, 관전 카메라, 미니맵, 최종교전 슬로우모션, 승리 연출, 사망 통계까지 함께 들고 있다. 이번 작업은 기능별 모듈로 책임을 나누고 `ArenaScene`은 scene orchestration과 외부 공개 메서드의 얇은 진입점으로 줄이는 리팩터링이다.
|
|
|
|
이번 작업은 **동작 보존 리팩터링**으로 진행한다. 게임 규칙, UI 문구, 튜닝 상수, 에셋, API 스펙은 바꾸지 않는다.
|
|
|
|
## 현재 코드 관찰
|
|
|
|
- 대상 파일은 `src/game/ArenaScene.js`다. `src/main.js`는 대소문자 포함 `./game/ArenaScene.js`를 import하므로 파일명/import 경로는 유지한다.
|
|
- 현재 파일 크기는 약 42KB이고 역할이 아래 구간에 섞여 있다.
|
|
|
|
| 구간 | 현재 책임 |
|
|
| --- | --- |
|
|
| `ArenaScene` constructor, `preload()`, `create()`, `startMatch()` | scene 상태 초기화, 입력 바인딩, 매치 시작 |
|
|
| `spawnSplitFighters()` | 전투 중 분열 전투원 생성 |
|
|
| `resetMatchDeathStats()` ~ `persistDailyDeathStats()` | 실제 매치 사망 집계, 상단 battle notice 스케줄링, API 연동 |
|
|
| `resetKillLog()` ~ `getKillLogNodes()` | 킬로그 DOM |
|
|
| `update()` ~ `getObservedCombatCenter()` | 전투 update loop, 관전 카메라, 선택 관전, presentation 관전 |
|
|
| `triggerFinalCombatSlowMotion()` ~ `restoreSceneTimeScale()` | 최종교전 슬로우모션 |
|
|
| `setPaused()` | 실제 매치 pause/resume |
|
|
| `setMainCameraZoom()` ~ `snapMinimapFrameValue()` | 줌과 미니맵 viewport frame |
|
|
| `updateScoreboard()` | 팀 badge HUD DOM |
|
|
| `finishMatch()` | 종료 판정, presentation 재시작, 사망 통계 저장, 승리 상태 |
|
|
| 파일 하단 helper들 | 승리 overlay/audio, death notice 문구, kill log node 생성, 스폰 확장, spectator 순수 계산 |
|
|
|
|
## 유지해야 할 외부 계약
|
|
|
|
리팩터링 중 아래 표면은 깨지지 않게 유지한다. 구현을 다른 모듈로 옮기더라도 필요하면 `ArenaScene`에 같은 이름의 delegate 메서드를 남긴다.
|
|
|
|
### 앱 진입점 계약
|
|
|
|
`src/main.js`는 다음을 사용한다.
|
|
|
|
- `new ArenaScene({ getInitialMatchConfig, setStatus })`
|
|
- `arenaScene.startMatch(matchConfig)`
|
|
- `arenaScene.isMatchPaused()`
|
|
- `arenaScene.togglePause()`
|
|
|
|
### combat 계약
|
|
|
|
`src/game/combat.js`는 scene에 아래 optional callback 메서드가 있다고 보고 호출한다.
|
|
|
|
- `scene.observeCombat(attacker, defender)`
|
|
- `scene.triggerFinalCombatSlowMotion(attacker, defender, attackAnimation)`
|
|
- `winner.scene.recordKill(winner, defender)`
|
|
- `fighter.scene.spawnSplitFighters(fighter, splitOnDeath)`
|
|
|
|
이 계약은 우선 유지한다. `combat.js`까지 구조를 바꾸는 작업은 이번 리팩터링의 필수 범위가 아니다.
|
|
|
|
### Phaser hook 계약
|
|
|
|
- `preload()`
|
|
- `create()`
|
|
- `update(time)`
|
|
|
|
hook은 `ArenaScene`에 남기고 기능 모듈로 위임한다.
|
|
|
|
## 권장 분리 경계
|
|
|
|
아래 파일명은 권장안이다. 기존 코드 패턴과 충돌하면 더 나은 이름을 써도 되지만, 책임 경계는 유지한다.
|
|
|
|
| 권장 모듈 | 옮길 책임 | 비고 |
|
|
| --- | --- | --- |
|
|
| `src/game/arenaMatchRuntime.js` | fighter plan 확장, spawn cluster 계산, team size sync, 분열 스폰 보조 로직 | 순수 helper를 먼저 빼고 scene mutation은 작은 함수로 감싼다. |
|
|
| `src/game/arenaSpectatorCamera.js` | spectator state 계산, 관전 대상 선택, presentation follow, 선택 fighter camera focus | camera 관련 계산과 scene 동작을 한 경계로 묶는다. |
|
|
| `src/game/arenaMinimap.js` | minimap camera 생성, zoom 연동, viewport frame 렌더링 | spectator 모듈이 너무 커지면 분리한다. |
|
|
| `src/game/arenaFinalCombatEffects.js` | final combat slow motion 진입/유지/복귀, easing, timeScale 적용/복원 | Arcade Physics timeScale 역수 적용을 그대로 보존한다. |
|
|
| `src/ui/arenaScoreboard.js` | 팀 badge DOM 갱신과 team click 핸들링 | DOM 생성 책임을 scene에서 뺀다. |
|
|
| `src/ui/arenaKillLog.js` | kill log reset/append/node factory/avatar URL helper | `document` 접근과 node cache 경계를 명확히 한다. |
|
|
| `src/ui/battleDeathNotice.js` | 종족별 death count helper, notice 문구 생성, notice DOM/timer, 오늘 사망 통계 fetch/persist 흐름 | presentation match 제외 조건을 유지한다. |
|
|
| `src/ui/victoryCelebration.js` | victory overlay DOM, confetti, fanfare audio priming/playback | `setStatus()` wrapper가 이 모듈만 호출하게 만든다. |
|
|
|
|
`ArenaScene`에 남길 책임은 다음 정도로 제한한다.
|
|
|
|
- scene 상태의 루트 소유권
|
|
- Phaser hook과 입력 이벤트 등록
|
|
- 매치 시작/종료 orchestration
|
|
- `main.js`와 `combat.js`가 부르는 공개 delegate 메서드
|
|
- 기능 모듈을 호출하는 update 흐름
|
|
|
|
## 구현 원칙
|
|
|
|
1. 첫 패스에서는 상태 저장소를 새로 만들지 않는다. 이미 scene에 있는 상태를 유지하고, 기능 모듈은 필요한 scene 또는 명시적 인자를 받게 한다.
|
|
2. 먼저 파일 하단의 순수 helper를 추출한다. 예를 들면 spectator 계산, spawn plan 계산, death count/message 계산은 scene method 이동보다 리스크가 낮다.
|
|
3. DOM 전용 로직은 `src/ui/`로 옮긴다. Phaser object 조작과 DOM node 생성이 한 함수에 섞이지 않게 한다.
|
|
4. 외부 호출 메서드는 한 번에 제거하지 않는다. 예를 들어 `recordKill()`은 `ArenaScene`에 남겨 `appendKillLog()`와 death count 갱신 함수에 위임해도 된다.
|
|
5. 공통 manager class를 크게 만들지 않는다. 기능별 함수 또는 상태가 분명한 작은 controller가 우선이다.
|
|
6. 기존 상수는 가능한 한 현재 위치를 유지한다. 기능 전용 상수가 새 모듈과 함께 이동할 때만 옮긴다.
|
|
7. 문서 업데이트가 필요한 구조 변경이면 `CONTEXT.md`, `agent.md`, `todo.md`의 설명도 실제 변경에 맞게 갱신한다.
|
|
|
|
## 권장 작업 순서
|
|
|
|
### 1. 기준선 확인
|
|
|
|
- `npm run build`로 현재 build 기준선을 확인한다.
|
|
- 자동 테스트 스크립트는 `package.json`에 없다. 빌드와 수동 smoke test를 기본 검증으로 잡는다.
|
|
|
|
### 2. pure helper부터 추출
|
|
|
|
- fighter plan/spawn cluster helper
|
|
- death count/message helper
|
|
- spectator state/position helper
|
|
- victory confetti/audio helper 중 DOM 밖 계산
|
|
|
|
이 단계에서는 `ArenaScene` 동작 순서와 method signature를 바꾸지 않는다.
|
|
|
|
### 3. DOM UI를 분리
|
|
|
|
- scoreboard
|
|
- kill log
|
|
- battle notice/death stats notice
|
|
- victory celebration
|
|
|
|
DOM module은 생성한 node, cached node, timer 정리 책임을 문서화된 함수 경계로 드러낸다.
|
|
|
|
### 4. camera/effect를 분리
|
|
|
|
- spectator camera와 selection focus
|
|
- minimap 생성/viewport frame
|
|
- final combat slow motion
|
|
|
|
카메라가 같은 프레임에서 여러 feature에게 조작되므로 `update()`의 우선순위는 그대로 유지한다.
|
|
|
|
현재 우선순위는 대략 다음과 같다.
|
|
|
|
1. fighter HUD sync
|
|
2. pause면 minimap frame만 갱신하고 return
|
|
3. 매치 진행 중 fighter update
|
|
4. presentation이면 presentation follow 후 return
|
|
5. 선택 fighter focus가 있으면 return
|
|
6. match over면 return
|
|
7. spectator state 기반 camera follow
|
|
8. minimap viewport frame 갱신
|
|
|
|
### 5. scene를 얇게 정리
|
|
|
|
- hook과 orchestration만 남았는지 확인한다.
|
|
- 외부 delegate 메서드가 계속 같은 이름으로 동작하는지 확인한다.
|
|
- import graph가 순환하지 않는지 확인한다. 특히 `combat.js`와 scene feature module이 서로 import하지 않게 한다.
|
|
|
|
## 리팩터링 중 주의점
|
|
|
|
- `presentationMode` 전투는 실제 매치와 다르다. 사망 통계 fetch/persist, battle notice, pause, 승리 연출 오디오 priming의 조건을 바꾸지 않는다.
|
|
- `matchId`는 비동기 death stats fetch와 전투 delayed action의 stale result를 막는 기준이다. 초기화 순서를 흐트러뜨리지 않는다.
|
|
- 새 매치 시작 시 battle notice timer, slow-motion timer/animation frame, combat object, selected fighter 상태가 정리되어야 한다.
|
|
- 최종교전 슬로우모션은 Phaser timer, tween, animation, Arcade Physics를 함께 건드린다. `arcadePhysicsTimeScale()`의 역수 규칙을 잃지 않는다.
|
|
- 미니맵 viewport frame은 main camera에서 ignore되고 zoom 상태에 따라 alpha/visibility가 바뀐다.
|
|
- `setStatus()`는 단순 상태 전달만 하지 않는다. 기존 승리/무승부 message에 맞춰 celebration overlay를 제거/생성한다.
|
|
- scoreboard badge 클릭은 `selectRandomTeamFighter()`로 이어지고 선택 fighter는 spectator 관전보다 우선한다.
|
|
- Slime `spawnMultiplier`와 `splitOnDeath`는 team size, scoreboard, finish 판정에 영향을 준다.
|
|
|
|
## 완료 조건
|
|
|
|
- `ArenaScene.js`가 기능별 모듈을 호출하는 orchestration 파일로 줄어든다.
|
|
- `main.js`와 `combat.js`의 기존 호출 계약이 깨지지 않는다.
|
|
- 실제 매치와 presentation 매치 흐름이 기존처럼 구분된다.
|
|
- 신규 모듈의 위치가 게임 로직(`src/game`)과 DOM UI(`src/ui`) 책임을 반영한다.
|
|
- `npm run build`가 통과한다.
|
|
- 관련 문서가 실제 구조와 맞게 갱신된다.
|
|
|
|
## 수동 smoke test
|
|
|
|
최소한 아래 흐름을 확인한다.
|
|
|
|
1. 앱 최초 로드 시 presentation 전투가 시작되고 실제 매치 입력 UI가 정상 동작한다.
|
|
2. 실제 매치를 시작하면 팀 badge와 전투원이 표시되고 restart가 새 매치를 시작한다.
|
|
3. pause/continue가 physics, timer, tween, sprite animation을 같이 멈추고 재개한다.
|
|
4. 마우스 wheel zoom, spectator follow, fighter 클릭 focus, team badge 클릭 focus, minimap viewport frame이 동작한다.
|
|
5. 처치 발생 시 kill log와 scoreboard 생존 수가 갱신된다.
|
|
6. Slime이 배정된 매치에서 spawn multiplier와 split-on-death가 깨지지 않는다.
|
|
7. 실제 매치가 끝나면 승리/무승부 상태와 victory celebration 흐름이 정상이다.
|
|
8. 실제 매치가 충분히 지속되면 battle notice 흐름이 동작한다. death stats API가 실패하더라도 scene이 멈추지 않고 warning 수준으로 끝난다.
|
|
|
|
## 산출물 기대치
|
|
|
|
- 기능별 신규 모듈
|
|
- 얇아진 `src/game/ArenaScene.js`
|
|
- 필요 시 갱신된 `CONTEXT.md`, `agent.md`, `todo.md`
|
|
- 검증 결과 요약
|