Files
maplecontest/docs/superpowers/specs/2026-06-15-node-map-ui-design.md

97 lines
6.7 KiB
Markdown
Raw Permalink 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.
# 노드 맵 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 = "<ruid>"`(`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` | 재생성 산출물 |