Improve combat result feedback

This commit is contained in:
2026-07-03 05:16:50 +09:00
parent ac38f234a3
commit 9b6dad4016

View File

@@ -7729,27 +7729,27 @@ export class BattleScene extends Phaser.Scene {
const visibleGains = gains.filter((gain) => gain.amount > 0).slice(0, 5);
let cursorX = x;
visibleGains.forEach((gain) => {
const chipWidth = Phaser.Math.Clamp(gain.label.length * 10 + 52, 72, 96);
const chipWidth = Phaser.Math.Clamp(gain.label.length * 11 + 62, 86, 116);
if (cursorX + chipWidth > x + maxWidth) {
return;
}
const bg = this.add.rectangle(cursorX, y, chipWidth, 30, 0x0b1118, 0.92);
const bg = this.add.rectangle(cursorX, y, chipWidth, 34, 0x0b1118, 0.94);
bg.setOrigin(0);
bg.setStrokeStyle(1, 0x53606c, 0.62);
const icon = this.createBattleUiIcon(cursorX + 16, y + 15, gain.icon, 24);
const text = this.add.text(cursorX + 32, y + 7, `+${gain.amount}`, {
const icon = this.createBattleUiIcon(cursorX + 18, y + 17, gain.icon, 28);
const text = this.add.text(cursorX + 36, y + 7, `+${gain.amount}`, {
fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif',
fontSize: '13px',
fontSize: '15px',
color: '#a8ffd0',
fontStyle: '700'
});
const label = this.add.text(cursorX + 55, y + 8, gain.label, {
const label = this.add.text(cursorX + 65, y + 9, gain.label, {
fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif',
fontSize: '11px',
fontSize: '12px',
color: '#d4dce6'
});
container.add([bg, icon, text, label]);
cursorX += chipWidth + 5;
cursorX += chipWidth + 6;
});
}
@@ -8271,13 +8271,14 @@ export class BattleScene extends Phaser.Scene {
}
private formatCombatResult(result: CombatResult) {
const outcome = this.formatCombatOutcome(result);
const defeated = result.defeated ? `\n${result.defender.name} 퇴각!` : `\n${result.defender.name} HP ${result.defender.hp}/${result.defender.maxHp}`;
const bondBonus = result.bondDamageBonus > 0 && result.bondLabel ? `\n공명 효과 ${result.bondLabel}: 피해 +${result.bondDamageBonus}%` : '';
const equipment = result.equipmentEffectLabels.length > 0 ? `\n보물 효과: ${result.equipmentEffectLabels.join(', ')}` : '';
const bond = result.bondExp > 0 ? `\n공명 경험치 +${result.bondExp}` : '';
const counter = result.counter ? `\n${this.formatCounterLine(result.counter)}` : '';
return `${result.attacker.name} ${result.usable?.name ?? commandLabels[result.action]}\n${outcome}${defeated}${bondBonus}${equipment}\n${this.formatCharacterGrowth(result.characterGrowth)}\n${this.formatEquipmentGrowth(result.attackerGrowth)}\n${this.formatEquipmentGrowth(result.defenderGrowth)}${bond}${counter}`;
const summaryOutcome = this.formatCombatOutcome(result);
const summaryDefeated = result.defeated ? `\n${result.defender.name} 격파!` : `\n${result.defender.name} HP ${result.defender.hp}/${result.defender.maxHp}`;
const summaryBondBonus = result.bondDamageBonus > 0 && result.bondLabel ? `\n공명 효과 ${result.bondLabel}: 피해 +${result.bondDamageBonus}%` : '';
const summaryEquipment = result.equipmentEffectLabels.length > 0 ? `\n장비 효과: ${result.equipmentEffectLabels.join(', ')}` : '';
const summaryBond = result.bondExp > 0 ? `\n공명 경험치 +${result.bondExp}` : '';
const summaryCounter = result.counter ? `\n${this.formatCounterLine(result.counter)}` : '';
return `${result.attacker.name} ${result.usable?.name ?? commandLabels[result.action]}\n${summaryOutcome}${summaryDefeated}${summaryBondBonus}${summaryEquipment}\n${this.formatCharacterGrowth(result.characterGrowth)}\n${this.formatEquipmentGrowth(result.attackerGrowth)}\n${this.formatEquipmentGrowth(result.defenderGrowth)}${summaryBond}${summaryCounter}`;
}
private formatCombatOutcome(result: CombatResult) {
@@ -8285,17 +8286,45 @@ export class BattleScene extends Phaser.Scene {
return `${result.defender.name}이 공격을 회피했습니다.`;
}
const summaryCritical = result.critical ? '치명타! ' : '';
return `${summaryCritical}${result.defender.name}에게 ${result.damage} 피해`;
if (!result.hit) {
return `${result.defender.name}이 공격을 회피했습니다.`;
}
const critical = result.critical ? '치명타! ' : '';
return `${critical}${result.defender.name}에게 ${result.damage} 피해`;
}
private formatCounterLine(result: CombatResult) {
const counterOutcome = result.hit ? `${result.critical ? '치명타로 ' : ''}${result.damage} 피해` : '회피';
const counterDefeated = result.defeated ? ` / ${result.defender.name} 격파` : ` / ${result.defender.name} HP ${result.defender.hp}/${result.defender.maxHp}`;
return `반격: ${result.attacker.name} -> ${result.defender.name} ${counterOutcome}${counterDefeated}`;
const outcome = result.hit ? `${result.critical ? '치명타로 ' : ''}${result.damage} 피해` : '회피됨';
const defeated = result.defeated ? ` / ${result.defender.name} 퇴각` : ` / ${result.defender.name} HP ${result.defender.hp}/${result.defender.maxHp}`;
return `반격(감소): ${result.attacker.name} -> ${result.defender.name} ${outcome}${defeated}`;
}
private combatGrowthLines(result: CombatResult) {
const summaryVerdict = result.hit ? `${result.critical ? '치명타 / ' : ''}명중 ${result.hitRate}%` : `회피 / 명중 ${result.hitRate}%`;
const summaryLines = [
summaryVerdict,
this.formatCharacterGrowth(result.characterGrowth),
this.formatEquipmentGrowth(result.attackerGrowth),
this.formatEquipmentGrowth(result.defenderGrowth)
];
if (result.bondExp > 0) {
summaryLines.push(`공명 경험치 +${result.bondExp}`);
}
if (result.equipmentEffectLabels.length > 0) {
summaryLines.push(`장비 효과: ${result.equipmentEffectLabels.join(', ')}`);
}
return summaryLines;
const verdict = result.hit ? `${result.critical ? '치명타 / ' : ''}명중 ${result.hitRate}%` : `회피됨 / 명중 ${result.hitRate}%`;
const lines = [
verdict,
@@ -8315,6 +8344,12 @@ export class BattleScene extends Phaser.Scene {
}
private formatSupportResult(result: SupportResult) {
const supportEffect =
result.usable.effect === 'heal'
? `${result.target.name} 병력 +${result.healAmount} (${result.target.hp}/${result.target.maxHp})`
: `${result.target.name} 강화: 공격 +${result.buff?.attackBonus ?? 0} / 명중 +${result.buff?.hitBonus ?? 0} / 치명 +${result.buff?.criticalBonus ?? 0}`;
return `${result.user.name} ${result.usable.name}\n${supportEffect}\n${this.formatCharacterGrowth(result.characterGrowth)}\n${this.formatEquipmentGrowth(result.equipmentGrowth)}`;
const effect =
result.usable.effect === 'heal'
? `${result.target.name} 병력 +${result.healAmount} (${result.target.hp}/${result.target.maxHp})`
@@ -9574,7 +9609,8 @@ export class BattleScene extends Phaser.Scene {
stage.fillRect(left + 18, top + 82, panelWidth - 36, 18);
const titleText = result.isCounter ? '반격' : result.usable?.name ?? commandLabels[result.action];
const title = this.trackCombatObject(this.add.text(left + panelWidth / 2, top + 24, titleText, {
const displayTitleText = result.isCounter ? '반격' : titleText;
const title = this.trackCombatObject(this.add.text(left + panelWidth / 2, top + 24, displayTitleText, {
fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif',
fontSize: '24px',
color: '#f4dfad',
@@ -9625,7 +9661,7 @@ export class BattleScene extends Phaser.Scene {
} else {
this.showCombatMiss(defenderX, groundY - 70, depth + 14);
}
const outcomeCard = this.renderCombatOutcomeCard(result, left + panelWidth / 2 - 198, top + 214, 396, depth + 16);
const outcomeCard = this.renderCombatOutcomeCard(result, left + panelWidth / 2 - 226, top + 196, 452, depth + 16);
await this.animateCombatGauge(defenderStatus.hpFill, result.previousDefenderHp / result.defender.maxHp, result.defender.hp / result.defender.maxHp, 420);
defenderStatus.hpText.setText(`${result.defender.hp} / ${result.defender.maxHp}`);
@@ -9660,7 +9696,117 @@ export class BattleScene extends Phaser.Scene {
this.hideCombatCutIn();
}
private renderCombatOutcomeSummaryCard(result: CombatResult, x: number, y: number, width: number, depth: number) {
const height = 84;
const isStrategy = result.action === 'strategy';
const icon: BattleUiIconKey = result.defeated
? 'critical'
: !result.hit
? 'move'
: result.critical
? 'critical'
: isStrategy
? 'success'
: 'hit';
const title = result.defeated
? '격파'
: !result.hit
? isStrategy
? '책략 실패'
: '회피'
: result.critical
? '치명타'
: isStrategy
? '책략 성공'
: '명중';
const tone = result.defeated || result.critical ? 0xd8732c : result.hit ? this.previewAccentColor(result) : 0x83d6ff;
const container = this.trackCombatObject(this.add.container(x, y));
container.setDepth(depth);
container.setAlpha(0);
container.setScale(0.96);
const bg = this.add.rectangle(0, 0, width, height, 0x0b121a, 0.98);
bg.setOrigin(0);
bg.setStrokeStyle(2, tone, 0.88);
const iconFrame = this.add.rectangle(42, height / 2, 64, 64, 0x101820, 0.94);
iconFrame.setStrokeStyle(1, tone, 0.72);
const iconImage = this.createBattleUiIcon(42, height / 2, icon, 58);
const titleText = this.add.text(84, 10, title, {
fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif',
fontSize: '23px',
color: result.hit ? '#fff2b8' : '#d9f1ff',
fontStyle: '700',
fixedWidth: 150
});
const detail = result.defeated ? `${result.defender.name} 전투 불능` : `${result.defender.name} HP ${result.defender.hp}/${result.defender.maxHp}`;
const detailText = this.add.text(86, 42, detail, {
fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif',
fontSize: '13px',
color: result.defeated ? '#ffb6a6' : '#c8d2dd',
fontStyle: '700',
fixedWidth: 190
});
const value = result.hit ? `-${result.damage}` : '0';
const valueText = this.add.text(width - 18, 9, value, {
fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif',
fontSize: '31px',
color: result.hit ? '#ffdf7b' : '#9fb0bf',
fontStyle: '700'
});
valueText.setOrigin(1, 0);
const valueLabel = this.add.text(width - 18, 47, '피해', {
fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif',
fontSize: '12px',
color: '#9fb0bf',
fontStyle: '700'
});
valueLabel.setOrigin(1, 0);
container.add([bg, iconFrame, iconImage, titleText, detailText, valueText, valueLabel]);
const badgeY = 64;
this.addCombatOutcomeChip(container, 86, badgeY, result.action === 'strategy' ? 'success' : 'hit', result.hit ? `${result.hitRate}%` : '실패', tone);
this.addCombatOutcomeChip(container, 154, badgeY, 'critical', result.critical ? '치명' : `${result.criticalRate}%`, 0xd8732c);
this.addCombatOutcomeChip(container, 222, badgeY, 'advantage', result.matchupLabel, palette.blue, 86);
if (result.bondDamageBonus > 0) {
this.addCombatOutcomeChip(container, width - 114, badgeY, 'leadership', `공명 +${result.bondDamageBonus}%`, palette.gold, 96);
}
this.tweens.add({ targets: container, alpha: 1, scale: 1, y: y - 4, duration: 160, ease: 'Sine.easeOut' });
return container;
}
private addCombatOutcomeChip(
container: Phaser.GameObjects.Container,
x: number,
y: number,
icon: BattleUiIconKey,
label: string,
tone: number,
width = 62
) {
const bg = this.add.rectangle(x, y, width, 22, 0x101820, 0.94);
bg.setOrigin(0, 0.5);
bg.setStrokeStyle(1, tone, 0.54);
const chipIcon = this.createBattleUiIcon(x + 13, y, icon, 19);
const text = this.add.text(x + 27, y - 6, this.truncateUiText(label, width > 70 ? 8 : 5), {
fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif',
fontSize: '10px',
color: '#f2e3bf',
fontStyle: '700',
fixedWidth: width - 30
});
container.add([bg, chipIcon, text]);
}
private renderCombatOutcomeCard(result: CombatResult, x: number, y: number, width: number, depth: number) {
return this.renderCombatOutcomeSummaryCard(result, x, y, width, depth);
const height = 66;
const isStrategy = result.action === 'strategy';
const icon: BattleUiIconKey = result.defeated
@@ -9759,7 +9905,86 @@ export class BattleScene extends Phaser.Scene {
await this.delay(170);
}
private renderCombatActionSummaryRibbon(result: CombatResult, x: number, y: number, width: number, depth: number) {
const accent = this.previewAccentColor(result);
const bg = this.trackCombatObject(this.add.rectangle(x, y, width, 64, 0x0b1118, 0.94));
bg.setOrigin(0);
bg.setDepth(depth);
bg.setStrokeStyle(1, accent, 0.72);
const actionFrame = this.trackCombatObject(this.add.rectangle(x + 25, y + 23, 42, 42, 0x101820, 0.9));
actionFrame.setDepth(depth + 1);
actionFrame.setStrokeStyle(1, accent, 0.68);
this.trackCombatIcon(x + 25, y + 23, this.actionIcon(result), 37, depth + 2);
const attackerWeapon = getItem(result.attacker.equipment.weapon.itemId);
const defenderArmor = getItem(result.defender.equipment.armor.itemId);
const dealtDamage = result.hit ? result.damage : 0;
this.renderCombatRibbonEquipment(x + 120, y + 8, this.itemIcon(attackerWeapon, 'weapon'), attackerWeapon.name, '공격 장비', depth + 1);
this.renderCombatRibbonEquipment(x + 250, y + 8, this.itemIcon(defenderArmor, 'armor'), defenderArmor.name, '방어 장비', depth + 1);
const actionLabel = this.trackCombatObject(this.add.text(x + 46, y + 9, result.usable?.name ?? commandLabels[result.action], {
fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif',
fontSize: '15px',
color: '#f4dfad',
fontStyle: '700',
fixedWidth: 72
}));
actionLabel.setDepth(depth + 1);
const damageText = this.trackCombatObject(this.add.text(x + width - 14, y + 8, `피해 ${dealtDamage}`, {
fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif',
fontSize: '15px',
color: result.hit ? '#ffcf91' : '#9fb0bf',
fontStyle: '700'
}));
damageText.setOrigin(1, 0);
damageText.setDepth(depth + 1);
const outcomeIcon = result.action === 'strategy' ? 'success' : result.critical ? 'critical' : result.hit ? 'hit' : 'move';
const outcomeLabel = result.action === 'strategy' ? (result.hit ? '성공' : '실패') : result.hit ? (result.critical ? '치명' : '명중') : '회피';
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 + 280, y + 38, 'counter', '반격', depth + 1, 58);
}
}
private renderCombatRibbonEquipment(
x: number,
y: number,
icon: BattleUiIconKey,
itemName: string,
role: string,
depth: number
) {
const frame = this.trackCombatObject(this.add.rectangle(x + 14, y + 14, 34, 34, 0x16212d, 0.9));
frame.setDepth(depth);
frame.setStrokeStyle(1, 0x647485, 0.58);
this.trackCombatIcon(x + 14, y + 14, icon, 30, depth + 1);
const label = this.trackCombatObject(this.add.text(x + 34, y - 1, this.truncateUiText(itemName, 7), {
fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif',
fontSize: '12px',
color: '#d4dce6',
fixedWidth: 84
}));
label.setDepth(depth);
const roleText = this.trackCombatObject(this.add.text(x + 34, y + 17, role, {
fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif',
fontSize: '10px',
color: '#9fb0bf',
fixedWidth: 84
}));
roleText.setDepth(depth);
}
private renderCombatActionRibbon(result: CombatResult, x: number, y: number, width: number, depth: number) {
this.renderCombatActionSummaryRibbon(result, x, y, width, depth);
return;
const accent = this.previewAccentColor(result);
const bg = this.trackCombatObject(this.add.rectangle(x, y, width, 64, 0x0b1118, 0.92));
bg.setOrigin(0);
@@ -9937,7 +10162,7 @@ export class BattleScene extends Phaser.Scene {
const growthEntries = this.supportGrowthEntries(result);
await this.delay(250);
const outcomeCard = this.renderSupportOutcomeCard(result, left + panelWidth / 2 - 190, top + 116, 380, depth + 5);
const outcomeCard = this.renderSupportOutcomeCard(result, left + panelWidth / 2 - 216, top + 108, 432, depth + 5);
await this.animateCombatGauge(targetStatus.hpFill, result.previousTargetHp / result.target.maxHp, result.target.hp / result.target.maxHp, 480);
targetStatus.hpText.setText(`${result.target.hp} / ${result.target.maxHp}`);
@@ -9956,7 +10181,72 @@ export class BattleScene extends Phaser.Scene {
this.hideCombatCutIn();
}
private renderSupportOutcomeSummaryCard(result: SupportResult, x: number, y: number, width: number, depth: number) {
const height = 84;
const heal = result.usable.effect === 'heal';
const tone = heal ? 0x59d18c : 0xd8b15f;
const container = this.trackCombatObject(this.add.container(x, y));
container.setDepth(depth);
container.setAlpha(0);
container.setScale(0.96);
const bg = this.add.rectangle(0, 0, width, height, 0x0b121a, 0.98);
bg.setOrigin(0);
bg.setStrokeStyle(2, tone, 0.88);
const iconFrame = this.add.rectangle(42, height / 2, 64, 64, 0x101820, 0.94);
iconFrame.setStrokeStyle(1, tone, 0.72);
const iconImage = this.createBattleUiIcon(42, height / 2, this.usablePowerIcon(result.usable), 58);
const titleText = this.add.text(84, 10, heal ? '병력 회복' : '강화 적용', {
fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif',
fontSize: '23px',
color: heal ? '#a8ffd0' : '#fff2b8',
fontStyle: '700',
fixedWidth: 156
});
const detail = heal
? `${result.target.name} HP ${result.previousTargetHp}${result.target.hp}`
: `${result.target.name} 공+${result.buff?.attackBonus ?? 0} 명+${result.buff?.hitBonus ?? 0} 치+${result.buff?.criticalBonus ?? 0}`;
const detailText = this.add.text(86, 42, detail, {
fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif',
fontSize: '13px',
color: '#d4dce6',
fontStyle: '700',
fixedWidth: 218
});
const value = heal ? `+${result.healAmount}` : `${result.buff?.turns ?? result.usable.duration ?? 1}`;
const valueText = this.add.text(width - 18, 9, value, {
fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif',
fontSize: '31px',
color: heal ? '#a8ffd0' : '#ffdf7b',
fontStyle: '700'
});
valueText.setOrigin(1, 0);
const valueLabel = this.add.text(width - 18, 47, heal ? '회복' : '턴', {
fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif',
fontSize: '12px',
color: '#9fb0bf',
fontStyle: '700'
});
valueLabel.setOrigin(1, 0);
container.add([bg, iconFrame, iconImage, titleText, detailText, valueText, valueLabel]);
const badgeY = 64;
this.addCombatOutcomeChip(container, 86, badgeY, this.usableIcon(result.usable), this.usableEffectLabel(result.usable), tone, 78);
this.addCombatOutcomeChip(container, 170, badgeY, 'success', '적용', 0x59d18c, 62);
this.addCombatOutcomeChip(container, 238, badgeY, this.usableTargetIcon(result.usable), `${this.usableTargetLabel(result.usable)} ${result.usable.range}`, palette.blue, 92);
this.tweens.add({ targets: container, alpha: 1, scale: 1, y: y - 4, duration: 160, ease: 'Sine.easeOut' });
return container;
}
private renderSupportOutcomeCard(result: SupportResult, x: number, y: number, width: number, depth: number) {
return this.renderSupportOutcomeSummaryCard(result, x, y, width, depth);
const height = 66;
const heal = result.usable.effect === 'heal';
const tone = heal ? 0x59d18c : 0xd8b15f;
@@ -10007,7 +10297,61 @@ export class BattleScene extends Phaser.Scene {
return container;
}
private renderSupportActionSummaryRibbon(result: SupportResult, x: number, y: number, width: number, depth: number) {
const accent = this.usableAccentColor(result.usable);
const bg = this.trackCombatObject(this.add.rectangle(x, y, width, 64, 0x0b1118, 0.94));
bg.setOrigin(0);
bg.setDepth(depth);
bg.setStrokeStyle(1, accent, 0.72);
const actionFrame = this.trackCombatObject(this.add.rectangle(x + 25, y + 23, 42, 42, 0x101820, 0.9));
actionFrame.setDepth(depth + 1);
actionFrame.setStrokeStyle(1, accent, 0.68);
this.trackCombatIcon(x + 25, y + 23, this.usableIcon(result.usable), 37, depth + 2);
const sourceItem = getItem(result.usable.command === 'strategy' ? result.user.equipment.weapon.itemId : result.user.equipment.accessory.itemId);
const sourceSlot: EquipmentSlot = result.usable.command === 'strategy' ? 'weapon' : 'accessory';
this.renderCombatRibbonEquipment(
x + 120,
y + 8,
this.itemIcon(sourceItem, sourceSlot),
sourceItem.name,
result.usable.command === 'strategy' ? '책략 도구' : '소모 도구',
depth + 1
);
this.renderCombatRibbonEquipment(x + 254, y + 8, this.usablePowerIcon(result.usable), result.target.name, this.usableTargetLabel(result.usable), depth + 1);
const actionLabel = this.trackCombatObject(this.add.text(x + 46, y + 9, result.usable.name, {
fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif',
fontSize: '15px',
color: '#f4dfad',
fontStyle: '700',
fixedWidth: 72
}));
actionLabel.setDepth(depth + 1);
const resultLabel =
result.usable.effect === 'heal'
? `회복 +${result.healAmount}`
: `강화 ${result.buff?.turns ?? result.usable.duration ?? 1}`;
const resultText = this.trackCombatObject(this.add.text(x + width - 14, y + 8, resultLabel, {
fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif',
fontSize: '15px',
color: result.usable.effect === 'heal' ? '#a8ffd0' : '#ffdf7b',
fontStyle: '700'
}));
resultText.setOrigin(1, 0);
resultText.setDepth(depth + 1);
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, this.usableTargetIcon(result.usable), `${this.usableTargetLabel(result.usable)} ${result.usable.range}`, depth + 1, 78);
}
private renderSupportActionRibbon(result: SupportResult, x: number, y: number, width: number, depth: number) {
this.renderSupportActionSummaryRibbon(result, x, y, width, depth);
return;
const accent = this.usableAccentColor(result.usable);
const bg = this.trackCombatObject(this.add.rectangle(x, y, width, 64, 0x0b1118, 0.92));
bg.setOrigin(0);
@@ -10121,6 +10465,7 @@ export class BattleScene extends Phaser.Scene {
fontStyle: '700'
}));
hpLabel.setDepth(85);
hpLabel.setText('병력');
const hpTrack = this.trackCombatObject(this.add.rectangle(x + 148, y + 54, width - 232, 9, 0x0a0f14, 0.9));
hpTrack.setOrigin(0, 0.5);
hpTrack.setDepth(85);
@@ -10145,6 +10490,7 @@ export class BattleScene extends Phaser.Scene {
fontStyle: '700'
}));
expLabel.setDepth(85);
expLabel.setText('경험');
const expTrack = this.trackCombatObject(this.add.rectangle(x + 148, y + 84, width - 232, 9, 0x0a0f14, 0.9));
expTrack.setOrigin(0, 0.5);
expTrack.setDepth(85);
@@ -10199,6 +10545,22 @@ export class BattleScene extends Phaser.Scene {
color: 0xd8b15f,
statGains: growth.statGains
};
return {
kind: 'character',
label: '장수 경험치',
owner: unit.name,
amountLabel: `+${growth.amount}`,
previousLevel: growth.previousLevel,
previousExp: growth.previousExp,
previousNext: 100,
level: growth.level,
exp: growth.exp,
next: growth.next,
leveled: growth.leveled,
color: 0xd8b15f,
statGains: growth.statGains
};
}
private equipmentGrowthEntry(unit: UnitData, growth: EquipmentGrowthResult): GrowthGaugeEntry {
@@ -10234,6 +10596,21 @@ export class BattleScene extends Phaser.Scene {
leveled: growth.leveled,
color: 0x83d6ff
};
return {
kind: 'bond',
label: '공명 경험치',
owner: growth.label,
amountLabel: `+${growth.amount}`,
previousLevel: growth.previousLevel,
previousExp: growth.previousExp,
previousNext: 100,
level: growth.level,
exp: growth.exp,
next: growth.next,
leveled: growth.leveled,
color: 0x83d6ff
};
}
private growthCelebrant(result: CombatResult) {
@@ -10273,7 +10650,8 @@ export class BattleScene extends Phaser.Scene {
panel.setDepth(config.depth);
panel.setStrokeStyle(2, palette.gold, 0.82);
const title = this.trackCombatObject(this.add.text(config.x + 16, config.y + 12, config.title, {
const displayTitle = config.title === '성장 결과' ? config.title : '성장 결과';
const title = this.trackCombatObject(this.add.text(config.x + 16, config.y + 12, displayTitle, {
fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif',
fontSize: '16px',
color: '#f4dfad',
@@ -10342,6 +10720,7 @@ export class BattleScene extends Phaser.Scene {
fontStyle: '700'
});
levelText.setOrigin(celebrant ? 0 : 0.5, 0);
levelText.setText(`Lv ${entry.previousLevel} → Lv ${entry.level}`);
const statText = this.add.text(celebrant ? contentX : 0, 16, entry.statGains ? '능력 상승' : `${entry.label} 효과 강화`, {
fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif',
fontSize: '16px',
@@ -10351,6 +10730,7 @@ export class BattleScene extends Phaser.Scene {
strokeThickness: 3
});
statText.setOrigin(celebrant ? 0 : 0.5, 0);
statText.setText(entry.statGains ? '능력 상승' : `${entry.label} 강화`);
container.add([bg, visualFrame, glow, title, levelText, statText]);
if (entry.statGains) {
this.addStatGainChipsToContainer(container, entry.statGains, celebrant ? contentX : -panelWidth / 2 + 44, 50, panelWidth - 132);
@@ -11024,6 +11404,7 @@ export class BattleScene extends Phaser.Scene {
lineSpacing: 2
}));
text.setOrigin(0.5);
text.setText(`공명 효과 ${result.bondLabel}\n피해 +${result.bondDamageBonus}%`);
text.setDepth(depth + 2);
this.tweens.add({ targets: aura, scale: 1.7, alpha: 0, duration: 560, ease: 'Sine.easeOut' });
@@ -11065,6 +11446,7 @@ export class BattleScene extends Phaser.Scene {
strokeThickness: 4
}));
miss.setOrigin(0.5);
miss.setText('회피');
miss.setDepth(depth);
this.tweens.add({
targets: miss,