From cc945fce8bf852d14d02de02573c45d68e201726 Mon Sep 17 00:00:00 2001 From: gahusb Date: Mon, 15 Jun 2026 14:17:01 +0900 Subject: [PATCH] =?UTF-8?q?docs(node-map):=20=EB=85=B8=EB=93=9C=20?= =?UTF-8?q?=EB=A7=B5=20UI=20=EA=B0=95=ED=99=94=20=EC=84=A4=EA=B3=84=20spec?= =?UTF-8?q?=20(=EC=95=84=EC=9D=B4=EC=BD=98=20=EB=85=B8=EB=93=9C+=EB=B0=B0?= =?UTF-8?q?=EA=B2=BD,=20nodeicons.json=20=EC=99=B8=EB=B6=80=ED=99=94)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.8 (1M context) --- .../specs/2026-06-15-node-map-ui-design.md | 96 +++++++++++++++++++ 1 file changed, 96 insertions(+) create mode 100644 docs/superpowers/specs/2026-06-15-node-map-ui-design.md diff --git a/docs/superpowers/specs/2026-06-15-node-map-ui-design.md b/docs/superpowers/specs/2026-06-15-node-map-ui-design.md new file mode 100644 index 0000000..082536a --- /dev/null +++ b/docs/superpowers/specs/2026-06-15-node-map-ui-design.md @@ -0,0 +1,96 @@ +# 노드 맵 UI 강화 설계 + +작성일: 2026-06-15 +브랜치: `feature/node-map-ui` + +## 목표 + +맵 노드 선택 화면(`MapHud`)을 **단색 박스+텍스트** → **공식 메이플 아이콘 노드 + 배경 이미지**로 강화한다. +절차 랜덤 배치·간선·진행 로직은 그대로. 아이콘/배경은 **`data/nodeicons.json` 한 파일로 외부화**해 나중에 RUID만 바꿔 재생성하면 교체되도록 한다. + +요청 원문: "노드 창이 단순 네모 박스안에 텍스트 … 백그라운드 이미지 삽입하고 특정 아이콘을 지정해서 노드로 … 랜덤 배치 … 노드 맵 UI 강화. 내가 나중에 변경할 수도 있으니 변경이 쉽게 가능하도록." + +## 확정된 결정 (브레인스토밍) + +| 항목 | 결정 | +|---|---| +| 노드 표현 | **아이콘만**(박스 제거). 상태는 아이콘 틴트로 | +| 배경 | **공식 메이플 배경 이미지** + 반투명 어두운 오버레이 | +| 아이콘 세트 | 사용자 확정(아래 표). 공식 maplestory RUID, 썸네일 검수 완료 | +| 변경 용이성 | 모든 RUID를 `data/nodeicons.json`로 외부화 → 편집+재생성으로 교체 | + +### 확정 아이콘/배경 (공식 maplestory, 흰박스 위험 없음) + +| 노드 타입 | 아이콘 | RUID | +|---|---|---| +| combat(전투) | 주황버섯 | `f98db6823e894a4f90308d61f75894ac` | +| elite(엘리트) | 돌골렘(Stumpy) | `793ed8a757534b89a82f460747d2df24` | +| boss(보스) | 주니어 발록 | `423056cdbbc04f4da131b9721c404d96` | +| shop(상점) | 보라 돈주머니 | `da37e1fac55d455b9ade08569f09f798` | +| rest(휴식) | 모닥불 | `b86c1b0568bd45f3ae4a4b97e1b4a594` | +| treasure(보물) | 금별 보물상자 | `f8a6d58e20f54e2ca899485055df1ce4` | +| **background** | 리스항구 | `d84241f17de344a097f5b96ac914f1d2` | + +## 현재 구조 (조사 결과) + +- `MapHud` 루트 = 1920×1080 **단색** 패널(`gen-slaydeck.mjs:1664`, 배경 이미지 없음) + 타이틀. +- 노드 = `pushMapNode(id,pos,size,label)`(`:1696`) — `Node_{id}` 단색 박스(56×56, 보스 72×72) + `Label` 텍스트 자식. 그리드 `r1c1~r6c4`(24) + `boss`(`:1727`). +- 타입 6종: combat/elite/shop/rest/treasure/boss. 타입→색/라벨은 **Lua `RenderMapNode`**(`:5626~5677`)가 런타임에 박스 `Color` + Label 텍스트로 채움. 상태 4단(현재 금색/방문 회색/도달 타입색/잠김 어둡게). +- 절차 생성 `GenerateMap`(`:5505`) → `self.MapNodes[id]={type,row,col,next}`, id `r{r}c{c}`가 UI 엔티티와 1:1. 버튼 바인딩(`:3597`)은 경로 기반. +- 이미지 주입 패턴: emit `sprite({dataId: RUID, type:0})`(`sprite()` 헬퍼 `:297`) / 런타임 `e.SpriteGUIRendererComponent.ImageRUID = ""`(`ApplyCardFace :4089`, chest `:5874`). 카드 프레임은 `data/cardframes.json`→`luaFramesTable()`(`:72`)→`self.CardFrames` Lua 테이블. + +## 상세 설계 + +### 1) `data/nodeicons.json` (신설 — 단일 소스) +```json +{ + "icons": { + "combat": "f98db6823e894a4f90308d61f75894ac", + "elite": "793ed8a757534b89a82f460747d2df24", + "boss": "423056cdbbc04f4da131b9721c404d96", + "shop": "da37e1fac55d455b9ade08569f09f798", + "rest": "b86c1b0568bd45f3ae4a4b97e1b4a594", + "treasure": "f8a6d58e20f54e2ca899485055df1ce4" + }, + "background": "d84241f17de344a097f5b96ac914f1d2" +} +``` +- 사용자가 나중에 RUID만 바꾸고 `node tools/deck/gen-slaydeck.mjs` 재실행하면 교체됨. (README/주석에 명시.) + +### 2) `gen-slaydeck.mjs` — 로드·검증·직렬화 +- 상단에서 `NODEICONS = JSON.parse(readFileSync('data/nodeicons.json'))` 로드. +- **fail-fast 검증**: `icons`에 6타입(combat/elite/boss/shop/rest/treasure) 전부 존재 + 32hex RUID, `background` 존재. 누락 시 throw(카드프레임 검증과 동일 패턴). +- `luaNodeIconsTable()` 헬퍼: `self.NodeIcons = { combat="...", ... }` Lua 테이블 문자열. OnBeginPlay init에 주입(CardFrames 패턴, `:2906/3361` 인접). `prop('any','NodeIcons')` 선언. + +### 3) MapHud emit 변경 +- **배경 자식 `MapHud/Bg`**: 루트 직후 push. `uisprite`, 1920×1080, `dataId = NODEICONS.background`, `type:0`, 흰색, `raycast:false`, displayOrder 최하(0). 항상 enable. +- **루트 오버레이**: 기존 루트 단색을 **반투명 어두운 오버레이**로(예: `{r:0.04,g:0.05,b:0.08,a:0.55}`)— 배경이 비치되 노드 가독성 확보. raycast 유지(뒤 월드 클릭 차단). +- **`pushMapNode` → 아이콘 노드**: `Node_{id}` 본체를 박스 대신 **아이콘 스프라이트**로 — `sprite({ color:{1,1,1,1}, type:0, raycast:true })`(emit 시 dataId 미지정, 런타임에 타입별 ImageRUID 주입) + `button()`. **`Label` 자식 제거**(아이콘만). 노드 크기 키움: 그리드 64×64, 보스 88×88. (좌표 헬퍼 `nodeX/nodeY`·그리드 생성 루프·버튼 바인딩은 불변.) + +### 4) RenderMapNode Lua 변경 +- 타입→박스색/라벨 매핑(`:5630~5656`) 제거. 대신: + - `e.SpriteGUIRendererComponent.ImageRUID = self.NodeIcons[type]` (없으면 combat 폴백). + - 상태별 `Color` 틴트(박스가 아니라 **아이콘**에): + - 현재(`CurrentNodeId`): `Color(1, 0.82, 0.3, 1)` 금색 + - 도달가능: `Color(1, 1, 1, 1)` 원색 + `ButtonComponent.Enable=true` + - 방문: `Color(0.5, 0.5, 0.55, 0.9)` 회색 + - 잠김: `Color(0.4, 0.4, 0.45, 0.45)` 어둡고 반투명 + 버튼 비활성 +- `SetText(.../Label ...)` 호출 제거(라벨 없음). 간선 도트(`RenderMapDots`)·`RenderMap` 루프는 불변. + +### 5) 미러/테스트 영향 +- 전투 규칙·맵 그래프 알고리즘 **미변경** → `sim-balance`/`rogue-map` 미러 동기화 불필요. +- 검증(카운트): `MapHud/Bg` 1개, `NodeIcons` 주입, 노드 ImageRUID 주입 코드 존재, 6 RUID 등장. 내용 출력 금지(`tools/verify/count.mjs`). +- 동작: 메이커 플레이테스트(아이콘 렌더·상태 틴트·랜덤 배치·노드 클릭 진행). + +## 리스크 +- **아이콘 비정사각/큰 스프라이트** → 64px UI에서 잘림/왜곡 가능(보스 발록은 확인됨, 골렘·버섯은 정사각 양호). type 0 렌더의 aspect 처리 확인, 필요 시 노드별 size 패딩 조정. +- **아이콘만 상태 가독성**: 잠김/방문 틴트 대비가 약하면 플레이테스트로 알파/명도 튜닝. +- **배경 오버레이 알파**: 너무 밝으면 노드가 묻힘 — 0.5~0.65 사이 튜닝. +- 흰박스: 전부 공식 maplestory(검증) — 위험 없음. 단 로컬 워크스페이스 reload 필요. + +## 변경 파일 요약 +| 파일 | 변경 | +|---|---| +| `data/nodeicons.json` | **신설** — 아이콘 6 + 배경 RUID (단일 소스) | +| `tools/deck/gen-slaydeck.mjs` | 로드·검증·luaNodeIconsTable, MapHud Bg/오버레이, pushMapNode 아이콘화, RenderMapNode ImageRUID+틴트 | +| `ui/DefaultGroup.ui`·`SlayDeckController.codeblock` | 재생성 산출물 |