Files
maplecontest/docs/superpowers/specs/2026-06-16-charselect-images-back-design.md
gahusb 2e8a1ab869 docs(spec): 직업 선택 캐릭터 이미지 + 뒤로가기 설계
CharacterSelectHud 단색 박스 → 캐릭터 이미지 카드(이름 하단 배너·선택 금색
테두리), 뒤로가기→로비. data/characters.json 단일 소스(메이커 임포트 RUID).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 00:59:43 +09:00

106 lines
8.0 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.
# 직업 선택 — 캐릭터 이미지 + 뒤로가기 설계
작성일: 2026-06-16
브랜치: `feature/charselect-images`
## 목표
런 시작 시 띄우는 **캐릭터(직업) 선택 화면**(`CharacterSelectHud`)을 두 가지로 개선한다:
1. 직업 3종(전사/도적/마법사)을 지금의 **단색 네모 박스** → **각 직업 캐릭터 이미지 카드**로. 이미지를 선택하면 그 직업으로 런 진행(기존 연결 유지).
2. 직업 선택 화면에 **뒤로가기** 버튼 추가 → 로비로 복귀.
요청 원문: "런 시작 시, 직업 선택 창을 뒤로가기도 가능하게 추가. 각 직업별로 지금은 네모 박스인데 각각 이미지(warrior/mage/bandit.png) 추가해서 적용하고, 선택했을 때 그 캐릭터로 진행하도록 연결."
## 확정된 결정 (브레인스토밍)
| 항목 | 결정 |
|---|---|
| 이미지 RUID 확보 | **사용자가 메이커에서 3 PNG 로컬 임포트**`.sprite`+RUID(P13 카드프레임과 동일). MCP/계정 업로드는 흰박스라 불가 |
| 이미지 배치 | **카드 전체를 이미지로**, 이름은 하단 배너, 선택 시 **금색 테두리** |
| 뒤로가기 대상 | **로비로** (`ShowLobby()`) — 로비 NPC에서 진입하므로 |
### 소스 이미지 (사용자 임포트 대상)
- 전사: `C:\Users\jaeoh\Desktop\workspace\source\images\maple\character\warrior.png` (~1.05MB)
- 법사: `…\mage.png` (~1.28MB)
- 도적: `…\bandit.png` (~1.0MB)
세 PNG는 현재 워크스페이스 미임포트(코드 미참조). 기존 `RootDesk/MyDesk/*_normal|unique|legend.sprite`는 P13 **카드 프레임**이지 캐릭터 초상화가 아니다.
## 현재 구조 (조사 결과)
- **CLASSES** 상수 `gen-slaydeck.mjs:7-11``warrior{label,maxHp}`, `bandit`, `magician`.
- **CharacterSelectHud emit** `:2432-2598`. `classCards` 배열 `:2482-2486` (key Warrior/Thief/Mage, classId warrior/bandit/magician, x 360/0/360, tint). 각 카드(270×330)의 자식: `Name`(상단, 108), `Portrait`(142×142 색상 tint, `:2524-2525` 부근), `Desc`(하단 105), `LockBody`/`LockShackle`(비활성 직업용). 별도 `…DeckButton`(덱 보기)·`StartButton`.
- **선택 로직**: 클릭 바인딩 `BindMenuButtons``:3100/3108/3116``SelectClass(classId)` `:3358-3361`(=`self.SelectedClass=…`+`RenderCharacterSelect()`). 시작 `:3151-3157``StartNewGame` `:3395-3399`(미선택 가드 후 `StartRun()`).
- **RenderCharacterSelect** `:3362-3394` — 선택 카드 밝게/미선택 어둡게 + Status 텍스트.
- **진입/전환**: `ShowState` `:3062-3078`가 HUD 토글. 진입 = 로비 NPC `OnLobbyNpcInteract` `:3199-3203`(런 비활성 시 `ShowCharacterSelect()` `:3355-3357`) 및 (사실상 미사용) MainMenu `:3092`. `ShowLobby` `:3175`. 게임은 OnBeginPlay→`ShowLobby`로 부팅(로비 허브).
- **emit 헬퍼**: `entity():466`, `transform():286`, `sprite():311`(`dataId`로 ImageRUID 주입 가능), `button():347`, `text():372`, `guid()`.
- **이미지 외부화 패턴**: 카드프레임은 `data/cardframes.json``luaFramesTable()`(`:72` 부근) → `self.CardFrames` Lua 테이블 + 런타임 `ApplyCardFace` `:4167-4202``e.SpriteGUIRendererComponent.ImageRUID=ruid` 주입. 생성 시 주입은 `sprite({dataId})`.
## 상세 설계
### 1) `data/characters.json` (신설 — 단일 소스)
```json
{
"portraits": {
"warrior": "<32hex RUID>",
"magician": "<32hex RUID>",
"bandit": "<32hex RUID>"
}
}
```
- 사용자 임포트 후 `RootDesk/MyDesk/*.sprite`에서 RUID를 읽어 채운다(파일명은 임포트 시 결정 — `warrior.sprite` 등으로 매칭, 모호하면 사용자 확인).
- 나중에 이미지 교체 = 이 파일 RUID만 바꿔 재생성.
### 2) `gen-slaydeck.mjs` — 로드·검증·주입
- 상단에서 `const CHARS = JSON.parse(readFileSync('data/characters.json','utf8'))` 로드(cardframes 로드 패턴 인접).
- **fail-fast 검증**: `portraits``warrior`/`magician`/`bandit` 3키 존재 + 각 값이 32hex. 누락 시 throw.
- 카드 Art 이미지는 **생성 시 `dataId` 주입**(런타임 테이블 불필요). 즉 `classCards`의 classId로 `CHARS.portraits[classId]`를 조회해 Art 스프라이트 `dataId`에 박는다.
### 3) CharacterSelectHud — 카드 전체 이미지화 (`:2432-2598`, `classCards` emit 루프)
각 직업 카드 구조를 다음으로 변경(엔티티 경로 `…/{key}Button`·클릭 바인딩은 **불변**):
- `{key}Button`(270×330): 클릭 가능한 **테두리 프레임**. sprite Color = 미선택 어둡게(`0.16,0.2,0.26,1`)/선택 금색(`1,0.82,0.3,1`). raycast on, `button()` 유지.
- 신규 자식 `Art`(약 258×318, 6px 인셋, center): `sprite({ dataId: CHARS.portraits[classId], type:1, raycast:false })` — 캐릭터 이미지 풀블리드. (테두리가 이미지 뒤로 6px 보임 → 금색 테두리 효과.)
- `Name`(하단 배너): 반투명 어두운 띠 sprite(예: `0,0,0,0.55`, 270×54, 하단) + 금색 텍스트. 기존 `Name` 재배치.
- **제거**: 기존 색상 `Portrait` 박스, `Desc` 텍스트(선택 레이아웃에 없음).
- `LockBody`/`LockShackle`: 비활성 직업용으로 유지(현재 3직업 모두 enabled라 표시 안 됨).
### 4) `RenderCharacterSelect` Lua 변경 (`:3362-3394`)
- 기존 "박스 밝게/어둡게"를 **테두리(=`{key}Button` sprite Color) 금색/어둡게**로 교체. 선택된 classId의 카드만 `Color(1,0.82,0.3,1)`, 나머지 `Color(0.16,0.2,0.26,1)`.
- Art 이미지는 생성 시 고정 주입이라 런타임 변경 없음. Status 텍스트 로직은 유지.
### 5) 뒤로가기 버튼
- 신규 `CharacterSelectHud/BackButton`(ShopHud `Leave` 패턴 재사용 `:2020-2031`): 좌상단(예: `pos {x:-820,y:430}`, 180×56), text "← 뒤로", DARK sprite + `button()`.
- `BindMenuButtons`에 바인딩 추가(ShopHud Leave 바인딩 패턴 `:3715-3717`): `back:ConnectEvent(ButtonClickEvent, function() self:ShowLobby() end)`. 핸들러 prop 저장(재바인딩 시 해제).
- `ShowCharacterSelect`/`SelectClass`/`StartNewGame`/`StartRun` 로직 불변.
### 6) GUID 네임스페이스
- 신규 엔티티(Art·NameBanner·BackButton)는 CharacterSelect용 기존 prefix에 번호 추가. 미등록 prefix면 ns 바이트 등록(생성기 끝 id 유일성 검증이 충돌 잡음).
## 흐름
```
로비(맵) ──NPC 상호작용──> ShowCharacterSelect (HUD 오버레이)
카드3=캐릭터 이미지, 클릭 → SelectClass → 금색 테두리
[시작] → StartNewGame(가드) → StartRun (그 직업으로)
[← 뒤로] → ShowLobby() → 로비 HUD 복귀
```
## 미러/테스트 영향
- 전투규칙·맵생성 **미변경**`sim-balance`/`rogue-map` 미러 동기화 불필요.
- 카운트 검증: `CharacterSelectHud/.../Art` ImageRUID 3개, `BackButton` 1개, characters.json 3 RUID 등장(`tools/verify/count.mjs` 또는 `grep -c`).
- 메이커 플레이테스트: 로비 NPC→3 이미지 표시→클릭 금색 테두리→시작 그 직업으로 진행→뒤로 로비 복귀.
## 리스크
- **이미지 임포트 선행 의존**: RUID가 있어야 생성기 실행 가능. 사용자 임포트 완료 후 진행(임포트 무관한 코드 골격은 먼저 작성 가능).
- **이미지 비율**: PNG가 세로 초상화면 258×318(≈0.81 비율)에서 잘리거나 여백 — 임포트 후 스크린샷으로 인셋/사이즈 조정.
- **`ShowLobby()` 재텔레포트**: 이미 로비 맵 위라 `GoLobbyMap` 재호출 시 위치/카메라 jolt 가능 → 보이면 뒤로가기를 `ShowState("lobby")`로 축소(플레이테스트 확인).
- 흰박스: 공식 절차(로컬 임포트)면 렌더됨. reload 필수.
## 변경 파일 요약
| 파일 | 변경 |
|---|---|
| `data/characters.json` | **신설** — 직업 3종 초상화 RUID(단일 소스) |
| `tools/deck/gen-slaydeck.mjs` | characters.json 로드·검증, CharacterSelectHud 카드 이미지화(Art/NameBanner), RenderCharacterSelect 테두리 선택표시, BackButton emit+바인딩 |
| `RootDesk/MyDesk/*.sprite` (×3) | 사용자 임포트 산출물(커밋) |
| `ui/DefaultGroup.ui`·`SlayDeckController.codeblock` | 재생성 산출물 |