diff --git a/docs/battle-command-menu-rework-local-command-menu.png b/docs/battle-command-menu-rework-local-command-menu.png new file mode 100644 index 0000000..c791eb2 Binary files /dev/null and b/docs/battle-command-menu-rework-local-command-menu.png differ diff --git a/docs/battle-command-menu-rework-local-strategy-menu.png b/docs/battle-command-menu-rework-local-strategy-menu.png new file mode 100644 index 0000000..9e48cd4 Binary files /dev/null and b/docs/battle-command-menu-rework-local-strategy-menu.png differ diff --git a/docs/battle-command-menu-rework-local-support-targeting.png b/docs/battle-command-menu-rework-local-support-targeting.png new file mode 100644 index 0000000..eb90676 Binary files /dev/null and b/docs/battle-command-menu-rework-local-support-targeting.png differ diff --git a/docs/battle-command-menu-rework-prod-command-menu.png b/docs/battle-command-menu-rework-prod-command-menu.png new file mode 100644 index 0000000..3fb3deb Binary files /dev/null and b/docs/battle-command-menu-rework-prod-command-menu.png differ diff --git a/docs/battle-command-menu-rework-prod-strategy-menu.png b/docs/battle-command-menu-rework-prod-strategy-menu.png new file mode 100644 index 0000000..24a289c Binary files /dev/null and b/docs/battle-command-menu-rework-prod-strategy-menu.png differ diff --git a/docs/battle-command-menu-rework-prod-support-targeting.png b/docs/battle-command-menu-rework-prod-support-targeting.png new file mode 100644 index 0000000..aa73ed3 Binary files /dev/null and b/docs/battle-command-menu-rework-prod-support-targeting.png differ diff --git a/docs/battle-command-menu-rework-report.md b/docs/battle-command-menu-rework-report.md new file mode 100644 index 0000000..285f03a --- /dev/null +++ b/docs/battle-command-menu-rework-report.md @@ -0,0 +1,23 @@ +# Battle command menu rework report + +## Summary + +- Reworked the battle command menu rows to use larger raster icons, clearer status badges, and short tactical details. +- Reworked strategy/item selection rows to align with the combat preview panel: large action icon, value card, and three compact metric chips. +- Reworked target candidate rows to show icon-based mini metrics for damage/heal/buff, success or hit rate, and counter/status impact. + +## Verification + +- Build: `pnpm build` succeeded. +- Local browser: command menu, strategy menu, and support target candidate guide opened without console errors. +- Deployed browser: `https://comtropy.synology.me/heros_web/?debug&debugBattleSetup=attack-preview&v=command-menu-rework-20260703b` opened in a PC browser without console errors. +- Console errors: 0 during local verification and 0 during deployed verification. + +## Captures + +- Local command menu: `docs/battle-command-menu-rework-local-command-menu.png` +- Local strategy menu: `docs/battle-command-menu-rework-local-strategy-menu.png` +- Local support targeting guide: `docs/battle-command-menu-rework-local-support-targeting.png` +- Deployed command menu: `docs/battle-command-menu-rework-prod-command-menu.png` +- Deployed strategy menu: `docs/battle-command-menu-rework-prod-strategy-menu.png` +- Deployed support targeting guide: `docs/battle-command-menu-rework-prod-support-targeting.png` diff --git a/src/game/scenes/BattleScene.ts b/src/game/scenes/BattleScene.ts index bdb6325..4723455 100644 --- a/src/game/scenes/BattleScene.ts +++ b/src/game/scenes/BattleScene.ts @@ -1441,6 +1441,7 @@ type TargetingGuideCandidate = { primary: string; secondary: string; tone: number; + metrics?: TargetingGuideMetric[]; }; type TargetingGuideNotice = { @@ -4470,7 +4471,7 @@ export class BattleScene extends Phaser.Scene { return { available: true, status: `대상 ${targets.length}`, - detail: `${target.name} · 피해 ${preview.damage} · ${preview.hitRate}%` + detail: `${target.name} · 피해 ${preview.damage} · ${preview.hitRate}% · ${preview.counterAvailable ? '반격 주의' : '반격 없음'}` }; } @@ -4508,10 +4509,10 @@ export class BattleScene extends Phaser.Scene { this.hideCommandMenu(); const commands: BattleCommand[] = ['attack', 'strategy', 'item', 'wait']; - const menuWidth = 300; - const titleHeight = 46; - const buttonHeight = 58; - const gap = 7; + const menuWidth = 330; + const titleHeight = 54; + const buttonHeight = 70; + const gap = 8; const padding = 10; const menuHeight = padding * 2 + titleHeight + commands.length * buttonHeight + (commands.length - 1) * gap; const { left, top } = this.commandMenuPosition(pointerX, pointerY, menuWidth, menuHeight); @@ -4522,16 +4523,16 @@ export class BattleScene extends Phaser.Scene { panel.setDepth(14); this.commandMenuObjects.push(panel); - const title = this.add.text(left + 16, top + padding + 2, `${unit.name} 명령`, { + const title = this.add.text(left + 16, top + padding + 3, `${unit.name} 명령`, { fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif', - fontSize: '18px', + fontSize: '20px', color: '#d8b15f', fontStyle: '700' }); title.setDepth(15); this.commandMenuObjects.push(title); - const subtitle = this.add.text(left + 16, top + padding + 26, `HP ${unit.hp}/${unit.maxHp} · ${getUnitClass(unit.classKey).name}`, { + const subtitle = this.add.text(left + 16, top + padding + 31, `HP ${unit.hp}/${unit.maxHp} · ${getUnitClass(unit.classKey).name}`, { fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif', fontSize: '12px', color: '#aeb7c2', @@ -4553,46 +4554,46 @@ export class BattleScene extends Phaser.Scene { background.setInteractive({ useHandCursor: true }); background.setDepth(15); - const iconFrame = this.add.rectangle(left + 40, centerY, 42, 42, 0x0a0f14, 0.9); + const iconFrame = this.add.rectangle(left + 46, centerY, 56, 56, 0x0a0f14, 0.93); iconFrame.setStrokeStyle(1, accent, info.available ? 0.72 : 0.44); iconFrame.setDepth(16); this.commandMenuObjects.push(iconFrame); - const icon = this.createBattleUiIcon(left + 40, centerY, this.commandIcon(command, unit), 38); + const icon = this.createBattleUiIcon(left + 46, centerY, this.commandIcon(command, unit), 50); icon.setDepth(17); this.commandMenuObjects.push(icon); - const text = this.add.text(left + 68, buttonTop + 8, commandLabels[command], { + const text = this.add.text(left + 84, buttonTop + 10, commandLabels[command], { fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif', - fontSize: '19px', + fontSize: '20px', color: info.available ? '#f2e3bf' : '#c8ced6', fontStyle: '700', - fixedWidth: 86 + fixedWidth: 92 }); text.setDepth(16); - const detail = this.add.text(left + 68, buttonTop + 34, info.detail, { + const detail = this.add.text(left + 84, buttonTop + 40, info.detail, { fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif', - fontSize: '11px', + fontSize: '12px', color: info.available ? '#aeb7c2' : '#ffcf9a', - fixedWidth: menuWidth - 164 + fixedWidth: menuWidth - 194 }); detail.setDepth(16); this.commandMenuObjects.push(detail); - const badgeLeft = left + menuWidth - 92; - const badge = this.add.rectangle(badgeLeft, buttonTop + 15, 74, 28, 0x0b1118, 0.94); + const badgeLeft = left + menuWidth - 106; + const badge = this.add.rectangle(badgeLeft, buttonTop + 19, 88, 32, 0x0b1118, 0.95); badge.setOrigin(0); badge.setDepth(16); badge.setStrokeStyle(1, info.available ? accent : 0xb64a45, info.available ? 0.62 : 0.74); this.commandMenuObjects.push(badge); - const badgeText = this.add.text(badgeLeft + 37, buttonTop + 21, info.status, { + const badgeText = this.add.text(badgeLeft + 44, buttonTop + 27, info.status, { fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif', - fontSize: '10px', + fontSize: '11px', color: info.available ? '#d4dce6' : '#ffcf9a', fontStyle: '700', - fixedWidth: 70, + fixedWidth: 84, align: 'center' }); badgeText.setOrigin(0.5, 0); @@ -4826,12 +4827,12 @@ export class BattleScene extends Phaser.Scene { targetCount, valueLabel: '피해', value: `${preview.damage}`, - detail: `${target.name} · 피해 ${preview.damage} · ${successLabel} ${preview.hitRate}%`, + detail: `${target.name} · 피해 ${preview.damage} · ${successLabel} ${preview.hitRate}% · ${preview.counterAvailable ? '반격 주의' : '반격 없음'}`, secondary: `치명 ${preview.criticalRate}% · 지형 ${preview.terrainLabel} · 사거리 ${usable.range}칸`, chips: [ - { icon: this.usablePowerIcon(usable), label: '효과', value: this.usableEffectLabel(usable), tone: accent }, + { icon: this.usablePowerIcon(usable), label: '피해', value: `${preview.damage}`, tone: 0xb64a45 }, { icon: usable.command === 'strategy' ? 'success' : 'hit', label: successLabel, value: `${preview.hitRate}%`, tone: this.rateTone(preview.hitRate) }, - { icon: this.usableTargetIcon(usable), label: '대상', value: `${targetCount}명`, tone: 0xb64a45 } + { icon: 'counter', label: '반격', value: preview.counterAvailable ? '주의' : '없음', tone: preview.counterAvailable ? 0xd8732c : 0x59d18c } ] }; } @@ -4846,7 +4847,7 @@ export class BattleScene extends Phaser.Scene { chips: [ { icon: this.usablePowerIcon(usable), label: '효과', value: this.usableEffectLabel(usable), tone: accent }, { icon: 'success', label: '성공률', value: '-', tone: 0x647485 }, - { icon: this.usableTargetIcon(usable), label: '대상', value: '없음', tone: 0xb64a45 } + { icon: 'counter', label: '반격', value: '-', tone: 0x647485 } ] }; } @@ -4906,33 +4907,33 @@ export class BattleScene extends Phaser.Scene { tone: number, available: boolean ) { - const chip = this.add.rectangle(x, y, width, 40, 0x0b1118, available ? 0.95 : 0.87); + const chip = this.add.rectangle(x, y, width, 44, 0x0b1118, available ? 0.96 : 0.88); chip.setOrigin(0); chip.setDepth(16); chip.setStrokeStyle(1, tone, available ? 0.64 : 0.38); this.commandMenuObjects.push(chip); - const iconImage = this.createBattleUiIcon(x + 20, y + 20, icon, 31); + const iconImage = this.createBattleUiIcon(x + 22, y + 22, icon, 34); iconImage.setAlpha(available ? 0.98 : 0.72); iconImage.setDepth(17); this.commandMenuObjects.push(iconImage); - const labelText = this.add.text(x + 41, y + 5, label, { + const labelText = this.add.text(x + 44, y + 6, label, { fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif', fontSize: '10px', color: '#9fb0bf', fontStyle: '700', - fixedWidth: width - 48 + fixedWidth: width - 52 }); labelText.setDepth(17); this.commandMenuObjects.push(labelText); - const valueText = this.add.text(x + 41, y + 21, this.truncateUiText(value, 10), { + const valueText = this.add.text(x + 44, y + 23, this.truncateUiText(value, 10), { fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif', fontSize: '13px', color: available ? '#f2e3bf' : '#c8ced6', fontStyle: '700', - fixedWidth: width - 48 + fixedWidth: width - 52 }); valueText.setDepth(17); this.commandMenuObjects.push(valueText); @@ -4953,17 +4954,17 @@ export class BattleScene extends Phaser.Scene { valueBox.setStrokeStyle(1, forecast.available ? tone : 0x6c7480, forecast.available ? 0.82 : 0.46); this.commandMenuObjects.push(valueBox); - const valueIcon = this.createBattleUiIcon(x + 30, y + height / 2, this.usablePowerIcon(usable), 46); + const valueIcon = this.createBattleUiIcon(x + 32, y + height / 2, this.usablePowerIcon(usable), 52); valueIcon.setAlpha(forecast.available ? 1 : 0.72); valueIcon.setDepth(17); this.commandMenuObjects.push(valueIcon); - const valueLabel = this.add.text(x + 62, y + 12, forecast.valueLabel, { + const valueLabel = this.add.text(x + 66, y + 13, forecast.valueLabel, { fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif', fontSize: '12px', color: '#9fb0bf', fontStyle: '700', - fixedWidth: width - 70 + fixedWidth: width - 74 }); valueLabel.setDepth(17); this.commandMenuObjects.push(valueLabel); @@ -5001,9 +5002,9 @@ export class BattleScene extends Phaser.Scene { const forecasts = usables.map((usable) => ({ usable, forecast: this.usableMenuForecast(unit, usable) })); const readyCount = forecasts.filter(({ forecast }) => forecast.available).length; - const menuWidth = 520; + const menuWidth = 540; const titleHeight = 58; - const rowHeight = 148; + const rowHeight = 154; const padding = 10; const menuHeight = padding * 2 + titleHeight + usables.length * rowHeight; const { left, top } = this.commandMenuPosition(pointerX, pointerY, menuWidth, menuHeight); @@ -5057,7 +5058,7 @@ export class BattleScene extends Phaser.Scene { const rowWidth = menuWidth - padding * 2; const rowRight = rowLeft + rowWidth; const contentLeft = rowLeft + 102; - const sideBoxWidth = 124; + const sideBoxWidth = 132; const sideBoxLeft = rowRight - sideBoxWidth - 10; const contentWidth = sideBoxLeft - contentLeft - 12; const baseFill = forecast.available ? 0x1a2630 : 0x151c23; @@ -5068,12 +5069,12 @@ export class BattleScene extends Phaser.Scene { bg.setInteractive({ useHandCursor: true }); this.commandMenuObjects.push(bg); - const iconFrame = this.add.rectangle(rowLeft + 48, rowTop + 70, 86, 86, 0x0a0f14, forecast.available ? 0.94 : 0.87); + const iconFrame = this.add.rectangle(rowLeft + 48, rowTop + 72, 84, 84, 0x0a0f14, forecast.available ? 0.94 : 0.87); iconFrame.setStrokeStyle(1, accent, forecast.available ? 0.72 : 0.42); iconFrame.setDepth(16); this.commandMenuObjects.push(iconFrame); - const icon = this.createBattleUiIcon(rowLeft + 48, rowTop + 70, this.usableIcon(usable), 78); + const icon = this.createBattleUiIcon(rowLeft + 48, rowTop + 72, this.usableIcon(usable), 70); icon.setAlpha(forecast.available ? 1 : 0.72); icon.setDepth(17); this.commandMenuObjects.push(icon); @@ -5137,14 +5138,14 @@ export class BattleScene extends Phaser.Scene { secondary.setDepth(16); this.commandMenuObjects.push(secondary); - this.renderUsableMenuValueCard(sideBoxLeft, rowTop + 52, sideBoxWidth, 76, usable, forecast, accent); + this.renderUsableMenuValueCard(sideBoxLeft, rowTop + 54, sideBoxWidth, 82, usable, forecast, accent); const chipGap = 6; const chipWidth = (contentWidth - chipGap * 2) / 3; forecast.chips.slice(0, 3).forEach((chip, chipIndex) => { this.renderUsableMenuInfoChip( contentLeft + chipIndex * (chipWidth + chipGap), - rowTop + 98, + rowTop + 102, chipWidth, chip.icon, chip.label, @@ -5500,14 +5501,20 @@ export class BattleScene extends Phaser.Scene { const candidates = targets.slice(0, 3).map((target): TargetingGuideCandidate => { const preview = this.combatPreview(unit, target, action, usable); const distance = this.tileDistance(unit, target); + const successLabel = this.previewSuccessLabel(preview); const counterLabel = preview.counterAvailable ? '반격 주의' : '반격 없음'; return { icon: this.previewDamageIcon(preview), name: target.name, meta: `${getUnitClass(target.classKey).name} · 거리 ${distance}/${range}`, primary: `${preview.damage} 피해`, - secondary: `${preview.hitRate}% ${this.previewSuccessLabel(preview)} · ${counterLabel}`, - tone: this.previewAccentColor(preview) + secondary: `${preview.hitRate}% ${successLabel} · ${counterLabel}`, + tone: this.previewAccentColor(preview), + metrics: [ + { icon: this.previewDamageIcon(preview), label: '피해', value: `${preview.damage}`, tone: 0xb64a45 }, + { icon: action === 'strategy' ? 'success' : 'hit', label: successLabel, value: `${preview.hitRate}%`, tone: this.rateTone(preview.hitRate) }, + { icon: 'counter', label: '반격', value: preview.counterAvailable ? '주의' : '없음', tone: preview.counterAvailable ? 0xd8732c : 0x59d18c } + ] }; }); @@ -5546,7 +5553,18 @@ export class BattleScene extends Phaser.Scene { meta: `${getUnitClass(target.classKey).name} · 거리 ${distance}/${usable.range}`, primary: isHeal ? `+${preview.healAmount} 회복` : `공+${preview.attackBonus}`, secondary: isHeal ? `HP ${target.hp} → ${preview.projectedHp}` : `명+${preview.hitBonus} · 치+${preview.criticalBonus} · ${preview.duration}턴`, - tone: isHeal ? 0x59d18c : accent + tone: isHeal ? 0x59d18c : accent, + metrics: isHeal + ? [ + { icon: this.usablePowerIcon(usable), label: '회복', value: `+${preview.healAmount}`, tone: 0x59d18c }, + { icon: 'hp', label: 'HP', value: `${preview.projectedHp}`, tone: 0x59d18c }, + { icon: 'success', label: '적용', value: '100%', tone: 0x59d18c } + ] + : [ + { icon: 'focus', label: '공격', value: `+${preview.attackBonus}`, tone: palette.gold }, + { icon: 'hit', label: '명중', value: `+${preview.hitBonus}`, tone: palette.blue }, + { icon: 'critical', label: '치명', value: `+${preview.criticalBonus}`, tone: 0xd8732c } + ] }; }); @@ -5590,37 +5608,37 @@ export class BattleScene extends Phaser.Scene { const left = panelX + 24; const width = panelWidth - 48; const top = this.sideContentTop(); - const height = 416; + const height = 492; const bg = this.trackSideObject(this.add.rectangle(left, top, width, height, 0x101820, 0.98)); bg.setOrigin(0); bg.setStrokeStyle(2, options.accent, 0.78); - const iconFrame = this.trackSideObject(this.add.rectangle(left + 40, top + 42, 72, 72, 0x0a0f14, 0.94)); + const iconFrame = this.trackSideObject(this.add.rectangle(left + 46, top + 46, 84, 84, 0x0a0f14, 0.94)); iconFrame.setStrokeStyle(1, options.accent, 0.76); - this.trackSideIcon(left + 40, top + 42, options.icon, 64); + this.trackSideIcon(left + 46, top + 46, options.icon, 70); - this.trackSideObject(this.add.text(left + 86, top + 11, options.title, { + this.trackSideObject(this.add.text(left + 98, top + 12, options.title, { fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif', fontSize: '20px', color: '#f4dfad', fontStyle: '700', - fixedWidth: width - 96 + fixedWidth: width - 108 })); - this.trackSideObject(this.add.text(left + 86, top + 39, options.subtitle, { + this.trackSideObject(this.add.text(left + 98, top + 40, options.subtitle, { fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif', fontSize: '12px', color: '#9fb0bf', fontStyle: '700', - fixedWidth: width - 96 + fixedWidth: width - 108 })); - this.trackSideObject(this.add.text(left + 86, top + 57, `사용자 ${options.unit.name}`, { + this.trackSideObject(this.add.text(left + 98, top + 61, `사용자 ${options.unit.name}`, { fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif', fontSize: '12px', color: '#c8d2dd', - fixedWidth: width - 96 + fixedWidth: width - 108 })); - const metricTop = top + 92; + const metricTop = top + 104; const metricGap = 6; const metricWidth = (width - metricGap * 2) / 3; options.metrics.slice(0, 3).forEach((metric, index) => { @@ -5726,28 +5744,28 @@ export class BattleScene extends Phaser.Scene { this.renderTargetingGuideMetric(left + index * (metricWidth + metricGap), metricTop, metricWidth, metric); }); - this.trackSideIcon(left + 17, top + 165, 'success', 26); - this.trackSideObject(this.add.text(left + 36, top + 154, '대상 후보', { + this.trackSideIcon(left + 17, top + 182, 'success', 28); + this.trackSideObject(this.add.text(left + 38, top + 170, '대상 후보', { fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif', fontSize: '17px', color: '#f2e3bf', fontStyle: '700' })); - const rowTop = top + 188; + const rowTop = top + 200; options.candidates.forEach((candidate, index) => { - this.renderTargetingCandidateRow(left, rowTop + index * 58, width, candidate); + this.renderTargetingCandidateRow(left, rowTop + index * 84, width, candidate); }); - const footerTop = top + 368; - const footer = this.trackSideObject(this.add.rectangle(left, footerTop, width, 34, 0x0b1118, 0.95)); + const footerTop = top + 454; + const footer = this.trackSideObject(this.add.rectangle(left, footerTop, width, 28, 0x0b1118, 0.95)); footer.setOrigin(0); const footerTone = options.notice?.tone ?? options.accent; footer.setStrokeStyle(1, footerTone, options.notice ? 0.72 : 0.52); - this.trackSideIcon(left + 18, footerTop + 17, options.notice?.icon ?? 'success', 26); + this.trackSideIcon(left + 18, footerTop + 14, options.notice?.icon ?? 'success', 24); const overflowText = options.overflowCount > 0 ? ` · 외 ${options.overflowCount}명` : ''; const footerText = options.notice?.text ?? `${options.footer}${overflowText}`; - this.trackSideObject(this.add.text(left + 38, footerTop + 8, footerText, { + this.trackSideObject(this.add.text(left + 38, footerTop + 6, footerText, { fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif', fontSize: '12px', color: options.notice ? '#ffcf9a' : '#d4dce6', @@ -5757,62 +5775,71 @@ export class BattleScene extends Phaser.Scene { } private renderTargetingGuideMetric(x: number, y: number, width: number, metric: TargetingGuideMetric) { - const bg = this.trackSideObject(this.add.rectangle(x, y, width, 54, 0x16212d, 0.96)); + const bg = this.trackSideObject(this.add.rectangle(x, y, width, 62, 0x16212d, 0.97)); bg.setOrigin(0); bg.setStrokeStyle(1, metric.tone, 0.62); - const iconFrame = this.trackSideObject(this.add.rectangle(x + 20, y + 27, 38, 38, 0x0a0f14, 0.88)); + const iconFrame = this.trackSideObject(this.add.rectangle(x + 23, y + 31, 44, 44, 0x0a0f14, 0.9)); iconFrame.setStrokeStyle(1, metric.tone, 0.54); - this.trackSideIcon(x + 20, y + 27, metric.icon, 35); - this.trackSideObject(this.add.text(x + 42, y + 7, metric.label, { + this.trackSideIcon(x + 23, y + 31, metric.icon, 40); + this.trackSideObject(this.add.text(x + 49, y + 9, metric.label, { fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif', fontSize: '10px', color: '#9fb0bf', fontStyle: '700', - fixedWidth: width - 50 + fixedWidth: width - 56 })); - this.trackSideObject(this.add.text(x + 42, y + 26, this.truncateUiText(metric.value, 8), { + this.trackSideObject(this.add.text(x + 49, y + 31, this.truncateUiText(metric.value, 8), { fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif', fontSize: metric.value.length > 4 ? '13px' : '16px', color: '#f2e3bf', fontStyle: '700', - fixedWidth: width - 50 + fixedWidth: width - 56 })); } private renderTargetingCandidateRow(x: number, y: number, width: number, candidate: TargetingGuideCandidate) { - const row = this.trackSideObject(this.add.rectangle(x, y, width, 52, 0x121a22, 0.97)); + const rowHeight = 78; + const row = this.trackSideObject(this.add.rectangle(x, y, width, rowHeight, 0x121a22, 0.98)); row.setOrigin(0); - row.setStrokeStyle(1, candidate.tone, 0.58); - const iconFrame = this.trackSideObject(this.add.rectangle(x + 23, y + 26, 40, 40, 0x0a0f14, 0.9)); - iconFrame.setStrokeStyle(1, candidate.tone, 0.52); - this.trackSideIcon(x + 23, y + 26, candidate.icon, 36); - this.trackSideObject(this.add.text(x + 50, y + 6, candidate.name, { + row.setStrokeStyle(1, candidate.tone, 0.64); + const iconFrame = this.trackSideObject(this.add.rectangle(x + 30, y + 39, 56, 56, 0x0a0f14, 0.92)); + iconFrame.setStrokeStyle(1, candidate.tone, 0.58); + this.trackSideIcon(x + 30, y + 39, candidate.icon, 50); + this.trackSideObject(this.add.text(x + 64, y + 7, candidate.name, { fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif', fontSize: '15px', color: '#f2e3bf', fontStyle: '700', - fixedWidth: width - 158 + fixedWidth: width - 74 })); - this.trackSideObject(this.add.text(x + 50, y + 27, candidate.meta, { + this.trackSideObject(this.add.text(x + 64, y + 29, candidate.meta, { fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif', fontSize: '10px', color: '#9fb0bf', - fixedWidth: width - 158 + fixedWidth: width - 74 })); - const primary = this.trackSideObject(this.add.text(x + width - 10, y + 7, candidate.primary, { - fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif', - fontSize: '15px', - color: '#f4dfad', - fontStyle: '700' - })); - primary.setOrigin(1, 0); - const secondary = this.trackSideObject(this.add.text(x + width - 10, y + 29, candidate.secondary, { - fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif', - fontSize: '10px', - color: '#c8d2dd', - fontStyle: '700' - })); - secondary.setOrigin(1, 0); + const metrics = candidate.metrics ?? [ + { icon: candidate.icon, label: '결과', value: candidate.primary, tone: candidate.tone }, + { icon: 'success', label: '판정', value: candidate.secondary, tone: 0x647485 } + ]; + const chipGap = 5; + const chipLeft = x + 64; + const chipTop = y + 49; + const chipWidth = (width - 74 - chipGap * 2) / 3; + metrics.slice(0, 3).forEach((metric, index) => { + const chipX = chipLeft + index * (chipWidth + chipGap); + const chip = this.trackSideObject(this.add.rectangle(chipX, chipTop, chipWidth, 22, 0x0b1118, 0.94)); + chip.setOrigin(0); + chip.setStrokeStyle(1, metric.tone, 0.54); + this.trackSideIcon(chipX + 12, chipTop + 11, metric.icon, 19); + this.trackSideObject(this.add.text(chipX + 25, chipTop + 5, this.truncateUiText(metric.value, 7), { + fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif', + fontSize: '11px', + color: '#f2e3bf', + fontStyle: '700', + fixedWidth: chipWidth - 30 + })); + }); } private showTargetingRangeTiles(unit: UnitData, range: number, fillColor: number, strokeColor: number) {