Add tactical battle UI icons
This commit is contained in:
BIN
src/assets/images/ui/battle-ui-icons.png
Normal file
BIN
src/assets/images/ui/battle-ui-icons.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 45 KiB |
57
src/game/data/battleUiIcons.ts
Normal file
57
src/game/data/battleUiIcons.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
import Phaser from 'phaser';
|
||||
|
||||
import battleUiIconsUrl from '../../assets/images/ui/battle-ui-icons.png';
|
||||
|
||||
export const battleUiIconTextureKey = 'battle-ui-icons';
|
||||
export const battleUiIconFrameSize = 48;
|
||||
|
||||
export const battleUiIconFrames = {
|
||||
hp: 0,
|
||||
might: 1,
|
||||
intelligence: 2,
|
||||
leadership: 3,
|
||||
attack: 4,
|
||||
defense: 5,
|
||||
move: 6,
|
||||
mastery: 7,
|
||||
sword: 8,
|
||||
spear: 9,
|
||||
axe: 10,
|
||||
bow: 11,
|
||||
strategy: 12,
|
||||
horse: 13,
|
||||
armor: 14,
|
||||
accessory: 15,
|
||||
hit: 16,
|
||||
critical: 17,
|
||||
terrain: 18,
|
||||
counter: 19,
|
||||
advantage: 20,
|
||||
success: 21
|
||||
} as const;
|
||||
|
||||
export type BattleUiIconKey = keyof typeof battleUiIconFrames;
|
||||
|
||||
export const battleUiIconManifest = {
|
||||
source: 'src/assets/images/ui/battle-ui-icons.png',
|
||||
textureKey: battleUiIconTextureKey,
|
||||
frameWidth: battleUiIconFrameSize,
|
||||
frameHeight: battleUiIconFrameSize,
|
||||
columns: 5,
|
||||
rows: 5,
|
||||
frames: battleUiIconFrames
|
||||
} as const;
|
||||
|
||||
export function loadBattleUiIcons(scene: Phaser.Scene, onReady: () => void) {
|
||||
if (scene.textures.exists(battleUiIconTextureKey)) {
|
||||
onReady();
|
||||
return;
|
||||
}
|
||||
|
||||
scene.load.once('complete', onReady);
|
||||
scene.load.spritesheet(battleUiIconTextureKey, battleUiIconsUrl, {
|
||||
frameWidth: battleUiIconFrameSize,
|
||||
frameHeight: battleUiIconFrameSize
|
||||
});
|
||||
scene.load.start();
|
||||
}
|
||||
@@ -11,12 +11,19 @@ import {
|
||||
unitTextureVariantKeys as unitAssetTextureVariantKeys,
|
||||
type UnitDirection
|
||||
} from '../data/unitAssets';
|
||||
import {
|
||||
battleUiIconFrames,
|
||||
battleUiIconTextureKey,
|
||||
loadBattleUiIcons,
|
||||
type BattleUiIconKey
|
||||
} from '../data/battleUiIcons';
|
||||
import {
|
||||
equipmentExpToNext,
|
||||
equipmentSlotLabels,
|
||||
equipmentSlots,
|
||||
getItem,
|
||||
type EquipmentSlot
|
||||
type EquipmentSlot,
|
||||
type ItemDefinition
|
||||
} from '../data/battleItems';
|
||||
import {
|
||||
campaignSaveSlotCount,
|
||||
@@ -1194,6 +1201,7 @@ type EquipmentGrowthResult = {
|
||||
type LevelUpStatGain = {
|
||||
label: string;
|
||||
amount: number;
|
||||
icon: BattleUiIconKey;
|
||||
};
|
||||
|
||||
type CharacterGrowthResult = {
|
||||
@@ -1285,6 +1293,7 @@ type CombatPreview = {
|
||||
criticalRate: number;
|
||||
counterAvailable: boolean;
|
||||
terrainLabel: string;
|
||||
matchupLabel: string;
|
||||
};
|
||||
|
||||
type CombatResult = CombatPreview & {
|
||||
@@ -2992,7 +3001,7 @@ export class BattleScene extends Phaser.Scene {
|
||||
}
|
||||
|
||||
private ensureScenarioAssets(onReady: () => void) {
|
||||
this.ensureScenarioMapTexture(() => this.ensureScenarioUnitTextures(onReady));
|
||||
this.ensureScenarioMapTexture(() => loadBattleUiIcons(this, () => this.ensureScenarioUnitTextures(onReady)));
|
||||
}
|
||||
|
||||
private ensureScenarioMapTexture(onReady: () => void) {
|
||||
@@ -4453,15 +4462,8 @@ export class BattleScene extends Phaser.Scene {
|
||||
}
|
||||
|
||||
private renderAttackPreview(preview: CombatPreview) {
|
||||
const counter = preview.counterAvailable ? ' / 반격 가능' : '';
|
||||
const bond = preview.bondDamageBonus > 0 && preview.bondLabel ? `\n공명 ${preview.bondLabel} 피해 +${preview.bondDamageBonus}%` : '';
|
||||
const equipment = preview.equipmentEffectLabels.length > 0
|
||||
? `\n보물 ${preview.equipmentEffectLabels.join(', ')} (${this.equipmentPreviewBonusText(preview)})`
|
||||
: '';
|
||||
this.renderUnitDetail(
|
||||
preview.defender,
|
||||
`${preview.attacker.name} ${commandLabels[preview.action]} 예측\n피해 ${preview.damage} / 명중 ${preview.hitRate}% / 치명 ${preview.criticalRate}%\n지형 ${preview.terrainLabel}${counter}${bond}${equipment}`
|
||||
);
|
||||
this.renderUnitDetail(preview.defender);
|
||||
this.renderCombatPreviewCard(preview);
|
||||
}
|
||||
|
||||
private renderSupportPreview(user: UnitData, target: UnitData, usable: BattleUsable) {
|
||||
@@ -4487,6 +4489,101 @@ export class BattleScene extends Phaser.Scene {
|
||||
return parts.join(' / ') || '보정 없음';
|
||||
}
|
||||
|
||||
private renderCombatPreviewCard(preview: CombatPreview) {
|
||||
const { panelX, panelWidth } = this.layout;
|
||||
const left = panelX + 24;
|
||||
const width = panelWidth - 48;
|
||||
const height = 124;
|
||||
const y = Math.min(this.sideContentTop() + 430, this.sideContentBottom(10) - height);
|
||||
|
||||
const bg = this.trackSideObject(this.add.rectangle(left, y, width, height, 0x121a22, 0.96));
|
||||
bg.setOrigin(0);
|
||||
bg.setStrokeStyle(2, palette.gold, 0.72);
|
||||
|
||||
this.trackSideIcon(left + 22, y + 25, this.actionIcon(preview), 32);
|
||||
this.trackSideObject(this.add.text(left + 44, y + 11, `${preview.attacker.name} ${commandLabels[preview.action]} 예측`, {
|
||||
fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif',
|
||||
fontSize: '16px',
|
||||
color: '#f4dfad',
|
||||
fontStyle: '700'
|
||||
}));
|
||||
|
||||
const attackerWeapon = getItem(preview.attacker.equipment.weapon.itemId);
|
||||
const defenderArmor = getItem(preview.defender.equipment.armor.itemId);
|
||||
this.renderPreviewEquipmentChip(left + 44, y + 38, 126, this.itemIcon(attackerWeapon, 'weapon'), attackerWeapon.name);
|
||||
this.renderPreviewEquipmentChip(left + 178, y + 38, 126, this.itemIcon(defenderArmor, 'armor'), defenderArmor.name);
|
||||
|
||||
const metricTop = y + 70;
|
||||
const metricWidth = (width - 24) / 3;
|
||||
this.renderPreviewMetric(left + 8, metricTop, metricWidth, 'attack', '피해', `${preview.damage}`);
|
||||
this.renderPreviewMetric(left + 8 + metricWidth, metricTop, metricWidth, preview.action === 'strategy' ? 'success' : 'hit', preview.action === 'strategy' ? '성공' : '명중', `${preview.hitRate}%`);
|
||||
this.renderPreviewMetric(left + 8 + metricWidth * 2, metricTop, metricWidth, 'critical', '치명', `${preview.criticalRate}%`);
|
||||
|
||||
const badgeY = y + 101;
|
||||
const maxBadgeRight = left + width - 10;
|
||||
let badgeX = left + 10;
|
||||
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);
|
||||
}
|
||||
if (preview.bondDamageBonus > 0 && preview.bondLabel) {
|
||||
badgeX = this.renderPreviewBadge(badgeX, badgeY, 'leadership', `공명 +${preview.bondDamageBonus}%`, maxBadgeRight);
|
||||
}
|
||||
if (preview.equipmentEffectLabels.length > 0) {
|
||||
this.renderPreviewBadge(badgeX, badgeY, 'mastery', '장비 보정', maxBadgeRight);
|
||||
}
|
||||
}
|
||||
|
||||
private renderPreviewEquipmentChip(x: number, y: number, width: number, icon: BattleUiIconKey, label: string) {
|
||||
const chip = this.trackSideObject(this.add.rectangle(x, y, width, 22, 0x0b1118, 0.86));
|
||||
chip.setOrigin(0);
|
||||
chip.setStrokeStyle(1, 0x53606c, 0.42);
|
||||
this.trackSideIcon(x + 12, y + 11, icon, 18);
|
||||
this.trackSideObject(this.add.text(x + 25, y + 4, label, {
|
||||
fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif',
|
||||
fontSize: '11px',
|
||||
color: '#d4dce6',
|
||||
wordWrap: { width: width - 32, useAdvancedWrap: true }
|
||||
}));
|
||||
}
|
||||
|
||||
private renderPreviewMetric(x: number, y: number, width: number, icon: BattleUiIconKey, label: string, value: string) {
|
||||
this.trackSideIcon(x + 13, y + 11, icon, 18);
|
||||
this.trackSideObject(this.add.text(x + 26, y + 2, label, {
|
||||
fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif',
|
||||
fontSize: '11px',
|
||||
color: '#9fb0bf'
|
||||
}));
|
||||
const valueText = this.trackSideObject(this.add.text(x + width - 8, y + 1, value, {
|
||||
fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif',
|
||||
fontSize: '15px',
|
||||
color: '#f2e3bf',
|
||||
fontStyle: '700'
|
||||
}));
|
||||
valueText.setOrigin(1, 0);
|
||||
}
|
||||
|
||||
private renderPreviewBadge(x: number, y: number, icon: BattleUiIconKey, label: string, maxRight?: number) {
|
||||
const width = Phaser.Math.Clamp(label.length * 8 + 34, 58, 120);
|
||||
if (maxRight && x + width > maxRight) {
|
||||
return x;
|
||||
}
|
||||
const badge = this.trackSideObject(this.add.rectangle(x, y, width, 20, 0x0b1118, 0.88));
|
||||
badge.setOrigin(0);
|
||||
badge.setStrokeStyle(1, 0x53606c, 0.44);
|
||||
this.trackSideIcon(x + 11, y + 10, icon, 15);
|
||||
this.trackSideObject(this.add.text(x + 22, y + 4, label, {
|
||||
fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif',
|
||||
fontSize: '10px',
|
||||
color: '#d4dce6'
|
||||
}));
|
||||
return x + width + 6;
|
||||
}
|
||||
|
||||
private finishUnitAction(unit: UnitData, message: string) {
|
||||
this.actedUnitIds.add(unit.id);
|
||||
this.phase = 'idle';
|
||||
@@ -5104,7 +5201,7 @@ export class BattleScene extends Phaser.Scene {
|
||||
exp: unit.exp,
|
||||
next: 100,
|
||||
leveled: unit.level > previousLevel,
|
||||
statGains: this.characterLevelUpStatGains(unit.level - previousLevel),
|
||||
statGains: this.characterLevelUpStatGains(unit, unit.level - previousLevel),
|
||||
flashArea: { x, y, width, height: 68, depth: depth + 3 }
|
||||
});
|
||||
}
|
||||
@@ -5397,7 +5494,7 @@ export class BattleScene extends Phaser.Scene {
|
||||
color: '#f4dfad',
|
||||
fontStyle: '700'
|
||||
});
|
||||
const statText = this.add.text(-width / 2 + 110, 26, stats ?? '숙련 효과 강화', {
|
||||
const statText = this.add.text(-width / 2 + 110, 22, stats ? '능력 상승' : '숙련 효과 강화', {
|
||||
fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif',
|
||||
fontSize: '16px',
|
||||
color: stats ? '#a8ffd0' : '#d8b15f',
|
||||
@@ -5406,6 +5503,9 @@ export class BattleScene extends Phaser.Scene {
|
||||
strokeThickness: 3
|
||||
});
|
||||
container.add([bg, burst, title, levelText, statText]);
|
||||
if (animation.statGains) {
|
||||
this.addStatGainChipsToContainer(container, animation.statGains, -width / 2 + 110, 46, width - 124);
|
||||
}
|
||||
|
||||
if (animation.unit) {
|
||||
const sprite = this.add.sprite(-width / 2 + 56, 10, this.unitActionTexture(animation.unit), this.unitActionFrameIndex('south', this.celebrationPose(animation.unit)));
|
||||
@@ -5428,6 +5528,40 @@ export class BattleScene extends Phaser.Scene {
|
||||
await this.delay(260);
|
||||
}
|
||||
|
||||
private addStatGainChipsToContainer(
|
||||
container: Phaser.GameObjects.Container,
|
||||
gains: LevelUpStatGain[],
|
||||
x: number,
|
||||
y: number,
|
||||
maxWidth: number
|
||||
) {
|
||||
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 * 12 + 42, 64, 92);
|
||||
if (cursorX + chipWidth > x + maxWidth) {
|
||||
return;
|
||||
}
|
||||
const bg = this.add.rectangle(cursorX, y, chipWidth, 24, 0x0b1118, 0.9);
|
||||
bg.setOrigin(0);
|
||||
bg.setStrokeStyle(1, 0x53606c, 0.5);
|
||||
const icon = this.createBattleUiIcon(cursorX + 13, y + 12, gain.icon, 18);
|
||||
const text = this.add.text(cursorX + 26, y + 5, `+${gain.amount}`, {
|
||||
fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif',
|
||||
fontSize: '12px',
|
||||
color: '#a8ffd0',
|
||||
fontStyle: '700'
|
||||
});
|
||||
const label = this.add.text(cursorX + 48, y + 5, gain.label, {
|
||||
fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif',
|
||||
fontSize: '11px',
|
||||
color: '#d4dce6'
|
||||
});
|
||||
container.add([bg, icon, text, label]);
|
||||
cursorX += chipWidth + 6;
|
||||
});
|
||||
}
|
||||
|
||||
private createResultLevelUpBurst(x: number, y: number, depth: number) {
|
||||
const ring = this.trackResultObject(this.add.circle(x, y + 2, 33));
|
||||
ring.setStrokeStyle(3, 0xffdf7b, 0.84);
|
||||
@@ -5617,7 +5751,8 @@ export class BattleScene extends Phaser.Scene {
|
||||
hitRate,
|
||||
criticalRate,
|
||||
counterAvailable: this.canCounterAttack(defender, attacker, action),
|
||||
terrainLabel: terrainRule.label
|
||||
terrainLabel: terrainRule.label,
|
||||
matchupLabel: this.combatMatchupLabel(attacker, defender, action)
|
||||
};
|
||||
}
|
||||
|
||||
@@ -5886,6 +6021,43 @@ export class BattleScene extends Phaser.Scene {
|
||||
return this.equipmentDefenseBonus(unit) + Math.floor(unit.stats.leadership / 18) + Math.floor(terrainDefenseBonus / 3);
|
||||
}
|
||||
|
||||
private combatMatchupLabel(attacker: UnitData, defender: UnitData, action: DamageCommand) {
|
||||
if (action === 'strategy') {
|
||||
const gap = attacker.stats.intelligence - defender.stats.intelligence;
|
||||
return gap >= 10 ? '책략 우위' : gap <= -10 ? '책략 불리' : '책략 균형';
|
||||
}
|
||||
|
||||
if (action === 'item') {
|
||||
const gap = attacker.stats.luck - defender.stats.luck;
|
||||
return gap >= 12 ? '도구 우위' : gap <= -12 ? '도구 불리' : '도구 균형';
|
||||
}
|
||||
|
||||
if (this.hasClassAdvantage(attacker, defender)) {
|
||||
return '상성 우위';
|
||||
}
|
||||
if (this.hasClassAdvantage(defender, attacker)) {
|
||||
return '상성 불리';
|
||||
}
|
||||
return '상성 보통';
|
||||
}
|
||||
|
||||
private hasClassAdvantage(attacker: UnitData, defender: UnitData) {
|
||||
const distance = this.tileDistance(attacker, defender);
|
||||
if (attacker.classKey === 'spearman' && defender.classKey === 'cavalry') {
|
||||
return true;
|
||||
}
|
||||
if (attacker.classKey === 'cavalry' && (defender.classKey === 'archer' || defender.classKey === 'strategist' || defender.classKey === 'quartermaster')) {
|
||||
return true;
|
||||
}
|
||||
if (attacker.classKey === 'archer' && distance > 1 && (defender.classKey === 'infantry' || defender.classKey === 'spearman' || defender.classKey === 'yellowTurban')) {
|
||||
return true;
|
||||
}
|
||||
if ((attacker.classKey === 'strategist' || attacker.classKey === 'quartermaster') && defender.stats.intelligence + 12 <= attacker.stats.intelligence) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private attackerEquipmentExpGain(unit: UnitData, action: DamageCommand) {
|
||||
if (action === 'attack') {
|
||||
return this.weaponExpGain(unit);
|
||||
@@ -7163,6 +7335,7 @@ export class BattleScene extends Phaser.Scene {
|
||||
}));
|
||||
title.setOrigin(0.5, 0);
|
||||
title.setDepth(depth + 8);
|
||||
this.renderCombatActionRibbon(result, left + panelWidth / 2 - 220, top + 54, 440, depth + 9);
|
||||
|
||||
const attackerDirection = 'east';
|
||||
const defenderDirection = 'west';
|
||||
@@ -7249,6 +7422,68 @@ export class BattleScene extends Phaser.Scene {
|
||||
this.hideCombatCutIn();
|
||||
}
|
||||
|
||||
private renderCombatActionRibbon(result: CombatResult, x: number, y: number, width: number, depth: number) {
|
||||
const bg = this.trackCombatObject(this.add.rectangle(x, y, width, 58, 0x0b1118, 0.88));
|
||||
bg.setOrigin(0);
|
||||
bg.setDepth(depth);
|
||||
bg.setStrokeStyle(1, palette.gold, 0.58);
|
||||
|
||||
this.trackCombatIcon(x + 22, y + 19, this.actionIcon(result), 28, depth + 1);
|
||||
const attackerWeapon = getItem(result.attacker.equipment.weapon.itemId);
|
||||
const defenderArmor = getItem(result.defender.equipment.armor.itemId);
|
||||
this.trackCombatIcon(x + 132, y + 19, this.itemIcon(attackerWeapon, 'weapon'), 22, depth + 1);
|
||||
this.trackCombatIcon(x + 248, y + 19, this.itemIcon(defenderArmor, 'armor'), 22, depth + 1);
|
||||
|
||||
const actionLabel = this.trackCombatObject(this.add.text(x + 42, y + 8, result.usable?.name ?? commandLabels[result.action], {
|
||||
fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif',
|
||||
fontSize: '15px',
|
||||
color: '#f4dfad',
|
||||
fontStyle: '700',
|
||||
fixedWidth: 74
|
||||
}));
|
||||
actionLabel.setDepth(depth + 1);
|
||||
|
||||
const weaponLabel = this.trackCombatObject(this.add.text(x + 148, y + 9, attackerWeapon.name, {
|
||||
fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif',
|
||||
fontSize: '12px',
|
||||
color: '#d4dce6',
|
||||
fixedWidth: 82
|
||||
}));
|
||||
weaponLabel.setDepth(depth + 1);
|
||||
|
||||
const armorLabel = this.trackCombatObject(this.add.text(x + 264, y + 9, defenderArmor.name, {
|
||||
fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif',
|
||||
fontSize: '12px',
|
||||
color: '#d4dce6',
|
||||
fixedWidth: 74
|
||||
}));
|
||||
armorLabel.setDepth(depth + 1);
|
||||
|
||||
const outcomeIcon = result.action === 'strategy' ? 'success' : result.critical ? 'critical' : 'hit';
|
||||
const outcomeLabel = result.action === 'strategy' ? (result.hit ? '성공' : '실패') : result.hit ? (result.critical ? '치명' : '명중') : '회피';
|
||||
this.renderCombatRibbonBadge(x + 42, y + 33, 'advantage', result.matchupLabel, depth + 1, 74);
|
||||
this.renderCombatRibbonBadge(x + 122, y + 33, outcomeIcon, outcomeLabel, depth + 1, 58);
|
||||
if (result.counter) {
|
||||
this.renderCombatRibbonBadge(x + 186, y + 33, 'counter', '반격', depth + 1, 54);
|
||||
}
|
||||
}
|
||||
|
||||
private renderCombatRibbonBadge(x: number, y: number, icon: BattleUiIconKey, label: string, depth: number, width = 52) {
|
||||
const badge = this.trackCombatObject(this.add.rectangle(x, y, width, 20, 0x101820, 0.92));
|
||||
badge.setOrigin(0);
|
||||
badge.setDepth(depth);
|
||||
badge.setStrokeStyle(1, 0x53606c, 0.48);
|
||||
this.trackCombatIcon(x + 11, y + 10, icon, 15, depth + 1);
|
||||
const text = this.trackCombatObject(this.add.text(x + 22, y + 4, label, {
|
||||
fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif',
|
||||
fontSize: '10px',
|
||||
color: '#f2e3bf',
|
||||
fontStyle: '700',
|
||||
fixedWidth: width - 26
|
||||
}));
|
||||
text.setDepth(depth + 1);
|
||||
}
|
||||
|
||||
private applyCombatSpriteDisplaySize(sprite: Phaser.GameObjects.Sprite, unit: UnitData, compact = false) {
|
||||
const size = this.combatSpriteDisplaySize(unit, compact);
|
||||
sprite.setDisplaySize(size, size);
|
||||
@@ -7386,17 +7621,18 @@ export class BattleScene extends Phaser.Scene {
|
||||
}));
|
||||
nameText.setDepth(85);
|
||||
|
||||
const hpLabel = this.trackCombatObject(this.add.text(x + 78, y + 45, '병력', {
|
||||
this.trackCombatIcon(x + 88, y + 54, 'hp', 18, 85);
|
||||
const hpLabel = this.trackCombatObject(this.add.text(x + 104, y + 45, '병력', {
|
||||
fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif',
|
||||
fontSize: '13px',
|
||||
color: '#9fb0bf',
|
||||
fontStyle: '700'
|
||||
}));
|
||||
hpLabel.setDepth(85);
|
||||
const hpTrack = this.trackCombatObject(this.add.rectangle(x + 126, y + 54, width - 210, 9, 0x0a0f14, 0.9));
|
||||
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);
|
||||
const hpFill = this.trackCombatObject(this.add.rectangle(x + 126, y + 54, width - 210, 7, 0x59d18c, 0.96));
|
||||
const hpFill = this.trackCombatObject(this.add.rectangle(x + 148, y + 54, width - 232, 7, 0x59d18c, 0.96));
|
||||
hpFill.setOrigin(0, 0.5);
|
||||
hpFill.setDepth(86);
|
||||
hpFill.setScale(unit.hp / unit.maxHp, 1);
|
||||
@@ -7409,17 +7645,18 @@ export class BattleScene extends Phaser.Scene {
|
||||
hpText.setOrigin(1, 0);
|
||||
hpText.setDepth(85);
|
||||
|
||||
const expLabel = this.trackCombatObject(this.add.text(x + 78, y + 75, '경험', {
|
||||
this.trackCombatIcon(x + 88, y + 84, 'mastery', 18, 85);
|
||||
const expLabel = this.trackCombatObject(this.add.text(x + 104, y + 75, '경험', {
|
||||
fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif',
|
||||
fontSize: '13px',
|
||||
color: '#9fb0bf',
|
||||
fontStyle: '700'
|
||||
}));
|
||||
expLabel.setDepth(85);
|
||||
const expTrack = this.trackCombatObject(this.add.rectangle(x + 126, y + 84, width - 210, 9, 0x0a0f14, 0.9));
|
||||
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);
|
||||
const expFill = this.trackCombatObject(this.add.rectangle(x + 126, y + 84, width - 210, 7, 0xd8b15f, 0.96));
|
||||
const expFill = this.trackCombatObject(this.add.rectangle(x + 148, y + 84, width - 232, 7, 0xd8b15f, 0.96));
|
||||
expFill.setOrigin(0, 0.5);
|
||||
expFill.setDepth(86);
|
||||
expFill.setScale((growth?.previousExp ?? unit.exp) / 100, 1);
|
||||
@@ -7666,7 +7903,7 @@ export class BattleScene extends Phaser.Scene {
|
||||
|
||||
private async playCombatLevelUpSpotlight(entry: GrowthGaugeEntry, celebrant: UnitData | undefined, x: number, y: number, width: number, depth: number) {
|
||||
const panelWidth = Math.min(540, width - 34);
|
||||
const panelHeight = 104;
|
||||
const panelHeight = 116;
|
||||
const container = this.trackCombatObject(this.add.container(x, y));
|
||||
container.setDepth(depth);
|
||||
container.setAlpha(0);
|
||||
@@ -7691,7 +7928,7 @@ export class BattleScene extends Phaser.Scene {
|
||||
fontStyle: '700'
|
||||
});
|
||||
levelText.setOrigin(celebrant ? 0 : 0.5, 0);
|
||||
const statText = this.add.text(celebrant ? -panelWidth / 2 + 112 : 0, 25, this.statGainLine(entry.statGains) ?? `${entry.label} 효과 강화`, {
|
||||
const statText = this.add.text(celebrant ? -panelWidth / 2 + 112 : 0, 25, entry.statGains ? '능력 상승' : `${entry.label} 효과 강화`, {
|
||||
fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif',
|
||||
fontSize: '16px',
|
||||
color: '#a8ffd0',
|
||||
@@ -7701,6 +7938,9 @@ export class BattleScene extends Phaser.Scene {
|
||||
});
|
||||
statText.setOrigin(celebrant ? 0 : 0.5, 0);
|
||||
container.add([bg, glow, title, levelText, statText]);
|
||||
if (entry.statGains) {
|
||||
this.addStatGainChipsToContainer(container, entry.statGains, celebrant ? -panelWidth / 2 + 112 : -panelWidth / 2 + 40, 51, panelWidth - 140);
|
||||
}
|
||||
|
||||
if (celebrant) {
|
||||
const sprite = this.add.sprite(-panelWidth / 2 + 56, 6, this.unitActionTexture(celebrant), this.unitActionFrameIndex('south', this.celebrationPose(celebrant)));
|
||||
@@ -7725,7 +7965,8 @@ export class BattleScene extends Phaser.Scene {
|
||||
}
|
||||
|
||||
private renderGrowthGaugeRow(entry: GrowthGaugeEntry, x: number, y: number, width: number, depth: number): GrowthGaugeView {
|
||||
const label = this.trackCombatObject(this.add.text(x, y - 3, `${entry.owner} ${entry.label}`, {
|
||||
this.trackCombatIcon(x + 8, y + 8, this.growthEntryIcon(entry), 16, depth);
|
||||
const label = this.trackCombatObject(this.add.text(x + 22, y - 3, `${entry.owner} ${entry.label}`, {
|
||||
fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif',
|
||||
fontSize: '13px',
|
||||
color: '#d4dce6',
|
||||
@@ -7804,6 +8045,22 @@ export class BattleScene extends Phaser.Scene {
|
||||
return stats ? `Lv ${entry.level} 달성! ${stats}` : `Lv ${entry.level} 달성!`;
|
||||
}
|
||||
|
||||
private growthEntryIcon(entry: GrowthGaugeEntry): BattleUiIconKey {
|
||||
if (entry.kind === 'character') {
|
||||
return entry.statGains?.[0]?.icon ?? 'hp';
|
||||
}
|
||||
if (entry.kind === 'bond') {
|
||||
return 'leadership';
|
||||
}
|
||||
if (entry.slot === 'armor') {
|
||||
return 'armor';
|
||||
}
|
||||
if (entry.slot === 'accessory') {
|
||||
return 'accessory';
|
||||
}
|
||||
return 'sword';
|
||||
}
|
||||
|
||||
private showGrowthEventText(text: Phaser.GameObjects.Text, label: string) {
|
||||
text.setText(label);
|
||||
text.setAlpha(1);
|
||||
@@ -8874,13 +9131,11 @@ export class BattleScene extends Phaser.Scene {
|
||||
while (unit.level < maxCharacterLevel && exp >= 100) {
|
||||
exp -= 100;
|
||||
unit.level += 1;
|
||||
unit.maxHp += 2;
|
||||
unit.hp = Math.min(unit.maxHp, unit.hp + 2);
|
||||
unit.attack += 1;
|
||||
leveled = true;
|
||||
levelUps += 1;
|
||||
}
|
||||
|
||||
const statGains = this.applyCharacterLevelUpStats(unit, levelUps);
|
||||
unit.exp = unit.level >= maxCharacterLevel ? Math.min(exp, 100) : exp;
|
||||
|
||||
return {
|
||||
@@ -8892,21 +9147,90 @@ export class BattleScene extends Phaser.Scene {
|
||||
next: 100,
|
||||
leveled,
|
||||
levelUps,
|
||||
statGains: this.characterLevelUpStatGains(levelUps)
|
||||
statGains
|
||||
};
|
||||
}
|
||||
|
||||
private characterLevelUpStatGains(levelUps: number): LevelUpStatGain[] {
|
||||
private applyCharacterLevelUpStats(unit: UnitData, levelUps: number) {
|
||||
const gains = this.characterLevelUpStatGains(unit, levelUps);
|
||||
gains.forEach((gain) => {
|
||||
switch (gain.icon) {
|
||||
case 'hp':
|
||||
unit.maxHp += gain.amount;
|
||||
unit.hp = Math.min(unit.maxHp, unit.hp + gain.amount);
|
||||
return;
|
||||
case 'attack':
|
||||
unit.attack += gain.amount;
|
||||
return;
|
||||
case 'might':
|
||||
unit.stats.might = Math.min(255, unit.stats.might + gain.amount);
|
||||
return;
|
||||
case 'intelligence':
|
||||
unit.stats.intelligence = Math.min(255, unit.stats.intelligence + gain.amount);
|
||||
return;
|
||||
case 'leadership':
|
||||
unit.stats.leadership = Math.min(255, unit.stats.leadership + gain.amount);
|
||||
return;
|
||||
case 'move':
|
||||
unit.stats.agility = Math.min(255, unit.stats.agility + gain.amount);
|
||||
return;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
});
|
||||
return gains;
|
||||
}
|
||||
|
||||
private characterLevelUpStatGains(unit: UnitData, levelUps: number): LevelUpStatGain[] {
|
||||
if (levelUps <= 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const profile = this.levelUpStatProfile(unit.classKey);
|
||||
return [
|
||||
{ label: '병력', amount: levelUps * 2 },
|
||||
{ label: '공격', amount: levelUps }
|
||||
{ label: '병력', amount: levelUps * 2, icon: 'hp' },
|
||||
{ label: '공격', amount: levelUps, icon: 'attack' },
|
||||
...profile.map((icon) => ({
|
||||
label: this.levelUpStatLabel(icon),
|
||||
amount: levelUps,
|
||||
icon
|
||||
}))
|
||||
];
|
||||
}
|
||||
|
||||
private levelUpStatProfile(classKey: UnitClassKey): BattleUiIconKey[] {
|
||||
switch (classKey) {
|
||||
case 'strategist':
|
||||
case 'quartermaster':
|
||||
return ['intelligence', 'leadership'];
|
||||
case 'archer':
|
||||
return ['might', 'intelligence'];
|
||||
case 'cavalry':
|
||||
return ['might', 'leadership', 'move'];
|
||||
case 'lord':
|
||||
case 'rebelLeader':
|
||||
case 'yellowTurban':
|
||||
return ['might', 'intelligence', 'leadership'];
|
||||
default:
|
||||
return ['might', 'leadership'];
|
||||
}
|
||||
}
|
||||
|
||||
private levelUpStatLabel(icon: BattleUiIconKey) {
|
||||
switch (icon) {
|
||||
case 'might':
|
||||
return '무력';
|
||||
case 'intelligence':
|
||||
return '지력';
|
||||
case 'leadership':
|
||||
return '통솔';
|
||||
case 'move':
|
||||
return '민첩';
|
||||
default:
|
||||
return '능력';
|
||||
}
|
||||
}
|
||||
|
||||
private formatEquipmentGrowth(result: EquipmentGrowthResult) {
|
||||
const levelUp = result.leveled ? ` / Lv ${result.level} 달성` : '';
|
||||
return `${equipmentSlotLabels[result.slot]} ${result.itemName} 경험치 +${result.amount} (${result.exp}/${result.next})${levelUp}`;
|
||||
@@ -9782,7 +10106,8 @@ export class BattleScene extends Phaser.Scene {
|
||||
}
|
||||
|
||||
private sideContentBottom(padding = 16) {
|
||||
return (this.miniMapLayout?.y ?? this.layout.panelY + this.layout.panelHeight - 140) - padding;
|
||||
const panelBottom = this.layout.panelY + this.layout.panelHeight - padding;
|
||||
return this.miniMapVisible && this.miniMapLayout ? this.miniMapLayout.y - padding : panelBottom;
|
||||
}
|
||||
|
||||
private renderBondPanel(message?: string) {
|
||||
@@ -10230,13 +10555,16 @@ export class BattleScene extends Phaser.Scene {
|
||||
header.setOrigin(0);
|
||||
header.setStrokeStyle(1, factionColor, 0.76);
|
||||
|
||||
this.trackSideObject(this.add.text(left + 14, top + 9, `${rosterLabels[unit.faction]} / ${unitClass.family}`, {
|
||||
const classIcon = this.trackSideIcon(left + 23, top + 31, this.unitMoveIcon(unit), 30);
|
||||
classIcon.setAlpha(acted ? 0.55 : 0.94);
|
||||
const headerTextX = left + 48;
|
||||
this.trackSideObject(this.add.text(headerTextX, top + 9, `${rosterLabels[unit.faction]} / ${unitClass.family}`, {
|
||||
fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif',
|
||||
fontSize: '14px',
|
||||
color: unit.faction === 'ally' ? '#9fd0ff' : '#ffb2a4',
|
||||
fontStyle: '700'
|
||||
}));
|
||||
this.trackSideObject(this.add.text(left + 14, top + 29, `${unit.name} ${unitClass.name}`, {
|
||||
this.trackSideObject(this.add.text(headerTextX, top + 29, `${unit.name} ${unitClass.name}`, {
|
||||
fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif',
|
||||
fontSize: '22px',
|
||||
color: acted ? '#bdbdbd' : '#f2e3bf',
|
||||
@@ -10266,7 +10594,8 @@ export class BattleScene extends Phaser.Scene {
|
||||
backText.setOrigin(0.5);
|
||||
}
|
||||
|
||||
this.trackSideObject(this.add.text(left, top + 78, '병력', {
|
||||
this.trackSideIcon(left + 10, top + 88, 'hp', 20);
|
||||
this.trackSideObject(this.add.text(left + 26, top + 78, '병력', {
|
||||
fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif',
|
||||
fontSize: '17px',
|
||||
color: '#d4dce6',
|
||||
@@ -10279,43 +10608,46 @@ export class BattleScene extends Phaser.Scene {
|
||||
fontStyle: '700'
|
||||
}));
|
||||
hpValue.setOrigin(1, 0);
|
||||
this.drawGauge(left + 76, top + 84, width - 160, 10, unit.hp / unit.maxHp, 0x59d18c);
|
||||
this.drawGauge(left + 92, top + 84, width - 176, 10, unit.hp / unit.maxHp, 0x59d18c);
|
||||
|
||||
const attackBonus = this.equipmentAttackBonus(unit);
|
||||
const summaryGap = 6;
|
||||
const summaryBoxWidth = (width - summaryGap * 2) / 3;
|
||||
this.renderCompactValueBox(left, top + 112, summaryBoxWidth, '공격', `${unit.attack + attackBonus}`);
|
||||
this.renderCompactValueBox(left + summaryBoxWidth + summaryGap, top + 112, summaryBoxWidth, '방어+', `${this.equipmentDefenseBonus(unit)}`);
|
||||
this.renderCompactValueBox(left, top + 112, summaryBoxWidth, 'attack', '공격', `${unit.attack + attackBonus}`);
|
||||
this.renderCompactValueBox(left + summaryBoxWidth + summaryGap, top + 112, summaryBoxWidth, 'defense', '방어+', `${this.equipmentDefenseBonus(unit)}`);
|
||||
this.renderCompactValueBox(
|
||||
left + (summaryBoxWidth + summaryGap) * 2,
|
||||
top + 112,
|
||||
summaryBoxWidth,
|
||||
this.unitMoveIcon(unit),
|
||||
'방향',
|
||||
`${this.directionSymbol(direction)} ${this.unitDirectionLabel(direction)}`
|
||||
);
|
||||
|
||||
const statTop = top + 158;
|
||||
statLabels.forEach((stat, index) => {
|
||||
this.renderStatRow(stat.label, unit.stats[stat.key], left, statTop + index * 28, width);
|
||||
this.renderStatRow(stat.key, stat.label, unit.stats[stat.key], left, statTop + index * 28, width);
|
||||
});
|
||||
|
||||
const positionText = `위치 ${unit.x + 1}, ${unit.y + 1} / ${terrainRule.label} ${terrainRating}% / 이동 ${unit.move}${acted ? ' / 행동완료' : ''}`;
|
||||
this.trackSideObject(this.add.text(left, top + 306, positionText, {
|
||||
this.trackSideIcon(left + 9, top + 315, acted ? 'counter' : this.unitMoveIcon(unit), 18);
|
||||
this.trackSideObject(this.add.text(left + 24, top + 306, positionText, {
|
||||
fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif',
|
||||
fontSize: '14px',
|
||||
color: acted ? '#bdbdbd' : '#9fb0bf',
|
||||
wordWrap: { width, useAdvancedWrap: true }
|
||||
wordWrap: { width: width - 24, useAdvancedWrap: true }
|
||||
}));
|
||||
|
||||
this.renderEquipmentSummary(unit, left, top + 330, width);
|
||||
if (message) {
|
||||
this.renderPanelMessage(message, left, top + 444, width, 66);
|
||||
this.renderPanelMessage(message, left, top + 460, width, 58);
|
||||
}
|
||||
this.showFacingIndicator(unit);
|
||||
}
|
||||
|
||||
private renderEquipmentSummary(unit: UnitData, x: number, y: number, width: number) {
|
||||
this.trackSideObject(this.add.text(x, y, '장비 / 숙련도', {
|
||||
this.trackSideIcon(x + 9, y + 10, 'mastery', 18);
|
||||
this.trackSideObject(this.add.text(x + 24, y, '장비 / 숙련도', {
|
||||
fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif',
|
||||
fontSize: '16px',
|
||||
color: '#f2e3bf',
|
||||
@@ -10323,7 +10655,7 @@ export class BattleScene extends Phaser.Scene {
|
||||
}));
|
||||
|
||||
equipmentSlots.forEach((slot, index) => {
|
||||
this.renderEquipmentRow(unit, slot, x, y + 24 + index * 28, width);
|
||||
this.renderEquipmentRow(unit, slot, x, y + 24 + index * 31, width);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -10332,23 +10664,22 @@ export class BattleScene extends Phaser.Scene {
|
||||
const item = getItem(state.itemId);
|
||||
const next = equipmentExpToNext(state.level);
|
||||
const isTreasure = item.rank === 'treasure';
|
||||
const bg = this.trackSideObject(this.add.rectangle(x, y, width, 26, isTreasure ? 0x1f2430 : 0x101820, isTreasure ? 0.94 : 0.86));
|
||||
const bg = this.trackSideObject(this.add.rectangle(x, y, width, 29, isTreasure ? 0x1f2430 : 0x101820, isTreasure ? 0.94 : 0.86));
|
||||
bg.setOrigin(0);
|
||||
bg.setStrokeStyle(1, isTreasure ? palette.gold : 0x53606c, isTreasure ? 0.58 : 0.42);
|
||||
|
||||
const iconFrame = this.trackSideObject(this.add.rectangle(x + 14, y + 13, 22, 22, 0x0a0f14, 0.82));
|
||||
const iconFrame = this.trackSideObject(this.add.rectangle(x + 15, y + 14.5, 24, 24, 0x0a0f14, 0.82));
|
||||
iconFrame.setStrokeStyle(1, isTreasure ? palette.gold : 0x53606c, isTreasure ? 0.72 : 0.48);
|
||||
|
||||
const icon = this.trackSideObject(this.add.image(x + 14, y + 13, this.itemIconKey(item.id)));
|
||||
icon.setDisplaySize(22, 22);
|
||||
this.trackSideIcon(x + 15, y + 14.5, this.itemIcon(item, slot), 22);
|
||||
|
||||
this.trackSideObject(this.add.text(x + 31, y + 6, equipmentSlotLabels[slot], {
|
||||
this.trackSideObject(this.add.text(x + 33, y + 4, equipmentSlotLabels[slot], {
|
||||
fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif',
|
||||
fontSize: '12px',
|
||||
color: '#9fb0bf',
|
||||
fontStyle: '700'
|
||||
}));
|
||||
this.trackSideObject(this.add.text(x + 78, y + 4, item.name, {
|
||||
this.trackSideObject(this.add.text(x + 78, y + 3, item.name, {
|
||||
fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif',
|
||||
fontSize: '13px',
|
||||
color: isTreasure ? '#f4dfad' : '#d4dce6',
|
||||
@@ -10362,6 +10693,7 @@ export class BattleScene extends Phaser.Scene {
|
||||
color: '#9fb0bf'
|
||||
}));
|
||||
|
||||
this.trackSideIcon(x + width - 69, y + 14, 'mastery', 15);
|
||||
const expText = this.trackSideObject(this.add.text(x + width - 54, y + 6, `${state.exp}/${next}`, {
|
||||
fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif',
|
||||
fontSize: '11px',
|
||||
@@ -10377,12 +10709,76 @@ export class BattleScene extends Phaser.Scene {
|
||||
}));
|
||||
levelText.setOrigin(1, 0);
|
||||
|
||||
this.drawGauge(x + 78, y + 20, width - 178, 4, state.exp / next, isTreasure ? 0xd8b15f : 0x58aee0);
|
||||
this.drawGauge(x + 78, y + 22, width - 178, 4, state.exp / next, isTreasure ? 0xd8b15f : 0x58aee0);
|
||||
}
|
||||
|
||||
private itemIconKey(itemId: string) {
|
||||
const key = `item-${itemId}`;
|
||||
return this.textures.exists(key) ? key : 'item-training-sword';
|
||||
private createBattleUiIcon(x: number, y: number, icon: BattleUiIconKey, size = 22) {
|
||||
const image = this.add.image(x, y, battleUiIconTextureKey, battleUiIconFrames[icon]);
|
||||
image.setDisplaySize(size, size);
|
||||
return image;
|
||||
}
|
||||
|
||||
private trackSideIcon(x: number, y: number, icon: BattleUiIconKey, size = 22) {
|
||||
return this.trackSideObject(this.createBattleUiIcon(x, y, icon, size));
|
||||
}
|
||||
|
||||
private trackCombatIcon(x: number, y: number, icon: BattleUiIconKey, size = 22, depth = 85) {
|
||||
const image = this.trackCombatObject(this.createBattleUiIcon(x, y, icon, size));
|
||||
image.setDepth(depth);
|
||||
return image;
|
||||
}
|
||||
|
||||
private itemIcon(item: ItemDefinition, slot: EquipmentSlot = item.slot): BattleUiIconKey {
|
||||
if (slot === 'armor') {
|
||||
return 'armor';
|
||||
}
|
||||
if (slot === 'accessory') {
|
||||
return item.id.includes('quiver') ? 'bow' : 'accessory';
|
||||
}
|
||||
if (item.id.includes('bow') || item.id.includes('quiver')) {
|
||||
return 'bow';
|
||||
}
|
||||
if (item.id.includes('spear') || item.id.includes('glaive') || item.id.includes('halberd') || item.id.includes('piercer')) {
|
||||
return 'spear';
|
||||
}
|
||||
if (item.id.includes('axe')) {
|
||||
return 'axe';
|
||||
}
|
||||
if (item.id.includes('fan') || item.id.includes('manual') || item.id.includes('scroll') || (item.strategyBonus ?? 0) > (item.attackBonus ?? 0)) {
|
||||
return 'strategy';
|
||||
}
|
||||
return 'sword';
|
||||
}
|
||||
|
||||
private unitMoveIcon(unit: UnitData): BattleUiIconKey {
|
||||
return unit.classKey === 'cavalry' ? 'horse' : 'move';
|
||||
}
|
||||
|
||||
private statIcon(key: keyof UnitStats): BattleUiIconKey {
|
||||
switch (key) {
|
||||
case 'might':
|
||||
return 'might';
|
||||
case 'intelligence':
|
||||
return 'intelligence';
|
||||
case 'leadership':
|
||||
return 'leadership';
|
||||
case 'agility':
|
||||
return 'move';
|
||||
case 'luck':
|
||||
return 'mastery';
|
||||
default:
|
||||
return 'might';
|
||||
}
|
||||
}
|
||||
|
||||
private actionIcon(preview: CombatPreview): BattleUiIconKey {
|
||||
if (preview.action === 'attack') {
|
||||
return this.itemIcon(getItem(preview.attacker.equipment.weapon.itemId), 'weapon');
|
||||
}
|
||||
if (preview.action === 'strategy') {
|
||||
return 'strategy';
|
||||
}
|
||||
return preview.usable ? this.itemIcon(getItem(preview.attacker.equipment.accessory.itemId), 'accessory') : 'accessory';
|
||||
}
|
||||
|
||||
private itemBonusText(item: ReturnType<typeof getItem>) {
|
||||
@@ -10418,11 +10814,12 @@ export class BattleScene extends Phaser.Scene {
|
||||
}, 0);
|
||||
}
|
||||
|
||||
private renderCompactValueBox(x: number, y: number, width: number, label: string, value: string) {
|
||||
private renderCompactValueBox(x: number, y: number, width: number, icon: BattleUiIconKey, label: string, value: string) {
|
||||
const bg = this.trackSideObject(this.add.rectangle(x, y, width, 38, 0x16212d, 0.92));
|
||||
bg.setOrigin(0);
|
||||
bg.setStrokeStyle(1, 0x53606c, 0.56);
|
||||
this.trackSideObject(this.add.text(x + 8, y + 6, label, {
|
||||
this.trackSideIcon(x + 13, y + 17, icon, 18);
|
||||
this.trackSideObject(this.add.text(x + 25, y + 6, label, {
|
||||
fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif',
|
||||
fontSize: '12px',
|
||||
color: '#9fb0bf'
|
||||
@@ -10436,15 +10833,16 @@ export class BattleScene extends Phaser.Scene {
|
||||
valueText.setOrigin(1, 0.5);
|
||||
}
|
||||
|
||||
private renderStatRow(label: string, value: number, x: number, y: number, width: number) {
|
||||
this.trackSideObject(this.add.text(x, y, label, {
|
||||
private renderStatRow(key: keyof UnitStats, label: string, value: number, x: number, y: number, width: number) {
|
||||
this.trackSideIcon(x + 9, y + 10, this.statIcon(key), 18);
|
||||
this.trackSideObject(this.add.text(x + 24, y, label, {
|
||||
fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif',
|
||||
fontSize: '16px',
|
||||
color: '#d4dce6',
|
||||
fontStyle: '700'
|
||||
}));
|
||||
|
||||
this.drawGauge(x + 76, y + 8, width - 130, 9, value / 100, value >= 80 ? 0xd8b15f : 0x58aee0);
|
||||
this.drawGauge(x + 92, y + 8, width - 146, 9, value / 100, value >= 80 ? 0xd8b15f : 0x58aee0);
|
||||
|
||||
const valueText = this.trackSideObject(this.add.text(x + width, y - 1, `${value}`, {
|
||||
fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif',
|
||||
|
||||
Reference in New Issue
Block a user