Improve battle preview readability
This commit is contained in:
@@ -5337,12 +5337,13 @@ 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 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)} · 치명 ${preview.criticalRate}%`,
|
||||
secondary: `${preview.hitRate}% ${this.previewSuccessLabel(preview)} · ${counterLabel}`,
|
||||
tone: this.previewAccentColor(preview)
|
||||
};
|
||||
});
|
||||
@@ -6178,85 +6179,86 @@ export class BattleScene extends Phaser.Scene {
|
||||
return '도구';
|
||||
}
|
||||
|
||||
private renderCombatPreviewCard(preview: CombatPreview, locked = false) {
|
||||
private renderCombatPreviewSummaryCard(preview: CombatPreview, locked = false) {
|
||||
const { panelX, panelWidth } = this.layout;
|
||||
const left = panelX + 24;
|
||||
const width = panelWidth - 48;
|
||||
const height = locked ? 438 : 402;
|
||||
const y = this.sideContentTop();
|
||||
const accent = this.previewAccentColor(preview);
|
||||
const defenderClass = getUnitClass(preview.defender.classKey);
|
||||
const attackerEquipmentLabel = preview.action === 'strategy' ? '책략 장비' : preview.action === 'item' ? '사용 장비' : '공격 장비';
|
||||
const distance = this.tileDistance(preview.attacker, preview.defender);
|
||||
const range = this.actionRange(preview.attacker, preview.action, preview.usable);
|
||||
const attackerWeapon = getItem(preview.attacker.equipment.weapon.itemId);
|
||||
const defenderArmor = getItem(preview.defender.equipment.armor.itemId);
|
||||
const sourceIcon = preview.usable ? this.usableIcon(preview.usable) : this.itemIcon(attackerWeapon, 'weapon');
|
||||
const sourceLabel = preview.usable ? this.usableChannelLabel(preview.usable) : attackerEquipmentLabel;
|
||||
const sourceName = preview.usable?.name ?? attackerWeapon.name;
|
||||
const sourceLabel = preview.usable ? this.usableChannelLabel(preview.usable) : preview.action === 'strategy' ? '책략 장비' : '공격 장비';
|
||||
const counterTone = preview.counterAvailable ? 0xb64a45 : 0x59d18c;
|
||||
|
||||
const bg = this.trackSideObject(this.add.rectangle(left, y, width, height, 0x121a22, 1));
|
||||
bg.setOrigin(0);
|
||||
bg.setStrokeStyle(2, accent, 0.78);
|
||||
bg.setStrokeStyle(2, accent, locked ? 0.95 : 0.78);
|
||||
|
||||
const actionFrame = this.trackSideObject(this.add.rectangle(left + 40, y + 40, 72, 72, 0x0a0f14, 0.94));
|
||||
actionFrame.setStrokeStyle(1, accent, 0.76);
|
||||
this.trackSideIcon(left + 40, y + 40, this.actionIcon(preview), 64);
|
||||
this.trackSideObject(this.add.text(left + 86, y + 8, `${preview.attacker.name} → ${preview.defender.name}`, {
|
||||
const actionFrame = this.trackSideObject(this.add.rectangle(left + 42, y + 42, 74, 74, 0x0a0f14, 0.95));
|
||||
actionFrame.setStrokeStyle(1, accent, 0.82);
|
||||
this.trackSideIcon(left + 42, y + 42, this.actionIcon(preview), 66);
|
||||
|
||||
this.trackSideObject(this.add.text(left + 88, y + 9, `${preview.attacker.name} → ${preview.defender.name}`, {
|
||||
fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif',
|
||||
fontSize: '20px',
|
||||
color: '#f4dfad',
|
||||
fontStyle: '700',
|
||||
fixedWidth: width - 96
|
||||
fixedWidth: width - 98
|
||||
}));
|
||||
this.trackSideObject(this.add.text(left + 86, y + 36, `${this.previewActionLabel(preview)} · ${this.previewActionTypeLabel(preview)} · ${defenderClass.name}`, {
|
||||
this.trackSideObject(this.add.text(left + 88, y + 37, `${this.previewActionLabel(preview)} · ${this.previewActionTypeLabel(preview)}`, {
|
||||
fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif',
|
||||
fontSize: '13px',
|
||||
color: '#9fb0bf',
|
||||
color: '#d4dce6',
|
||||
fontStyle: '700',
|
||||
fixedWidth: width - 96
|
||||
fixedWidth: width - 98
|
||||
}));
|
||||
|
||||
this.trackSideObject(this.add.text(left + 86, y + 56, `거리 ${this.tileDistance(preview.attacker, preview.defender)} / 사거리 ${this.actionRange(preview.attacker, preview.action, preview.usable)} · ${locked ? '선택됨' : '대상 후보'}`, {
|
||||
this.trackSideObject(this.add.text(left + 88, y + 57, `거리 ${distance}/${range} · ${locked ? '선택됨' : '대상 후보'}`, {
|
||||
fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif',
|
||||
fontSize: '12px',
|
||||
color: locked ? '#f2e3bf' : '#c8d2dd',
|
||||
fixedWidth: width - 96
|
||||
color: locked ? '#f2e3bf' : '#9fb0bf',
|
||||
fixedWidth: width - 98
|
||||
}));
|
||||
|
||||
const chipGap = 8;
|
||||
const chipWidth = (width - 20 - chipGap) / 2;
|
||||
this.renderPreviewEquipmentChip(left + 10, y + 82, chipWidth, sourceIcon, sourceLabel, sourceName);
|
||||
this.renderPreviewEquipmentChip(left + 10 + chipWidth + chipGap, y + 82, chipWidth, this.itemIcon(defenderArmor, 'armor'), '대상 방어구', defenderArmor.name);
|
||||
|
||||
const metricGap = 8;
|
||||
const metricWidth = (width - 20 - metricGap) / 2;
|
||||
this.renderPreviewOutcomeCard(left + 10, y + 142, metricWidth, this.previewDamageIcon(preview), '예상 피해', `${preview.damage}`, 0xb64a45);
|
||||
this.renderPreviewOutcomeCard(
|
||||
const metricGap = 6;
|
||||
const metricWidth = (width - 20 - metricGap * 2) / 3;
|
||||
const metricTop = y + 90;
|
||||
this.renderPreviewDecisionCard(left + 10, metricTop, metricWidth, this.previewDamageIcon(preview), '예상 피해', `${preview.damage}`, 0xb64a45);
|
||||
this.renderPreviewDecisionCard(
|
||||
left + 10 + metricWidth + metricGap,
|
||||
y + 142,
|
||||
metricTop,
|
||||
metricWidth,
|
||||
preview.action === 'strategy' ? 'success' : 'hit',
|
||||
this.previewSuccessLabel(preview),
|
||||
`${preview.hitRate}%`,
|
||||
accent
|
||||
);
|
||||
this.renderPreviewOutcomeCard(left + 10, y + 206, metricWidth, 'critical', '치명', `${preview.criticalRate}%`, 0xd8732c);
|
||||
this.renderPreviewOutcomeCard(
|
||||
left + 10 + metricWidth + metricGap,
|
||||
y + 206,
|
||||
this.renderPreviewDecisionCard(
|
||||
left + 10 + (metricWidth + metricGap) * 2,
|
||||
metricTop,
|
||||
metricWidth,
|
||||
'counter',
|
||||
'반격',
|
||||
preview.counterAvailable ? '주의' : '없음',
|
||||
preview.counterAvailable ? 0xb64a45 : 0x59d18c
|
||||
counterTone
|
||||
);
|
||||
|
||||
this.renderPreviewForecastRow(preview, left + 10, y + 272, width - 20, accent);
|
||||
this.renderPreviewModifierCards(left + 10, y + 308, width - 20, this.previewModifierSummaries(preview), accent);
|
||||
const chipGap = 8;
|
||||
const chipWidth = (width - 20 - chipGap) / 2;
|
||||
this.renderPreviewEquipmentChip(left + 10, y + 184, chipWidth, sourceIcon, sourceLabel, sourceName);
|
||||
this.renderPreviewEquipmentChip(left + 10 + chipWidth + chipGap, y + 184, chipWidth, this.itemIcon(defenderArmor, 'armor'), '대상 방어구', defenderArmor.name);
|
||||
|
||||
const badgeY = y + 374;
|
||||
this.renderPreviewForecastRow(preview, left + 10, y + 238, width - 20, accent);
|
||||
this.renderPreviewModifierCards(left + 10, y + 278, width - 20, this.previewModifierSummaries(preview), accent);
|
||||
|
||||
const badgeY = y + 346;
|
||||
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, 'critical', `치명 ${preview.criticalRate}%`, maxBadgeRight);
|
||||
badgeX = this.renderPreviewBadge(badgeX, badgeY, 'advantage', preview.matchupLabel, maxBadgeRight);
|
||||
let detailBadgeX = this.renderPreviewBadge(badgeX, badgeY, 'terrain', preview.terrainLabel, maxBadgeRight);
|
||||
if (preview.bondDamageBonus > 0 && preview.bondLabel) {
|
||||
@@ -6270,66 +6272,63 @@ export class BattleScene extends Phaser.Scene {
|
||||
}
|
||||
}
|
||||
|
||||
private renderSupportPreviewCard(preview: SupportPreview, locked = false) {
|
||||
private renderSupportPreviewSummaryCard(preview: SupportPreview, locked = false) {
|
||||
const { panelX, panelWidth } = this.layout;
|
||||
const left = panelX + 24;
|
||||
const width = panelWidth - 48;
|
||||
const height = locked ? 438 : 402;
|
||||
const y = this.sideContentTop();
|
||||
const accent = this.usableAccentColor(preview.usable);
|
||||
const isHeal = preview.usable.effect === 'heal';
|
||||
const distance = this.tileDistance(preview.user, preview.target);
|
||||
|
||||
const bg = this.trackSideObject(this.add.rectangle(left, y, width, height, 0x121a22, 1));
|
||||
bg.setOrigin(0);
|
||||
bg.setStrokeStyle(2, accent, 0.78);
|
||||
bg.setStrokeStyle(2, accent, locked ? 0.95 : 0.78);
|
||||
|
||||
const actionFrame = this.trackSideObject(this.add.rectangle(left + 40, y + 40, 72, 72, 0x0a0f14, 0.94));
|
||||
actionFrame.setStrokeStyle(1, accent, 0.76);
|
||||
this.trackSideIcon(left + 40, y + 40, this.usableIcon(preview.usable), 64);
|
||||
this.trackSideObject(this.add.text(left + 86, y + 8, `${preview.user.name} → ${preview.target.name}`, {
|
||||
const actionFrame = this.trackSideObject(this.add.rectangle(left + 42, y + 42, 74, 74, 0x0a0f14, 0.95));
|
||||
actionFrame.setStrokeStyle(1, accent, 0.82);
|
||||
this.trackSideIcon(left + 42, y + 42, this.usableIcon(preview.usable), 66);
|
||||
|
||||
this.trackSideObject(this.add.text(left + 88, y + 9, `${preview.user.name} → ${preview.target.name}`, {
|
||||
fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif',
|
||||
fontSize: '20px',
|
||||
color: '#f4dfad',
|
||||
fontStyle: '700',
|
||||
fixedWidth: width - 96
|
||||
fixedWidth: width - 98
|
||||
}));
|
||||
this.trackSideObject(this.add.text(left + 86, y + 36, `${preview.usable.name} · ${this.usableEffectLabel(preview.usable)} · ${this.usableTargetLabel(preview.usable)} ${preview.usable.range}칸`, {
|
||||
this.trackSideObject(this.add.text(left + 88, y + 37, `${preview.usable.name} · ${this.usableEffectLabel(preview.usable)}`, {
|
||||
fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif',
|
||||
fontSize: '13px',
|
||||
color: '#9fb0bf',
|
||||
color: '#d4dce6',
|
||||
fontStyle: '700',
|
||||
fixedWidth: width - 96
|
||||
fixedWidth: width - 98
|
||||
}));
|
||||
this.trackSideObject(this.add.text(left + 86, y + 56, `거리 ${this.tileDistance(preview.user, preview.target)} / 사거리 ${preview.usable.range} · ${locked ? '선택됨' : '대상 후보'}`, {
|
||||
this.trackSideObject(this.add.text(left + 88, y + 57, `거리 ${distance}/${preview.usable.range} · ${locked ? '선택됨' : '대상 후보'}`, {
|
||||
fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif',
|
||||
fontSize: '12px',
|
||||
color: locked ? '#f2e3bf' : '#c8d2dd',
|
||||
fixedWidth: width - 96
|
||||
color: locked ? '#f2e3bf' : '#9fb0bf',
|
||||
fixedWidth: width - 98
|
||||
}));
|
||||
|
||||
const participantGap = 8;
|
||||
const participantWidth = (width - 20 - participantGap) / 2;
|
||||
this.renderPreviewParticipantBox(
|
||||
left + 10,
|
||||
y + 82,
|
||||
participantWidth,
|
||||
preview.user,
|
||||
'사용자',
|
||||
accent
|
||||
);
|
||||
this.renderPreviewParticipantBox(
|
||||
left + 10 + participantWidth + participantGap,
|
||||
y + 82,
|
||||
participantWidth,
|
||||
preview.target,
|
||||
'대상',
|
||||
preview.target.faction === preview.user.faction ? palette.blue : 0xb86b55
|
||||
);
|
||||
const metricGap = 6;
|
||||
const metricWidth = (width - 20 - metricGap * 2) / 3;
|
||||
const metricTop = y + 90;
|
||||
if (isHeal) {
|
||||
this.renderPreviewDecisionCard(left + 10, metricTop, metricWidth, this.usablePowerIcon(preview.usable), '회복', `+${preview.healAmount}`, 0x59d18c);
|
||||
this.renderPreviewDecisionCard(left + 10 + metricWidth + metricGap, metricTop, metricWidth, 'success', '적용', '100%', accent);
|
||||
this.renderPreviewDecisionCard(left + 10 + (metricWidth + metricGap) * 2, metricTop, metricWidth, 'hp', '병력', `${preview.projectedHp}`, 0x59d18c);
|
||||
} else {
|
||||
this.renderPreviewDecisionCard(left + 10, metricTop, metricWidth, this.usablePowerIcon(preview.usable), '강화', `+${preview.attackBonus}`, palette.gold);
|
||||
this.renderPreviewDecisionCard(left + 10 + metricWidth + metricGap, metricTop, metricWidth, 'hit', '명중', `+${preview.hitBonus}`, accent);
|
||||
this.renderPreviewDecisionCard(left + 10 + (metricWidth + metricGap) * 2, metricTop, metricWidth, 'leadership', '지속', `${preview.duration}턴`, 0x83d6ff);
|
||||
}
|
||||
|
||||
const chipGap = 8;
|
||||
const chipWidth = (width - 20 - chipGap) / 2;
|
||||
this.renderPreviewEquipmentChip(
|
||||
left + 10,
|
||||
y + 146,
|
||||
y + 184,
|
||||
chipWidth,
|
||||
preview.usable.command === 'item' ? 'accessory' : 'strategy',
|
||||
this.usableChannelLabel(preview.usable),
|
||||
@@ -6337,43 +6336,77 @@ export class BattleScene extends Phaser.Scene {
|
||||
);
|
||||
this.renderPreviewEquipmentChip(
|
||||
left + 10 + chipWidth + chipGap,
|
||||
y + 146,
|
||||
y + 184,
|
||||
chipWidth,
|
||||
this.usableTargetIcon(preview.usable),
|
||||
'대상/사거리',
|
||||
`${preview.target.name} · ${preview.usable.range}칸`
|
||||
this.usableTargetLabel(preview.usable),
|
||||
preview.target.name
|
||||
);
|
||||
|
||||
const metricTop = y + 204;
|
||||
const metricGap = 6;
|
||||
const metricWidth = (width - 20 - metricGap * 2) / 3;
|
||||
if (preview.usable.effect === 'heal') {
|
||||
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, this.usableTargetIcon(preview.usable), '대상', `${this.usableTargetLabel(preview.usable)} ${preview.usable.range}`, accent);
|
||||
} else {
|
||||
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);
|
||||
}
|
||||
this.renderSupportForecastRow(preview, left + 10, y + 238, width - 20, accent);
|
||||
this.renderPreviewModifierCards(left + 10, y + 278, width - 20, this.supportPreviewModifierSummaries(preview), accent);
|
||||
|
||||
this.renderSupportForecastRow(preview, left + 10, y + 272, width - 20, accent);
|
||||
this.renderPreviewModifierCards(left + 10, y + 308, width - 20, this.supportPreviewModifierSummaries(preview), accent);
|
||||
|
||||
const badgeY = y + 374;
|
||||
const badgeY = y + 346;
|
||||
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, this.usableTargetIcon(preview.usable), `${this.usableTargetLabel(preview.usable)} ${preview.usable.range}칸`, maxBadgeRight);
|
||||
let detailBadgeX = this.renderPreviewBadge(badgeX, badgeY, preview.usable.command === 'item' ? 'accessory' : 'strategy', this.usableChannelLabel(preview.usable), maxBadgeRight);
|
||||
if (preview.usable.effect === 'focus') {
|
||||
this.renderPreviewBadge(detailBadgeX, badgeY, 'leadership', `${preview.duration}턴`, maxBadgeRight);
|
||||
if (!isHeal) {
|
||||
detailBadgeX = this.renderPreviewBadge(detailBadgeX, badgeY, 'critical', `치명 +${preview.criticalBonus}`, maxBadgeRight);
|
||||
}
|
||||
if (locked) {
|
||||
this.renderPreviewConfirmBar(left + 10, y + 402, width - 20, accent, `${preview.target.name} 선택됨`);
|
||||
}
|
||||
}
|
||||
|
||||
private renderPreviewDecisionCard(
|
||||
x: number,
|
||||
y: number,
|
||||
width: number,
|
||||
icon: BattleUiIconKey,
|
||||
label: string,
|
||||
value: string,
|
||||
tone: number
|
||||
) {
|
||||
const bg = this.trackSideObject(this.add.rectangle(x, y, width, 84, 0x16212d, 0.97));
|
||||
bg.setOrigin(0);
|
||||
bg.setStrokeStyle(1, tone, 0.72);
|
||||
|
||||
const iconFrame = this.trackSideObject(this.add.rectangle(x + width / 2, y + 25, 44, 44, 0x0a0f14, 0.92));
|
||||
iconFrame.setStrokeStyle(1, tone, 0.58);
|
||||
this.trackSideIcon(x + width / 2, y + 25, icon, 40);
|
||||
|
||||
const labelText = this.trackSideObject(this.add.text(x + width / 2, y + 47, label, {
|
||||
fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif',
|
||||
fontSize: '11px',
|
||||
color: '#9fb0bf',
|
||||
fontStyle: '700',
|
||||
fixedWidth: width - 8,
|
||||
align: 'center'
|
||||
}));
|
||||
labelText.setOrigin(0.5, 0);
|
||||
|
||||
const valueHasKorean = /[가-힣]/.test(value);
|
||||
const valueText = this.trackSideObject(this.add.text(x + width / 2, y + 62, value, {
|
||||
fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif',
|
||||
fontSize: valueHasKorean || value.length > 4 ? '17px' : '22px',
|
||||
color: '#f2e3bf',
|
||||
fontStyle: '700',
|
||||
fixedWidth: width - 8,
|
||||
align: 'center'
|
||||
}));
|
||||
valueText.setOrigin(0.5, 0);
|
||||
}
|
||||
|
||||
private renderCombatPreviewCard(preview: CombatPreview, locked = false) {
|
||||
return this.renderCombatPreviewSummaryCard(preview, locked);
|
||||
}
|
||||
|
||||
private renderSupportPreviewCard(preview: SupportPreview, locked = false) {
|
||||
return this.renderSupportPreviewSummaryCard(preview, locked);
|
||||
}
|
||||
|
||||
private renderPreviewModifierCards(
|
||||
x: number,
|
||||
y: number,
|
||||
|
||||
Reference in New Issue
Block a user