feat(charselect): 직업 카드 캐릭터 이미지 + 뒤로가기 (소스)
- 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) <noreply@anthropic.com>
This commit is contained in:
7
data/characters.json
Normal file
7
data/characters.json
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"portraits": {
|
||||||
|
"warrior": "28c88fdc5ab44f34a8b3fc1e19d4ce78",
|
||||||
|
"magician": "3b9ea1f066a744bb859df47fef817277",
|
||||||
|
"bandit": "efa920e58d31426486ef974106e7dc8b"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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 누락/형식오류');
|
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에 사용.
|
// 전투 카메라 고정값(StS2: 플레이어 좌·몬스터 우). KickCombatCamera가 StartCombat에서 재confine에 사용.
|
||||||
const CAM = JSON.parse(readFileSync('data/camera.json', 'utf8'));
|
const CAM = JSON.parse(readFileSync('data/camera.json', 'utf8'));
|
||||||
|
|
||||||
@@ -2501,41 +2507,40 @@ function upsertUi() {
|
|||||||
],
|
],
|
||||||
}));
|
}));
|
||||||
select.push(entity({
|
select.push(entity({
|
||||||
id: guid('menu', 120 + i),
|
id: guid('menu', 200 + i),
|
||||||
path: `${base}/Name`,
|
path: `${base}/Art`,
|
||||||
modelId: 'uitext',
|
modelId: 'uisprite',
|
||||||
entryId: 'UIText',
|
entryId: 'UISprite',
|
||||||
componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.TextComponent',
|
componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent',
|
||||||
displayOrder: 0,
|
displayOrder: 0,
|
||||||
components: [
|
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 } }),
|
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({ color: TRANSPARENT }),
|
sprite({ dataId: CHARS.portraits[cls.classId], color: { r: 1, g: 1, b: 1, a: 1 }, type: 0, raycast: false }),
|
||||||
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 }),
|
|
||||||
],
|
],
|
||||||
}));
|
}));
|
||||||
select.push(entity({
|
select.push(entity({
|
||||||
id: guid('menu', 130 + i),
|
id: guid('menu', 210 + i),
|
||||||
path: `${base}/Portrait`,
|
path: `${base}/NameBanner`,
|
||||||
modelId: 'uisprite',
|
modelId: 'uisprite',
|
||||||
entryId: 'UISprite',
|
entryId: 'UISprite',
|
||||||
componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent',
|
componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent',
|
||||||
displayOrder: 1,
|
displayOrder: 1,
|
||||||
components: [
|
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 } }),
|
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: cls.tint, type: 1 }),
|
sprite({ color: { r: 0, g: 0, b: 0, a: 0.55 }, type: 1, raycast: false }),
|
||||||
],
|
],
|
||||||
}));
|
}));
|
||||||
select.push(entity({
|
select.push(entity({
|
||||||
id: guid('menu', 140 + i),
|
id: guid('menu', 120 + i),
|
||||||
path: `${base}/Desc`,
|
path: `${base}/Name`,
|
||||||
modelId: 'uitext',
|
modelId: 'uitext',
|
||||||
entryId: 'UIText',
|
entryId: 'UIText',
|
||||||
componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.TextComponent',
|
componentNames: 'MOD.Core.UITransformComponent,MOD.Core.SpriteGUIRendererComponent,MOD.Core.TextComponent',
|
||||||
displayOrder: 2,
|
displayOrder: 2,
|
||||||
components: [
|
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 }),
|
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) {
|
if (!cls.enabled) {
|
||||||
@@ -2593,6 +2598,20 @@ function upsertUi() {
|
|||||||
text({ value: '\uC2DC\uC791', fontSize: 30, bold: true, color: GOLD, alignment: 0 }),
|
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;
|
select[0].jsonString.enable = false;
|
||||||
emit('MainMenu', menu);
|
emit('MainMenu', menu);
|
||||||
emit('CharacterSelectHud', select);
|
emit('CharacterSelectHud', select);
|
||||||
@@ -2901,6 +2920,7 @@ function writeCodeblocks() {
|
|||||||
prop('number', 'AscensionLevel', '0'),
|
prop('number', 'AscensionLevel', '0'),
|
||||||
prop('number', 'AscensionUnlocked', '0'),
|
prop('number', 'AscensionUnlocked', '0'),
|
||||||
prop('any', 'StartGameHandler'),
|
prop('any', 'StartGameHandler'),
|
||||||
|
prop('any', 'CharBackHandler'),
|
||||||
prop('string', 'SelectedClass', '""'),
|
prop('string', 'SelectedClass', '""'),
|
||||||
prop('any', 'DrawPileHandler'),
|
prop('any', 'DrawPileHandler'),
|
||||||
prop('any', 'DiscardPileHandler'),
|
prop('any', 'DiscardPileHandler'),
|
||||||
@@ -3156,6 +3176,14 @@ if start ~= nil and start.ButtonComponent ~= nil then
|
|||||||
end
|
end
|
||||||
self.StartGameHandler = start:ConnectEvent(ButtonClickEvent, function() self:StartNewGame() end)
|
self.StartGameHandler = start:ConnectEvent(ButtonClickEvent, function() self:StartNewGame() end)
|
||||||
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")
|
local ascMinus = _EntityService:GetEntityByPath("/ui/DefaultGroup/MainMenu/AscMinus")
|
||||||
if ascMinus ~= nil and ascMinus.ButtonComponent ~= nil then
|
if ascMinus ~= nil and ascMinus.ButtonComponent ~= nil then
|
||||||
if self.AscMinusHandler ~= nil then
|
if self.AscMinusHandler ~= nil then
|
||||||
@@ -3362,7 +3390,7 @@ self:RenderCharacterSelect()`, [
|
|||||||
method('RenderCharacterSelect', `local warrior = _EntityService:GetEntityByPath("/ui/DefaultGroup/CharacterSelectHud/WarriorButton")
|
method('RenderCharacterSelect', `local warrior = _EntityService:GetEntityByPath("/ui/DefaultGroup/CharacterSelectHud/WarriorButton")
|
||||||
if warrior ~= nil and warrior.SpriteGUIRendererComponent ~= nil then
|
if warrior ~= nil and warrior.SpriteGUIRendererComponent ~= nil then
|
||||||
if self.SelectedClass == "warrior" 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
|
else
|
||||||
warrior.SpriteGUIRendererComponent.Color = Color(0.16, 0.2, 0.26, 1)
|
warrior.SpriteGUIRendererComponent.Color = Color(0.16, 0.2, 0.26, 1)
|
||||||
end
|
end
|
||||||
@@ -3370,7 +3398,7 @@ end
|
|||||||
local mage = _EntityService:GetEntityByPath("/ui/DefaultGroup/CharacterSelectHud/MageButton")
|
local mage = _EntityService:GetEntityByPath("/ui/DefaultGroup/CharacterSelectHud/MageButton")
|
||||||
if mage ~= nil and mage.SpriteGUIRendererComponent ~= nil then
|
if mage ~= nil and mage.SpriteGUIRendererComponent ~= nil then
|
||||||
if self.SelectedClass == "magician" 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
|
else
|
||||||
mage.SpriteGUIRendererComponent.Color = Color(0.16, 0.2, 0.26, 1)
|
mage.SpriteGUIRendererComponent.Color = Color(0.16, 0.2, 0.26, 1)
|
||||||
end
|
end
|
||||||
@@ -3378,7 +3406,7 @@ end
|
|||||||
local thief = _EntityService:GetEntityByPath("/ui/DefaultGroup/CharacterSelectHud/ThiefButton")
|
local thief = _EntityService:GetEntityByPath("/ui/DefaultGroup/CharacterSelectHud/ThiefButton")
|
||||||
if thief ~= nil and thief.SpriteGUIRendererComponent ~= nil then
|
if thief ~= nil and thief.SpriteGUIRendererComponent ~= nil then
|
||||||
if self.SelectedClass == "bandit" 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
|
else
|
||||||
thief.SpriteGUIRendererComponent.Color = Color(0.16, 0.2, 0.26, 1)
|
thief.SpriteGUIRendererComponent.Color = Color(0.16, 0.2, 0.26, 1)
|
||||||
end
|
end
|
||||||
|
|||||||
Reference in New Issue
Block a user