Rework battle command selection menus
This commit is contained in:
BIN
docs/battle-command-menu-rework-local-command-menu.png
Normal file
BIN
docs/battle-command-menu-rework-local-command-menu.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 150 KiB |
BIN
docs/battle-command-menu-rework-local-strategy-menu.png
Normal file
BIN
docs/battle-command-menu-rework-local-strategy-menu.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 146 KiB |
BIN
docs/battle-command-menu-rework-local-support-targeting.png
Normal file
BIN
docs/battle-command-menu-rework-local-support-targeting.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 144 KiB |
BIN
docs/battle-command-menu-rework-prod-command-menu.png
Normal file
BIN
docs/battle-command-menu-rework-prod-command-menu.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 150 KiB |
BIN
docs/battle-command-menu-rework-prod-strategy-menu.png
Normal file
BIN
docs/battle-command-menu-rework-prod-strategy-menu.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 146 KiB |
BIN
docs/battle-command-menu-rework-prod-support-targeting.png
Normal file
BIN
docs/battle-command-menu-rework-prod-support-targeting.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 143 KiB |
23
docs/battle-command-menu-rework-report.md
Normal file
23
docs/battle-command-menu-rework-report.md
Normal file
@@ -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`
|
||||||
@@ -1441,6 +1441,7 @@ type TargetingGuideCandidate = {
|
|||||||
primary: string;
|
primary: string;
|
||||||
secondary: string;
|
secondary: string;
|
||||||
tone: number;
|
tone: number;
|
||||||
|
metrics?: TargetingGuideMetric[];
|
||||||
};
|
};
|
||||||
|
|
||||||
type TargetingGuideNotice = {
|
type TargetingGuideNotice = {
|
||||||
@@ -4470,7 +4471,7 @@ export class BattleScene extends Phaser.Scene {
|
|||||||
return {
|
return {
|
||||||
available: true,
|
available: true,
|
||||||
status: `대상 ${targets.length}`,
|
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();
|
this.hideCommandMenu();
|
||||||
|
|
||||||
const commands: BattleCommand[] = ['attack', 'strategy', 'item', 'wait'];
|
const commands: BattleCommand[] = ['attack', 'strategy', 'item', 'wait'];
|
||||||
const menuWidth = 300;
|
const menuWidth = 330;
|
||||||
const titleHeight = 46;
|
const titleHeight = 54;
|
||||||
const buttonHeight = 58;
|
const buttonHeight = 70;
|
||||||
const gap = 7;
|
const gap = 8;
|
||||||
const padding = 10;
|
const padding = 10;
|
||||||
const menuHeight = padding * 2 + titleHeight + commands.length * buttonHeight + (commands.length - 1) * gap;
|
const menuHeight = padding * 2 + titleHeight + commands.length * buttonHeight + (commands.length - 1) * gap;
|
||||||
const { left, top } = this.commandMenuPosition(pointerX, pointerY, menuWidth, menuHeight);
|
const { left, top } = this.commandMenuPosition(pointerX, pointerY, menuWidth, menuHeight);
|
||||||
@@ -4522,16 +4523,16 @@ export class BattleScene extends Phaser.Scene {
|
|||||||
panel.setDepth(14);
|
panel.setDepth(14);
|
||||||
this.commandMenuObjects.push(panel);
|
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',
|
fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif',
|
||||||
fontSize: '18px',
|
fontSize: '20px',
|
||||||
color: '#d8b15f',
|
color: '#d8b15f',
|
||||||
fontStyle: '700'
|
fontStyle: '700'
|
||||||
});
|
});
|
||||||
title.setDepth(15);
|
title.setDepth(15);
|
||||||
this.commandMenuObjects.push(title);
|
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',
|
fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif',
|
||||||
fontSize: '12px',
|
fontSize: '12px',
|
||||||
color: '#aeb7c2',
|
color: '#aeb7c2',
|
||||||
@@ -4553,46 +4554,46 @@ export class BattleScene extends Phaser.Scene {
|
|||||||
background.setInteractive({ useHandCursor: true });
|
background.setInteractive({ useHandCursor: true });
|
||||||
background.setDepth(15);
|
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.setStrokeStyle(1, accent, info.available ? 0.72 : 0.44);
|
||||||
iconFrame.setDepth(16);
|
iconFrame.setDepth(16);
|
||||||
this.commandMenuObjects.push(iconFrame);
|
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);
|
icon.setDepth(17);
|
||||||
this.commandMenuObjects.push(icon);
|
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',
|
fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif',
|
||||||
fontSize: '19px',
|
fontSize: '20px',
|
||||||
color: info.available ? '#f2e3bf' : '#c8ced6',
|
color: info.available ? '#f2e3bf' : '#c8ced6',
|
||||||
fontStyle: '700',
|
fontStyle: '700',
|
||||||
fixedWidth: 86
|
fixedWidth: 92
|
||||||
});
|
});
|
||||||
text.setDepth(16);
|
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',
|
fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif',
|
||||||
fontSize: '11px',
|
fontSize: '12px',
|
||||||
color: info.available ? '#aeb7c2' : '#ffcf9a',
|
color: info.available ? '#aeb7c2' : '#ffcf9a',
|
||||||
fixedWidth: menuWidth - 164
|
fixedWidth: menuWidth - 194
|
||||||
});
|
});
|
||||||
detail.setDepth(16);
|
detail.setDepth(16);
|
||||||
this.commandMenuObjects.push(detail);
|
this.commandMenuObjects.push(detail);
|
||||||
|
|
||||||
const badgeLeft = left + menuWidth - 92;
|
const badgeLeft = left + menuWidth - 106;
|
||||||
const badge = this.add.rectangle(badgeLeft, buttonTop + 15, 74, 28, 0x0b1118, 0.94);
|
const badge = this.add.rectangle(badgeLeft, buttonTop + 19, 88, 32, 0x0b1118, 0.95);
|
||||||
badge.setOrigin(0);
|
badge.setOrigin(0);
|
||||||
badge.setDepth(16);
|
badge.setDepth(16);
|
||||||
badge.setStrokeStyle(1, info.available ? accent : 0xb64a45, info.available ? 0.62 : 0.74);
|
badge.setStrokeStyle(1, info.available ? accent : 0xb64a45, info.available ? 0.62 : 0.74);
|
||||||
this.commandMenuObjects.push(badge);
|
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',
|
fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif',
|
||||||
fontSize: '10px',
|
fontSize: '11px',
|
||||||
color: info.available ? '#d4dce6' : '#ffcf9a',
|
color: info.available ? '#d4dce6' : '#ffcf9a',
|
||||||
fontStyle: '700',
|
fontStyle: '700',
|
||||||
fixedWidth: 70,
|
fixedWidth: 84,
|
||||||
align: 'center'
|
align: 'center'
|
||||||
});
|
});
|
||||||
badgeText.setOrigin(0.5, 0);
|
badgeText.setOrigin(0.5, 0);
|
||||||
@@ -4826,12 +4827,12 @@ export class BattleScene extends Phaser.Scene {
|
|||||||
targetCount,
|
targetCount,
|
||||||
valueLabel: '피해',
|
valueLabel: '피해',
|
||||||
value: `${preview.damage}`,
|
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}칸`,
|
secondary: `치명 ${preview.criticalRate}% · 지형 ${preview.terrainLabel} · 사거리 ${usable.range}칸`,
|
||||||
chips: [
|
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: 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: [
|
chips: [
|
||||||
{ icon: this.usablePowerIcon(usable), label: '효과', value: this.usableEffectLabel(usable), tone: accent },
|
{ icon: this.usablePowerIcon(usable), label: '효과', value: this.usableEffectLabel(usable), tone: accent },
|
||||||
{ icon: 'success', label: '성공률', value: '-', tone: 0x647485 },
|
{ 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,
|
tone: number,
|
||||||
available: boolean
|
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.setOrigin(0);
|
||||||
chip.setDepth(16);
|
chip.setDepth(16);
|
||||||
chip.setStrokeStyle(1, tone, available ? 0.64 : 0.38);
|
chip.setStrokeStyle(1, tone, available ? 0.64 : 0.38);
|
||||||
this.commandMenuObjects.push(chip);
|
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.setAlpha(available ? 0.98 : 0.72);
|
||||||
iconImage.setDepth(17);
|
iconImage.setDepth(17);
|
||||||
this.commandMenuObjects.push(iconImage);
|
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',
|
fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif',
|
||||||
fontSize: '10px',
|
fontSize: '10px',
|
||||||
color: '#9fb0bf',
|
color: '#9fb0bf',
|
||||||
fontStyle: '700',
|
fontStyle: '700',
|
||||||
fixedWidth: width - 48
|
fixedWidth: width - 52
|
||||||
});
|
});
|
||||||
labelText.setDepth(17);
|
labelText.setDepth(17);
|
||||||
this.commandMenuObjects.push(labelText);
|
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',
|
fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif',
|
||||||
fontSize: '13px',
|
fontSize: '13px',
|
||||||
color: available ? '#f2e3bf' : '#c8ced6',
|
color: available ? '#f2e3bf' : '#c8ced6',
|
||||||
fontStyle: '700',
|
fontStyle: '700',
|
||||||
fixedWidth: width - 48
|
fixedWidth: width - 52
|
||||||
});
|
});
|
||||||
valueText.setDepth(17);
|
valueText.setDepth(17);
|
||||||
this.commandMenuObjects.push(valueText);
|
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);
|
valueBox.setStrokeStyle(1, forecast.available ? tone : 0x6c7480, forecast.available ? 0.82 : 0.46);
|
||||||
this.commandMenuObjects.push(valueBox);
|
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.setAlpha(forecast.available ? 1 : 0.72);
|
||||||
valueIcon.setDepth(17);
|
valueIcon.setDepth(17);
|
||||||
this.commandMenuObjects.push(valueIcon);
|
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',
|
fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif',
|
||||||
fontSize: '12px',
|
fontSize: '12px',
|
||||||
color: '#9fb0bf',
|
color: '#9fb0bf',
|
||||||
fontStyle: '700',
|
fontStyle: '700',
|
||||||
fixedWidth: width - 70
|
fixedWidth: width - 74
|
||||||
});
|
});
|
||||||
valueLabel.setDepth(17);
|
valueLabel.setDepth(17);
|
||||||
this.commandMenuObjects.push(valueLabel);
|
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 forecasts = usables.map((usable) => ({ usable, forecast: this.usableMenuForecast(unit, usable) }));
|
||||||
const readyCount = forecasts.filter(({ forecast }) => forecast.available).length;
|
const readyCount = forecasts.filter(({ forecast }) => forecast.available).length;
|
||||||
const menuWidth = 520;
|
const menuWidth = 540;
|
||||||
const titleHeight = 58;
|
const titleHeight = 58;
|
||||||
const rowHeight = 148;
|
const rowHeight = 154;
|
||||||
const padding = 10;
|
const padding = 10;
|
||||||
const menuHeight = padding * 2 + titleHeight + usables.length * rowHeight;
|
const menuHeight = padding * 2 + titleHeight + usables.length * rowHeight;
|
||||||
const { left, top } = this.commandMenuPosition(pointerX, pointerY, menuWidth, menuHeight);
|
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 rowWidth = menuWidth - padding * 2;
|
||||||
const rowRight = rowLeft + rowWidth;
|
const rowRight = rowLeft + rowWidth;
|
||||||
const contentLeft = rowLeft + 102;
|
const contentLeft = rowLeft + 102;
|
||||||
const sideBoxWidth = 124;
|
const sideBoxWidth = 132;
|
||||||
const sideBoxLeft = rowRight - sideBoxWidth - 10;
|
const sideBoxLeft = rowRight - sideBoxWidth - 10;
|
||||||
const contentWidth = sideBoxLeft - contentLeft - 12;
|
const contentWidth = sideBoxLeft - contentLeft - 12;
|
||||||
const baseFill = forecast.available ? 0x1a2630 : 0x151c23;
|
const baseFill = forecast.available ? 0x1a2630 : 0x151c23;
|
||||||
@@ -5068,12 +5069,12 @@ export class BattleScene extends Phaser.Scene {
|
|||||||
bg.setInteractive({ useHandCursor: true });
|
bg.setInteractive({ useHandCursor: true });
|
||||||
this.commandMenuObjects.push(bg);
|
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.setStrokeStyle(1, accent, forecast.available ? 0.72 : 0.42);
|
||||||
iconFrame.setDepth(16);
|
iconFrame.setDepth(16);
|
||||||
this.commandMenuObjects.push(iconFrame);
|
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.setAlpha(forecast.available ? 1 : 0.72);
|
||||||
icon.setDepth(17);
|
icon.setDepth(17);
|
||||||
this.commandMenuObjects.push(icon);
|
this.commandMenuObjects.push(icon);
|
||||||
@@ -5137,14 +5138,14 @@ export class BattleScene extends Phaser.Scene {
|
|||||||
secondary.setDepth(16);
|
secondary.setDepth(16);
|
||||||
this.commandMenuObjects.push(secondary);
|
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 chipGap = 6;
|
||||||
const chipWidth = (contentWidth - chipGap * 2) / 3;
|
const chipWidth = (contentWidth - chipGap * 2) / 3;
|
||||||
forecast.chips.slice(0, 3).forEach((chip, chipIndex) => {
|
forecast.chips.slice(0, 3).forEach((chip, chipIndex) => {
|
||||||
this.renderUsableMenuInfoChip(
|
this.renderUsableMenuInfoChip(
|
||||||
contentLeft + chipIndex * (chipWidth + chipGap),
|
contentLeft + chipIndex * (chipWidth + chipGap),
|
||||||
rowTop + 98,
|
rowTop + 102,
|
||||||
chipWidth,
|
chipWidth,
|
||||||
chip.icon,
|
chip.icon,
|
||||||
chip.label,
|
chip.label,
|
||||||
@@ -5500,14 +5501,20 @@ export class BattleScene extends Phaser.Scene {
|
|||||||
const candidates = targets.slice(0, 3).map((target): TargetingGuideCandidate => {
|
const candidates = targets.slice(0, 3).map((target): TargetingGuideCandidate => {
|
||||||
const preview = this.combatPreview(unit, target, action, usable);
|
const preview = this.combatPreview(unit, target, action, usable);
|
||||||
const distance = this.tileDistance(unit, target);
|
const distance = this.tileDistance(unit, target);
|
||||||
|
const successLabel = this.previewSuccessLabel(preview);
|
||||||
const counterLabel = preview.counterAvailable ? '반격 주의' : '반격 없음';
|
const counterLabel = preview.counterAvailable ? '반격 주의' : '반격 없음';
|
||||||
return {
|
return {
|
||||||
icon: this.previewDamageIcon(preview),
|
icon: this.previewDamageIcon(preview),
|
||||||
name: target.name,
|
name: target.name,
|
||||||
meta: `${getUnitClass(target.classKey).name} · 거리 ${distance}/${range}`,
|
meta: `${getUnitClass(target.classKey).name} · 거리 ${distance}/${range}`,
|
||||||
primary: `${preview.damage} 피해`,
|
primary: `${preview.damage} 피해`,
|
||||||
secondary: `${preview.hitRate}% ${this.previewSuccessLabel(preview)} · ${counterLabel}`,
|
secondary: `${preview.hitRate}% ${successLabel} · ${counterLabel}`,
|
||||||
tone: this.previewAccentColor(preview)
|
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}`,
|
meta: `${getUnitClass(target.classKey).name} · 거리 ${distance}/${usable.range}`,
|
||||||
primary: isHeal ? `+${preview.healAmount} 회복` : `공+${preview.attackBonus}`,
|
primary: isHeal ? `+${preview.healAmount} 회복` : `공+${preview.attackBonus}`,
|
||||||
secondary: isHeal ? `HP ${target.hp} → ${preview.projectedHp}` : `명+${preview.hitBonus} · 치+${preview.criticalBonus} · ${preview.duration}턴`,
|
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 left = panelX + 24;
|
||||||
const width = panelWidth - 48;
|
const width = panelWidth - 48;
|
||||||
const top = this.sideContentTop();
|
const top = this.sideContentTop();
|
||||||
const height = 416;
|
const height = 492;
|
||||||
const bg = this.trackSideObject(this.add.rectangle(left, top, width, height, 0x101820, 0.98));
|
const bg = this.trackSideObject(this.add.rectangle(left, top, width, height, 0x101820, 0.98));
|
||||||
bg.setOrigin(0);
|
bg.setOrigin(0);
|
||||||
bg.setStrokeStyle(2, options.accent, 0.78);
|
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);
|
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',
|
fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif',
|
||||||
fontSize: '20px',
|
fontSize: '20px',
|
||||||
color: '#f4dfad',
|
color: '#f4dfad',
|
||||||
fontStyle: '700',
|
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',
|
fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif',
|
||||||
fontSize: '12px',
|
fontSize: '12px',
|
||||||
color: '#9fb0bf',
|
color: '#9fb0bf',
|
||||||
fontStyle: '700',
|
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',
|
fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif',
|
||||||
fontSize: '12px',
|
fontSize: '12px',
|
||||||
color: '#c8d2dd',
|
color: '#c8d2dd',
|
||||||
fixedWidth: width - 96
|
fixedWidth: width - 108
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const metricTop = top + 92;
|
const metricTop = top + 104;
|
||||||
const metricGap = 6;
|
const metricGap = 6;
|
||||||
const metricWidth = (width - metricGap * 2) / 3;
|
const metricWidth = (width - metricGap * 2) / 3;
|
||||||
options.metrics.slice(0, 3).forEach((metric, index) => {
|
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.renderTargetingGuideMetric(left + index * (metricWidth + metricGap), metricTop, metricWidth, metric);
|
||||||
});
|
});
|
||||||
|
|
||||||
this.trackSideIcon(left + 17, top + 165, 'success', 26);
|
this.trackSideIcon(left + 17, top + 182, 'success', 28);
|
||||||
this.trackSideObject(this.add.text(left + 36, top + 154, '대상 후보', {
|
this.trackSideObject(this.add.text(left + 38, top + 170, '대상 후보', {
|
||||||
fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif',
|
fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif',
|
||||||
fontSize: '17px',
|
fontSize: '17px',
|
||||||
color: '#f2e3bf',
|
color: '#f2e3bf',
|
||||||
fontStyle: '700'
|
fontStyle: '700'
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const rowTop = top + 188;
|
const rowTop = top + 200;
|
||||||
options.candidates.forEach((candidate, index) => {
|
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 footerTop = top + 454;
|
||||||
const footer = this.trackSideObject(this.add.rectangle(left, footerTop, width, 34, 0x0b1118, 0.95));
|
const footer = this.trackSideObject(this.add.rectangle(left, footerTop, width, 28, 0x0b1118, 0.95));
|
||||||
footer.setOrigin(0);
|
footer.setOrigin(0);
|
||||||
const footerTone = options.notice?.tone ?? options.accent;
|
const footerTone = options.notice?.tone ?? options.accent;
|
||||||
footer.setStrokeStyle(1, footerTone, options.notice ? 0.72 : 0.52);
|
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 overflowText = options.overflowCount > 0 ? ` · 외 ${options.overflowCount}명` : '';
|
||||||
const footerText = options.notice?.text ?? `${options.footer}${overflowText}`;
|
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',
|
fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif',
|
||||||
fontSize: '12px',
|
fontSize: '12px',
|
||||||
color: options.notice ? '#ffcf9a' : '#d4dce6',
|
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) {
|
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.setOrigin(0);
|
||||||
bg.setStrokeStyle(1, metric.tone, 0.62);
|
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);
|
iconFrame.setStrokeStyle(1, metric.tone, 0.54);
|
||||||
this.trackSideIcon(x + 20, y + 27, metric.icon, 35);
|
this.trackSideIcon(x + 23, y + 31, metric.icon, 40);
|
||||||
this.trackSideObject(this.add.text(x + 42, y + 7, metric.label, {
|
this.trackSideObject(this.add.text(x + 49, y + 9, metric.label, {
|
||||||
fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif',
|
fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif',
|
||||||
fontSize: '10px',
|
fontSize: '10px',
|
||||||
color: '#9fb0bf',
|
color: '#9fb0bf',
|
||||||
fontStyle: '700',
|
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',
|
fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif',
|
||||||
fontSize: metric.value.length > 4 ? '13px' : '16px',
|
fontSize: metric.value.length > 4 ? '13px' : '16px',
|
||||||
color: '#f2e3bf',
|
color: '#f2e3bf',
|
||||||
fontStyle: '700',
|
fontStyle: '700',
|
||||||
fixedWidth: width - 50
|
fixedWidth: width - 56
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
private renderTargetingCandidateRow(x: number, y: number, width: number, candidate: TargetingGuideCandidate) {
|
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.setOrigin(0);
|
||||||
row.setStrokeStyle(1, candidate.tone, 0.58);
|
row.setStrokeStyle(1, candidate.tone, 0.64);
|
||||||
const iconFrame = this.trackSideObject(this.add.rectangle(x + 23, y + 26, 40, 40, 0x0a0f14, 0.9));
|
const iconFrame = this.trackSideObject(this.add.rectangle(x + 30, y + 39, 56, 56, 0x0a0f14, 0.92));
|
||||||
iconFrame.setStrokeStyle(1, candidate.tone, 0.52);
|
iconFrame.setStrokeStyle(1, candidate.tone, 0.58);
|
||||||
this.trackSideIcon(x + 23, y + 26, candidate.icon, 36);
|
this.trackSideIcon(x + 30, y + 39, candidate.icon, 50);
|
||||||
this.trackSideObject(this.add.text(x + 50, y + 6, candidate.name, {
|
this.trackSideObject(this.add.text(x + 64, y + 7, candidate.name, {
|
||||||
fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif',
|
fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif',
|
||||||
fontSize: '15px',
|
fontSize: '15px',
|
||||||
color: '#f2e3bf',
|
color: '#f2e3bf',
|
||||||
fontStyle: '700',
|
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',
|
fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif',
|
||||||
fontSize: '10px',
|
fontSize: '10px',
|
||||||
color: '#9fb0bf',
|
color: '#9fb0bf',
|
||||||
fixedWidth: width - 158
|
fixedWidth: width - 74
|
||||||
}));
|
}));
|
||||||
const primary = this.trackSideObject(this.add.text(x + width - 10, y + 7, candidate.primary, {
|
const metrics = candidate.metrics ?? [
|
||||||
fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif',
|
{ icon: candidate.icon, label: '결과', value: candidate.primary, tone: candidate.tone },
|
||||||
fontSize: '15px',
|
{ icon: 'success', label: '판정', value: candidate.secondary, tone: 0x647485 }
|
||||||
color: '#f4dfad',
|
];
|
||||||
fontStyle: '700'
|
const chipGap = 5;
|
||||||
}));
|
const chipLeft = x + 64;
|
||||||
primary.setOrigin(1, 0);
|
const chipTop = y + 49;
|
||||||
const secondary = this.trackSideObject(this.add.text(x + width - 10, y + 29, candidate.secondary, {
|
const chipWidth = (width - 74 - chipGap * 2) / 3;
|
||||||
fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif',
|
metrics.slice(0, 3).forEach((metric, index) => {
|
||||||
fontSize: '10px',
|
const chipX = chipLeft + index * (chipWidth + chipGap);
|
||||||
color: '#c8d2dd',
|
const chip = this.trackSideObject(this.add.rectangle(chipX, chipTop, chipWidth, 22, 0x0b1118, 0.94));
|
||||||
fontStyle: '700'
|
chip.setOrigin(0);
|
||||||
}));
|
chip.setStrokeStyle(1, metric.tone, 0.54);
|
||||||
secondary.setOrigin(1, 0);
|
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) {
|
private showTargetingRangeTiles(unit: UnitData, range: number, fillColor: number, strokeColor: number) {
|
||||||
|
|||||||
Reference in New Issue
Block a user