From f2c470f9727e41ecfd2ccdac28ce86365082dd08 Mon Sep 17 00:00:00 2001 From: gahusb Date: Tue, 16 Jun 2026 01:08:00 +0900 Subject: [PATCH] =?UTF-8?q?docs(plan):=20=EC=A7=81=EC=97=85=20=EC=84=A0?= =?UTF-8?q?=ED=83=9D=20=EC=BA=90=EB=A6=AD=ED=84=B0=20=EC=9D=B4=EB=AF=B8?= =?UTF-8?q?=EC=A7=80=20+=20=EB=92=A4=EB=A1=9C=EA=B0=80=EA=B8=B0=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84=20=EA=B3=84=ED=9A=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../2026-06-16-charselect-images-back.md | 205 ++++++++++++++++++ 1 file changed, 205 insertions(+) create mode 100644 docs/superpowers/plans/2026-06-16-charselect-images-back.md diff --git a/docs/superpowers/plans/2026-06-16-charselect-images-back.md b/docs/superpowers/plans/2026-06-16-charselect-images-back.md new file mode 100644 index 0000000..21a7df6 --- /dev/null +++ b/docs/superpowers/plans/2026-06-16-charselect-images-back.md @@ -0,0 +1,205 @@ +# 직업 선택 캐릭터 이미지 + 뒤로가기 — 구현 계획 + +> **For agentic workers:** REQUIRED SUB-SKILL: superpowers:executing-plans 로 태스크 단위 구현. 단계는 `- [ ]` 체크박스. + +**Goal:** CharacterSelectHud의 단색 박스를 캐릭터 이미지 카드로 바꾸고(선택 시 금색 테두리), 뒤로가기 버튼으로 로비 복귀를 추가한다. + +**Architecture:** 단일 생성기 `tools/deck/gen-slaydeck.mjs` 수정 + `data/characters.json` 신설(초상화 RUID 단일 소스). 이미지는 생성 시 `sprite({dataId})`로 주입, 선택 표시는 기존 `RenderCharacterSelect`의 Button Color를 금색으로. 뒤로가기는 ShopHud 나가기 패턴 재사용 → `ShowLobby()`. 산출물(ui/codeblock) 재생성. + +**Tech Stack:** Node ESM 생성기, MSW Lua codeblock, MSW UI JSON. 검증=카운트+메이커 플레이테스트(이 저장소는 단위테스트 대신 카운트/플레이테스트). + +**확정 RUID (메이커 임포트 완료, `.sprite`에서 추출):** +- warrior `28c88fdc5ab44f34a8b3fc1e19d4ce78` +- magician `3b9ea1f066a744bb859df47fef817277` +- bandit `efa920e58d31426486ef974106e7dc8b` + +--- + +### Task 1: `data/characters.json` + 생성기 로드·검증 + +**Files:** +- Create: `data/characters.json` +- Modify: `tools/deck/gen-slaydeck.mjs:91-96` 인접(NODEICONS 로드 블록 뒤) + +- [ ] **Step 1:** `data/characters.json` 작성 +```json +{ + "portraits": { + "warrior": "28c88fdc5ab44f34a8b3fc1e19d4ce78", + "magician": "3b9ea1f066a744bb859df47fef817277", + "bandit": "efa920e58d31426486ef974106e7dc8b" + } +} +``` + +- [ ] **Step 2:** gen-slaydeck.mjs NODEICONS 검증 블록(`:96`) 바로 뒤에 로드+fail-fast 검증 추가 +```js +// 캐릭터 선택 초상화 (메이커 임포트 RUID, data/characters.json 단일 소스 — 교체 시 이 파일만 수정 후 재생성) +const CHARS = JSON.parse(readFileSync('data/characters.json', 'utf8')); +for (const c of ['warrior', 'magician', 'bandit']) { + if (!/^[0-9a-f]{32}$/.test((CHARS.portraits || {})[c] || '')) throw new Error(`[gen-slaydeck] characters.json portraits.${c} RUID 누락/형식오류`); +} +``` + +- [ ] **Step 3:** 생성기 실행해 에러 없는지 확인(아직 UI 미사용이라 출력 동일) +``` +node tools/deck/gen-slaydeck.mjs +``` +Expected: 성공 메시지 1줄, throw 없음. + +--- + +### Task 2: CharacterSelectHud — 카드 이미지화 (classCards 루프) + +**Files:** Modify `tools/deck/gen-slaydeck.mjs:2516-2540` (Portrait/Desc 블록), `:2503-2515` (Name) + +카드 본체 `{key}Button`(2490-2502)·DeckButton(2567-2580)·StartButton·click 바인딩 경로는 **불변**. `cls.tint`/`cls.desc`는 더는 안 쓰이나 배열 정의는 그대로 둬도 무방. + +- [ ] **Step 1:** `Name`(2503-2515) 위치를 하단으로 — `transform`의 `pos: { x: 0, y: 108 }` → `pos: { x: 0, y: -137 }`. (displayOrder 0 유지) — 텍스트는 그대로(금색). + +- [ ] **Step 2:** `Portrait` 엔티티(2516-2527)를 **`Art` 이미지로 교체**. 경로·guid·sprite 변경: +```js + select.push(entity({ + id: guid('menu', 200 + i), + path: `${base}/Art`, + modelId: 'uisprite', + entryId: 'UISprite', + componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent', + displayOrder: 0, + components: [ + transform({ parentW: 270, parentH: 330, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 258, y: 318 }, pos: { x: 0, y: 0 } }), + sprite({ dataId: CHARS.portraits[cls.classId], color: { r: 1, g: 1, b: 1, a: 1 }, type: 0, raycast: false }), + ], + })); +``` +(258×318, 6px 인셋 → 부모 Button 색이 테두리로 보임. type:0=이미지 풀, raycast off=클릭은 부모 Button으로.) + +- [ ] **Step 3:** `Desc` 엔티티(2528-2540) **삭제**(emit 안 함). + +- [ ] **Step 4:** `Name` 뒤에 반투명 하단 배너 `NameBanner` 추가(displayOrder 1, Art 위·Name 아래). Name의 displayOrder를 2로 올림. +```js + select.push(entity({ + id: guid('menu', 210 + i), + path: `${base}/NameBanner`, + modelId: 'uisprite', + entryId: 'UISprite', + componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent', + displayOrder: 1, + components: [ + transform({ parentW: 270, parentH: 330, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 258, y: 60 }, pos: { x: 0, y: -137 } }), + sprite({ color: { r: 0, g: 0, b: 0, a: 0.55 }, type: 1, raycast: false }), + ], + })); +``` +그리고 Name 엔티티의 `displayOrder: 0` → `displayOrder: 2`로. + +- [ ] **Step 5:** 생성 + 카운트 검증 +``` +node tools/deck/gen-slaydeck.mjs +node tools/verify/count.mjs ui "CharacterSelectHud/WarriorButton/Art" "CharacterSelectHud/MageButton/Art" "CharacterSelectHud/ThiefButton/Art" +grep -c "28c88fdc5ab44f34a8b3fc1e19d4ce78" ui/DefaultGroup.ui # warrior RUID 1 +``` +Expected: Art 3개 존재, RUID 등장. (count.mjs 없으면 `grep -c '/Art"' ui/DefaultGroup.ui`.) + +--- + +### Task 3: RenderCharacterSelect — 선택 = 금색 테두리 + +**Files:** Modify `tools/deck/gen-slaydeck.mjs:3362-3394` + +- [ ] **Step 1:** 선택 시 색을 금색으로. 세 군데 `Color(0.28, 0.36, 0.46, 1)` → `Color(1, 0.82, 0.3, 1)` (미선택 `Color(0.16, 0.2, 0.26, 1)`는 유지). Status 텍스트 로직 불변. + - `gen-slaydeck.mjs`에서 `Color(0.28, 0.36, 0.46, 1)` 를 `Color(1, 0.82, 0.3, 1)` 로 (RenderCharacterSelect 내 3회) 치환. + +- [ ] **Step 2:** 생성 + 확인 +``` +node tools/deck/gen-slaydeck.mjs +grep -c "Color(1, 0.82, 0.3, 1)" RootDesk/MyDesk/SlayDeckController.codeblock # 증가 확인(기존 사용처 + 3) +``` + +--- + +### Task 4: 뒤로가기 버튼 + 바인딩 + +**Files:** Modify `tools/deck/gen-slaydeck.mjs` — CharacterSelectHud emit(StartButton 뒤 `:2595` 직후), BindMenuButtons(`:3158` 뒤), prop 선언부 + +- [ ] **Step 1:** StartButton emit(2582-2595) 직후에 BackButton emit 추가(StartButton 패턴 복제, 좌상단 배치) +```js + select.push(entity({ + id: guid('menu', 230), + path: '/ui/DefaultGroup/CharacterSelectHud/BackButton', + modelId: 'uibutton', + entryId: 'UIButton', + componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.ButtonComponent,MOD.Core.TextComponent', + displayOrder: 22, + components: [ + transform({ parentW: 1920, parentH: 1080, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 180, y: 56 }, pos: { x: -800, y: 430 }, align: ALIGN_CENTER }), + sprite({ color: { r: 0.15, g: 0.2, b: 0.26, a: 1 }, type: 1, raycast: true }), + button(), + text({ value: '← 뒤로', fontSize: 26, bold: true, color: GOLD, alignment: 0 }), + ], + })); +``` + +- [ ] **Step 2:** BindMenuButtons(StartGameHandler 블록 `:3151-3158` 뒤)에 BackButton 바인딩 추가 +```lua +local charBack = _EntityService:GetEntityByPath("/ui/DefaultGroup/CharacterSelectHud/BackButton") +if charBack ~= nil and charBack.ButtonComponent ~= nil then + if self.CharBackHandler ~= nil then + charBack:DisconnectEvent(ButtonClickEvent, self.CharBackHandler) + self.CharBackHandler = nil + end + self.CharBackHandler = charBack:ConnectEvent(ButtonClickEvent, function() self:ShowLobby() end) +end +``` +(이 Lua는 BindMenuButtons 메서드 본문 문자열 끝에 삽입. 실제 탭/`\t` 스타일은 해당 메서드 본문 규칙을 따른다 — BindMenuButtons는 실탭 사용.) + +- [ ] **Step 3:** prop `CharBackHandler` 선언 추가. 기존 핸들러 prop 목록(예: `StartGameHandler`/`NewGameHandler` 등 `prop('any','...')` 선언부)을 grep으로 찾아 같은 형식으로 `CharBackHandler` 추가. +``` +grep -n "StartGameHandler" tools/deck/gen-slaydeck.mjs # prop 선언 위치 확인 +``` + +- [ ] **Step 4:** 생성 + 검증 +``` +node tools/deck/gen-slaydeck.mjs +node tools/verify/count.mjs ui "CharacterSelectHud/BackButton" # 1 +grep -c "CharBackHandler" RootDesk/MyDesk/SlayDeckController.codeblock # ≥2 (선언+바인딩+해제) +``` + +--- + +### Task 5: 산출물 재생성 커밋 + .sprite 커밋 + 플레이테스트 + +**Files:** `ui/DefaultGroup.ui`, `RootDesk/MyDesk/SlayDeckController.codeblock`(재생성), `RootDesk/MyDesk/*.sprite`(임포트) + +- [ ] **Step 1:** 최종 재생성 + git status로 의도 외 변경 없는지 확인 +``` +node tools/deck/gen-slaydeck.mjs +git status --short +``` +Expected: 변경 = gen-slaydeck.mjs, data/characters.json, ui/DefaultGroup.ui, SlayDeckController.codeblock (+ common.gamelogic은 churn이면 내용 동일 시 git checkout 복원). untracked = 임포트 .sprite. + +- [ ] **Step 2:** 소스 커밋(생성기+데이터) → 산출물 커밋(재생성 명시) → .sprite 커밋 분리 +``` +git add tools/deck/gen-slaydeck.mjs data/characters.json +git commit -m "feat(charselect): 직업 카드 캐릭터 이미지 + 뒤로가기 (소스)" +git add ui/DefaultGroup.ui RootDesk/MyDesk/SlayDeckController.codeblock +git commit -m "chore: 산출물 재생성 (charselect 이미지+뒤로가기)" +git add "RootDesk/MyDesk/warrior.sprite" "RootDesk/MyDesk/mage.sprite" "RootDesk/MyDesk/bandit.sprite" +git commit -m "chore(assets): 캐릭터 초상화 스프라이트 임포트(전사/법사/도적)" +``` +(2차전직 아트 12종 .sprite는 별도 — 향후 2차 전직 선택 이미지용. 사용자 의사 확인 후 커밋/보류.) + +- [ ] **Step 3:** 메이커 플레이테스트(사용자 워크스페이스 reload 후): 로비 NPC→직업 선택 진입→3 카드에 캐릭터 이미지 표시→클릭 시 금색 테두리·Status 갱신→시작 시 그 직업으로 런→뒤로가기 시 로비 복귀. 빌드 콘솔 0 에러. + - 이미지 비율 왜곡/잘림 보이면 Art size(258×318) 조정. + - 뒤로가기 시 재텔레포트 jolt 보이면 BackButton 바인딩을 `self:ShowState("lobby")`로 축소. + +- [ ] **Step 4:** push + PR (`node tools/git/gitea-pr.mjs create `, UTF-8). + +--- + +## Self-Review + +- **스펙 커버리지**: 이미지 적용(T1,T2) · 선택→진행 연결(기존 SelectClass/StartNewGame 불변, T2가 클릭경로 보존) · 선택 금색 테두리(T3) · 뒤로가기→로비(T4) · characters.json 단일소스(T1) · 검증/플레이테스트(T5). 누락 없음. +- **플레이스홀더**: RUID·좌표·색·Lua 전부 구체값. count.mjs 부재 시 grep 대체 명시. +- **타입 일관성**: `CHARS.portraits[classId]`(classId=warrior/magician/bandit, classCards.classId와 일치). 핸들러 `CharBackHandler` 일관. Art/NameBanner guid(200+i/210+i/230) 미사용 번호. +- **리스크**: 이미지 비율(T5 Step3 조정), ShowLobby 재텔레포트(T5 Step3 폴백 ShowState), 메이커 reload 필수(산출물 디스크 반영).