From 00903f265972719867a0b8cc25e41308b8b56b58 Mon Sep 17 00:00:00 2001 From: gahusb Date: Tue, 16 Jun 2026 01:16:00 +0900 Subject: [PATCH] =?UTF-8?q?feat(charselect):=20=EC=A7=81=EC=97=85=20?= =?UTF-8?q?=EC=B9=B4=EB=93=9C=20=EC=BA=90=EB=A6=AD=ED=84=B0=20=EC=9D=B4?= =?UTF-8?q?=EB=AF=B8=EC=A7=80=20+=20=EB=92=A4=EB=A1=9C=EA=B0=80=EA=B8=B0?= =?UTF-8?q?=20(=EC=86=8C=EC=8A=A4)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - data/characters.json 신설(전사/법사/도적 초상화 RUID 단일 소스), 생성기 로드·검증 - CharacterSelectHud: 단색 박스 → 카드 전체 캐릭터 이미지(Art 풀블리드 258×318) + 하단 이름 배너(NameBanner), Portrait/Desc 제거 - RenderCharacterSelect: 선택 시 카드 테두리 금색(Art 6px 인셋 뒤로) - BackButton 추가 + BindMenuButtons 바인딩 → ShowLobby(로비 복귀), prop CharBackHandler Co-Authored-By: Claude Opus 4.8 (1M context) --- data/characters.json | 7 ++++ tools/deck/gen-slaydeck.mjs | 66 ++++++++++++++++++++++++++----------- 2 files changed, 54 insertions(+), 19 deletions(-) create mode 100644 data/characters.json diff --git a/data/characters.json b/data/characters.json new file mode 100644 index 0000000..9112bbe --- /dev/null +++ b/data/characters.json @@ -0,0 +1,7 @@ +{ + "portraits": { + "warrior": "28c88fdc5ab44f34a8b3fc1e19d4ce78", + "magician": "3b9ea1f066a744bb859df47fef817277", + "bandit": "efa920e58d31426486ef974106e7dc8b" + } +} diff --git a/tools/deck/gen-slaydeck.mjs b/tools/deck/gen-slaydeck.mjs index 5cc9f02..af88f68 100644 --- a/tools/deck/gen-slaydeck.mjs +++ b/tools/deck/gen-slaydeck.mjs @@ -95,6 +95,12 @@ for (const t of ['combat', 'elite', 'boss', 'shop', 'rest', 'treasure']) { } if (!/^[0-9a-f]{32}$/.test(NODEICONS.background || '')) throw new Error('[gen-slaydeck] nodeicons.json background RUID 누락/형식오류'); +// 캐릭터 선택 초상화 (메이커 임포트 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 누락/형식오류`); +} + // 전투 카메라 고정값(StS2: 플레이어 좌·몬스터 우). KickCombatCamera가 StartCombat에서 재confine에 사용. const CAM = JSON.parse(readFileSync('data/camera.json', 'utf8')); @@ -2501,41 +2507,40 @@ function upsertUi() { ], })); select.push(entity({ - id: guid('menu', 120 + i), - path: `${base}/Name`, - modelId: 'uitext', - entryId: 'UIText', - componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.TextComponent', + 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: 230, y: 54 }, pos: { x: 0, y: 108 } }), - sprite({ color: TRANSPARENT }), - text({ value: cls.label, fontSize: 34, bold: true, color: cls.enabled ? GOLD : { r: 0.55, g: 0.58, b: 0.62, a: 1 }, alignment: 4 }), + 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 }), ], })); select.push(entity({ - id: guid('menu', 130 + i), - path: `${base}/Portrait`, + 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: 142, y: 142 }, pos: { x: 0, y: 8 } }), - sprite({ color: cls.tint, type: 1 }), + 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 }), ], })); select.push(entity({ - id: guid('menu', 140 + i), - path: `${base}/Desc`, + id: guid('menu', 120 + i), + path: `${base}/Name`, modelId: 'uitext', entryId: 'UIText', componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.TextComponent', displayOrder: 2, components: [ - transform({ parentW: 270, parentH: 330, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 230, y: 50 }, pos: { x: 0, y: -105 } }), + transform({ parentW: 270, parentH: 330, anchor: { x: 0.5, y: 0.5 }, pivot: { x: 0.5, y: 0.5 }, size: { x: 230, y: 54 }, pos: { x: 0, y: -137 } }), sprite({ color: TRANSPARENT }), - text({ value: cls.desc, fontSize: 20, color: cls.enabled ? { r: 0.86, g: 0.9, b: 0.94, a: 1 } : { r: 0.52, g: 0.55, b: 0.59, a: 1 }, alignment: 4 }), + text({ value: cls.label, fontSize: 34, bold: true, color: cls.enabled ? GOLD : { r: 0.55, g: 0.58, b: 0.62, a: 1 }, alignment: 4 }), ], })); if (!cls.enabled) { @@ -2593,6 +2598,20 @@ function upsertUi() { text({ value: '\uC2DC\uC791', fontSize: 30, bold: true, color: GOLD, alignment: 0 }), ], })); + 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: '\u2190 \uB4A4\uB85C', fontSize: 26, bold: true, color: GOLD, alignment: 0 }), + ], + })); select[0].jsonString.enable = false; emit('MainMenu', menu); emit('CharacterSelectHud', select); @@ -2901,6 +2920,7 @@ function writeCodeblocks() { prop('number', 'AscensionLevel', '0'), prop('number', 'AscensionUnlocked', '0'), prop('any', 'StartGameHandler'), + prop('any', 'CharBackHandler'), prop('string', 'SelectedClass', '""'), prop('any', 'DrawPileHandler'), prop('any', 'DiscardPileHandler'), @@ -3156,6 +3176,14 @@ if start ~= nil and start.ButtonComponent ~= nil then end self.StartGameHandler = start:ConnectEvent(ButtonClickEvent, function() self:StartNewGame() end) end +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 local ascMinus = _EntityService:GetEntityByPath("/ui/DefaultGroup/MainMenu/AscMinus") if ascMinus ~= nil and ascMinus.ButtonComponent ~= nil then if self.AscMinusHandler ~= nil then @@ -3362,7 +3390,7 @@ self:RenderCharacterSelect()`, [ method('RenderCharacterSelect', `local warrior = _EntityService:GetEntityByPath("/ui/DefaultGroup/CharacterSelectHud/WarriorButton") if warrior ~= nil and warrior.SpriteGUIRendererComponent ~= nil then if self.SelectedClass == "warrior" then - warrior.SpriteGUIRendererComponent.Color = Color(0.28, 0.36, 0.46, 1) + warrior.SpriteGUIRendererComponent.Color = Color(1, 0.82, 0.3, 1) else warrior.SpriteGUIRendererComponent.Color = Color(0.16, 0.2, 0.26, 1) end @@ -3370,7 +3398,7 @@ end local mage = _EntityService:GetEntityByPath("/ui/DefaultGroup/CharacterSelectHud/MageButton") if mage ~= nil and mage.SpriteGUIRendererComponent ~= nil then if self.SelectedClass == "magician" then - mage.SpriteGUIRendererComponent.Color = Color(0.28, 0.36, 0.46, 1) + mage.SpriteGUIRendererComponent.Color = Color(1, 0.82, 0.3, 1) else mage.SpriteGUIRendererComponent.Color = Color(0.16, 0.2, 0.26, 1) end @@ -3378,7 +3406,7 @@ end local thief = _EntityService:GetEntityByPath("/ui/DefaultGroup/CharacterSelectHud/ThiefButton") if thief ~= nil and thief.SpriteGUIRendererComponent ~= nil then if self.SelectedClass == "bandit" then - thief.SpriteGUIRendererComponent.Color = Color(0.28, 0.36, 0.46, 1) + thief.SpriteGUIRendererComponent.Color = Color(1, 0.82, 0.3, 1) else thief.SpriteGUIRendererComponent.Color = Color(0.16, 0.2, 0.26, 1) end