Clarify battle tactic icon semantics

This commit is contained in:
2026-07-02 21:48:39 +09:00
parent 79e535c2bb
commit 4979740abf

View File

@@ -4231,26 +4231,94 @@ export class BattleScene extends Phaser.Scene {
}
private usableAccentColor(usable: BattleUsable) {
if (usable.command === 'item') {
return palette.gold;
}
if (usable.effect === 'heal') {
return palette.green;
}
if (usable.effect === 'focus') {
return palette.blue;
}
return 0xb64a45;
return this.usableVisualStyle(usable).accent;
}
private usableEffectLabel(usable: BattleUsable) {
return this.usableVisualStyle(usable).effectLabel;
}
private usableChannelLabel(usable: BattleUsable) {
return usable.command === 'item' ? '도구' : '책략';
}
private usableTargetIcon(usable: BattleUsable): BattleUiIconKey {
if (usable.target === 'self') {
return 'counter';
}
if (usable.target === 'ally') {
return 'leadership';
}
return 'hit';
}
private usablePowerIcon(usable: BattleUsable): BattleUiIconKey {
return this.usableVisualStyle(usable).powerIcon;
}
private usableVisualStyle(usable: BattleUsable) {
const id = usable.id.toLowerCase();
if (usable.command === 'item') {
if (usable.effect === 'heal') {
return {
icon: 'heal' as BattleUiIconKey,
powerIcon: 'heal' as BattleUiIconKey,
accent: palette.gold,
effectLabel: '회복',
powerLabel: `회복 ${usable.power}+`
};
}
return {
icon: 'accessory' as BattleUiIconKey,
powerIcon: 'focus' as BattleUiIconKey,
accent: palette.gold,
effectLabel: '강화',
powerLabel: `${usable.duration ?? 1}턴 강화`
};
}
if (usable.effect === 'heal') {
return '회복';
return {
icon: 'heal' as BattleUiIconKey,
powerIcon: 'heal' as BattleUiIconKey,
accent: palette.green,
effectLabel: '회복',
powerLabel: `회복 ${usable.power}+`
};
}
if (usable.effect === 'focus') {
return '강화';
return {
icon: 'focus' as BattleUiIconKey,
powerIcon: 'leadership' as BattleUiIconKey,
accent: palette.blue,
effectLabel: '격려',
powerLabel: `${usable.duration ?? 1}턴 강화`
};
}
return '피해';
if (id.includes('fire')) {
return {
icon: 'fire' as BattleUiIconKey,
powerIcon: 'fire' as BattleUiIconKey,
accent: 0xd8732c,
effectLabel: '화염',
powerLabel: `화염 ${usable.power}`
};
}
if (id.includes('roar') || id.includes('shout')) {
return {
icon: 'shout' as BattleUiIconKey,
powerIcon: 'shout' as BattleUiIconKey,
accent: 0xc96b4a,
effectLabel: '위압',
powerLabel: `위압 ${usable.power}`
};
}
return {
icon: 'strategy' as BattleUiIconKey,
powerIcon: 'strategy' as BattleUiIconKey,
accent: 0xb64a45,
effectLabel: '피해',
powerLabel: `위력 ${usable.power}`
};
}
private usableTargetLabel(usable: BattleUsable) {
@@ -4264,13 +4332,7 @@ export class BattleScene extends Phaser.Scene {
}
private usablePowerLabel(usable: BattleUsable) {
if (usable.effect === 'heal') {
return `회복 ${usable.power}+`;
}
if (usable.effect === 'focus') {
return `${usable.duration ?? 1}턴 강화`;
}
return `위력 ${usable.power}`;
return this.usableVisualStyle(usable).powerLabel;
}
private usableTargetCount(user: UnitData, usable: BattleUsable) {
@@ -4287,7 +4349,7 @@ export class BattleScene extends Phaser.Scene {
private usableDetailText(user: UnitData, usable: BattleUsable) {
const targetCount = this.usableTargetCount(user, usable);
return [
`${usable.name} / ${this.usableEffectLabel(usable)} / ${this.usableTargetLabel(usable)}`,
`${usable.name} / ${this.usableChannelLabel(usable)} / ${this.usableEffectLabel(usable)} / ${this.usableTargetLabel(usable)}`,
`${this.usablePowerLabel(usable)} · 사거리 ${usable.range} · 대상 ${targetCount}`,
usable.description
].join('\n');
@@ -4400,7 +4462,7 @@ export class BattleScene extends Phaser.Scene {
const summary = this.add.text(
left + 68,
rowTop + 34,
`${this.usableEffectLabel(usable)} · ${this.usableTargetLabel(usable)} · 사거리 ${usable.range} · 대상 ${this.usableTargetCount(unit, usable)}`,
`${this.usableChannelLabel(usable)} · ${this.usableEffectLabel(usable)} · ${this.usableTargetLabel(usable)} · 대상 ${this.usableTargetCount(unit, usable)}`,
{
fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif',
fontSize: '12px',
@@ -4412,8 +4474,8 @@ export class BattleScene extends Phaser.Scene {
this.commandMenuObjects.push(summary);
this.renderUsableMenuPill(left + 68, rowTop + 49, 82, this.usableIcon(usable), this.usableEffectLabel(usable), accent);
this.renderUsableMenuPill(left + 156, rowTop + 49, 90, 'move', `거리 ${usable.range}`, accent);
this.renderUsableMenuPill(left + 252, rowTop + 49, 86, usable.target === 'enemy' ? 'hit' : 'success', this.usablePowerLabel(usable), accent);
this.renderUsableMenuPill(left + 156, rowTop + 49, 90, this.usableTargetIcon(usable), `${this.usableTargetLabel(usable)} ${usable.range}`, accent);
this.renderUsableMenuPill(left + 252, rowTop + 49, 86, this.usablePowerIcon(usable), this.usablePowerLabel(usable), accent);
const choose = (choicePointer: Phaser.Input.Pointer) => {
if (choicePointer.rightButtonDown()) {
@@ -4799,6 +4861,9 @@ export class BattleScene extends Phaser.Scene {
}
private previewAccentColor(preview: CombatPreview) {
if (preview.usable) {
return this.usableAccentColor(preview.usable);
}
if (preview.action === 'strategy') {
return palette.blue;
}
@@ -4812,6 +4877,23 @@ export class BattleScene extends Phaser.Scene {
return preview.action === 'strategy' ? '성공' : '명중';
}
private previewDamageIcon(preview: CombatPreview): BattleUiIconKey {
return preview.usable ? this.usablePowerIcon(preview.usable) : 'attack';
}
private previewActionTypeLabel(preview: CombatPreview) {
if (preview.usable) {
return this.usableEffectLabel(preview.usable);
}
if (preview.action === 'attack') {
return '무기 공격';
}
if (preview.action === 'strategy') {
return '책략';
}
return '도구';
}
private renderCombatPreviewCard(preview: CombatPreview) {
const { panelX, panelWidth } = this.layout;
const left = panelX + 24;
@@ -4849,7 +4931,7 @@ export class BattleScene extends Phaser.Scene {
const metricTop = y + 86;
const metricGap = 6;
const metricWidth = (width - 20 - metricGap * 2) / 3;
this.renderPreviewMetric(left + 10, metricTop, metricWidth, 'attack', '피해', `${preview.damage}`, 0xb64a45);
this.renderPreviewMetric(left + 10, metricTop, metricWidth, this.previewDamageIcon(preview), '피해', `${preview.damage}`, 0xb64a45);
this.renderPreviewMetric(
left + 10 + metricWidth + metricGap,
metricTop,
@@ -4866,10 +4948,9 @@ export class BattleScene extends Phaser.Scene {
const badgeY = y + 150;
const maxBadgeRight = left + width - 10;
let badgeX = left + 10;
badgeX = this.renderPreviewBadge(badgeX, badgeY, this.actionIcon(preview), this.previewActionTypeLabel(preview), maxBadgeRight);
badgeX = this.renderPreviewBadge(badgeX, badgeY, preview.action === 'strategy' ? 'success' : 'hit', `${preview.hitRate}% ${this.previewSuccessLabel(preview)}`, maxBadgeRight);
badgeX = this.renderPreviewBadge(badgeX, badgeY, 'advantage', preview.matchupLabel, maxBadgeRight);
if (preview.action === 'strategy') {
badgeX = this.renderPreviewBadge(badgeX, badgeY, 'success', `${preview.hitRate}% 성공`, maxBadgeRight);
}
badgeX = this.renderPreviewBadge(badgeX, badgeY, 'terrain', preview.terrainLabel, maxBadgeRight);
if (preview.counterAvailable) {
badgeX = this.renderPreviewBadge(badgeX, badgeY, 'counter', '반격 가능', maxBadgeRight);
@@ -4913,11 +4994,11 @@ export class BattleScene extends Phaser.Scene {
const metricGap = 6;
const metricWidth = (width - 20 - metricGap * 2) / 3;
if (preview.usable.effect === 'heal') {
this.renderPreviewMetric(left + 10, metricTop, metricWidth, 'hp', '회복', `+${preview.healAmount}`, 0x59d18c);
this.renderPreviewMetric(left + 10, metricTop, metricWidth, this.usablePowerIcon(preview.usable), '회복', `+${preview.healAmount}`, 0x59d18c);
this.renderPreviewMetric(left + 10 + metricWidth + metricGap, metricTop, metricWidth, 'success', '적용', '100%', accent);
this.renderPreviewMetric(left + 10 + (metricWidth + metricGap) * 2, metricTop, metricWidth, 'move', '거리', `${preview.usable.range}`, accent);
this.renderPreviewMetric(left + 10 + (metricWidth + metricGap) * 2, metricTop, metricWidth, this.usableTargetIcon(preview.usable), '대상', `${this.usableTargetLabel(preview.usable)} ${preview.usable.range}`, accent);
} else {
this.renderPreviewMetric(left + 10, metricTop, metricWidth, 'attack', '공격', `+${preview.attackBonus}`, 0xd8b15f);
this.renderPreviewMetric(left + 10, metricTop, metricWidth, this.usablePowerIcon(preview.usable), '강화', `+${preview.attackBonus}`, 0xd8b15f);
this.renderPreviewMetric(left + 10 + metricWidth + metricGap, metricTop, metricWidth, 'hit', '명중', `+${preview.hitBonus}`, accent);
this.renderPreviewMetric(left + 10 + (metricWidth + metricGap) * 2, metricTop, metricWidth, 'critical', '치명', `+${preview.criticalBonus}`, 0xd8732c);
}
@@ -4928,8 +5009,8 @@ export class BattleScene extends Phaser.Scene {
const maxBadgeRight = left + width - 10;
let badgeX = left + 10;
badgeX = this.renderPreviewBadge(badgeX, badgeY, this.usableIcon(preview.usable), this.usableEffectLabel(preview.usable), maxBadgeRight);
badgeX = this.renderPreviewBadge(badgeX, badgeY, 'move', `사거리 ${preview.usable.range}`, maxBadgeRight);
badgeX = this.renderPreviewBadge(badgeX, badgeY, 'success', preview.usable.command === 'item' ? '도구 사용' : '책략 사용', maxBadgeRight);
badgeX = this.renderPreviewBadge(badgeX, badgeY, this.usableTargetIcon(preview.usable), `${this.usableTargetLabel(preview.usable)} ${preview.usable.range}`, maxBadgeRight);
badgeX = this.renderPreviewBadge(badgeX, badgeY, preview.usable.command === 'item' ? 'accessory' : 'strategy', this.usableChannelLabel(preview.usable), maxBadgeRight);
if (preview.usable.effect === 'focus') {
this.renderPreviewBadge(badgeX, badgeY, 'leadership', `${preview.duration}`, maxBadgeRight);
}
@@ -5019,7 +5100,7 @@ export class BattleScene extends Phaser.Scene {
row.setOrigin(0);
row.setStrokeStyle(1, accent, 0.48);
const isHeal = preview.usable.effect === 'heal';
this.trackSideIcon(x + 14, y + 12, isHeal ? 'heal' : 'focus', 20);
this.trackSideIcon(x + 14, y + 12, this.usablePowerIcon(preview.usable), 20);
this.trackSideObject(this.add.text(x + 26, y + 5, isHeal ? '적용 후 HP' : '강화 결과', {
fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif',
fontSize: '11px',
@@ -5054,7 +5135,7 @@ export class BattleScene extends Phaser.Scene {
}
private renderPreviewBadge(x: number, y: number, icon: BattleUiIconKey, label: string, maxRight?: number) {
const width = Phaser.Math.Clamp(label.length * 8 + 48, 76, 146);
const width = Phaser.Math.Clamp(label.length * 7 + 44, 68, 136);
if (maxRight && x + width > maxRight) {
return x;
}
@@ -7987,10 +8068,11 @@ export class BattleScene extends Phaser.Scene {
const outcomeIcon = result.action === 'strategy' ? 'success' : result.critical ? 'critical' : 'hit';
const outcomeLabel = result.action === 'strategy' ? (result.hit ? '성공' : '실패') : result.hit ? (result.critical ? '치명' : '명중') : '회피';
this.renderCombatRibbonBadge(x + 46, y + 38, 'advantage', result.matchupLabel, depth + 1, 78);
this.renderCombatRibbonBadge(x + 46, y + 38, this.actionIcon(result), this.previewActionTypeLabel(result), depth + 1, 78);
this.renderCombatRibbonBadge(x + 130, y + 38, outcomeIcon, outcomeLabel, depth + 1, 62);
this.renderCombatRibbonBadge(x + 198, y + 38, 'advantage', result.matchupLabel, depth + 1, 76);
if (result.counter) {
this.renderCombatRibbonBadge(x + 198, y + 38, 'counter', '반격', depth + 1, 58);
this.renderCombatRibbonBadge(x + 280, y + 38, 'counter', '반격', depth + 1, 58);
}
}
@@ -8145,7 +8227,7 @@ export class BattleScene extends Phaser.Scene {
const targetFrame = this.trackCombatObject(this.add.rectangle(x + 256, y + 22, 34, 34, 0x16212d, 0.9));
targetFrame.setDepth(depth + 1);
targetFrame.setStrokeStyle(1, 0x647485, 0.58);
this.trackCombatIcon(x + 256, y + 22, result.usable.effect === 'heal' ? 'heal' : 'focus', 30, depth + 2);
this.trackCombatIcon(x + 256, y + 22, this.usablePowerIcon(result.usable), 30, depth + 2);
const actionLabel = this.trackCombatObject(this.add.text(x + 46, y + 9, result.usable.name, {
fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif',
@@ -8201,7 +8283,7 @@ export class BattleScene extends Phaser.Scene {
this.renderCombatRibbonBadge(x + 46, y + 38, this.usableIcon(result.usable), this.usableEffectLabel(result.usable), depth + 1, 68);
this.renderCombatRibbonBadge(x + 120, y + 38, 'success', '적용', depth + 1, 58);
this.renderCombatRibbonBadge(x + 184, y + 38, 'move', `거리 ${result.usable.range}`, depth + 1, 66);
this.renderCombatRibbonBadge(x + 184, y + 38, this.usableTargetIcon(result.usable), `${this.usableTargetLabel(result.usable)} ${result.usable.range}`, depth + 1, 78);
}
private renderCombatStatusPanel(unit: UnitData, x: number, y: number, width: number, growth?: CharacterGrowthResult) {
@@ -11244,7 +11326,8 @@ export class BattleScene extends Phaser.Scene {
fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif',
fontSize: '14px',
color: isTreasure ? '#f4dfad' : '#d4dce6',
fontStyle: isTreasure ? '700' : '400'
fontStyle: isTreasure ? '700' : '400',
fixedWidth: width - 214
}));
const bonusText = this.itemBonusText(item);
@@ -11348,22 +11431,7 @@ export class BattleScene extends Phaser.Scene {
}
private usableIcon(usable: BattleUsable): BattleUiIconKey {
if (usable.command === 'item') {
return usable.effect === 'heal' ? 'heal' : 'accessory';
}
if (usable.effect === 'heal') {
return 'heal';
}
if (usable.effect === 'focus') {
return 'focus';
}
if (usable.id === 'fireTactic') {
return 'fire';
}
if (usable.id.toLowerCase().includes('roar')) {
return 'shout';
}
return 'strategy';
return this.usableVisualStyle(usable).icon;
}
private actionIcon(preview: CombatPreview): BattleUiIconKey {
@@ -11378,9 +11446,9 @@ export class BattleScene extends Phaser.Scene {
private itemBonusText(item: ReturnType<typeof getItem>) {
const parts = [
item.attackBonus ? `공+${item.attackBonus}` : '',
item.defenseBonus ? `방+${item.defenseBonus}` : '',
item.strategyBonus ? `책+${item.strategyBonus}` : ''
item.attackBonus ? `+${item.attackBonus}` : '',
item.defenseBonus ? `+${item.defenseBonus}` : '',
item.strategyBonus ? `+${item.strategyBonus}` : ''
].filter(Boolean);
return parts.length > 0 ? parts.join(' ') : '-';
}