From a2ec898791f7b43caa12d432b531d5ffc7065e1b Mon Sep 17 00:00:00 2001 From: Wickedness Date: Fri, 3 Jul 2026 04:00:56 +0900 Subject: [PATCH] Improve battle result feedback --- src/game/scenes/BattleScene.ts | 341 +++++++++++++++++++-------------- 1 file changed, 202 insertions(+), 139 deletions(-) diff --git a/src/game/scenes/BattleScene.ts b/src/game/scenes/BattleScene.ts index 4dc969b..6352fe8 100644 --- a/src/game/scenes/BattleScene.ts +++ b/src/game/scenes/BattleScene.ts @@ -1281,6 +1281,7 @@ type ResultGaugeAnimation = { levelText: Phaser.GameObjects.Text; eventText: Phaser.GameObjects.Text; unit?: UnitData; + icon?: BattleUiIconKey; levelPrefix: string; previousLevel: number; previousExp: number; @@ -6829,6 +6830,7 @@ export class BattleScene extends Phaser.Scene { valueText, levelText, eventText, + icon: 'leadership', levelPrefix: 'Lv ', previousLevel, previousExp, @@ -6935,12 +6937,14 @@ export class BattleScene extends Phaser.Scene { characterEventText.setDepth(depth + 4); characterEventText.setAlpha(0); if (characterGained > 0 || unit.level > previousLevel) { + const statGains = this.characterLevelUpStatGains(unit, unit.level - previousLevel); this.queueResultGaugeAnimation({ fill: characterFill, valueText: characterValueText, levelText: characterLevelText, eventText: characterEventText, unit, + icon: statGains[0]?.icon ?? 'hp', levelPrefix: 'Lv ', previousLevel, previousExp, @@ -6949,7 +6953,7 @@ export class BattleScene extends Phaser.Scene { exp: unit.exp, next: 100, leveled: unit.level > previousLevel, - statGains: this.characterLevelUpStatGains(unit, unit.level - previousLevel), + statGains, flashArea: { x, y, width, height: 68, depth: depth + 3 } }); } @@ -6997,6 +7001,7 @@ export class BattleScene extends Phaser.Scene { valueText, levelText: label, eventText, + icon: slot === 'weapon' ? 'mastery' : slot === 'armor' ? 'armor' : 'accessory', levelPrefix: `${equipmentSlotLabels[slot]} Lv`, previousLevel: previousEquipmentLevel, previousExp: previousEquipmentExp, @@ -7224,11 +7229,16 @@ export class BattleScene extends Phaser.Scene { container.setAlpha(0); container.setScale(0.9); - const width = 480; - const bg = this.add.rectangle(0, 0, width, 96, 0x211624, 0.98); + const width = 520; + const height = 128; + const visualX = -width / 2 + 60; + const contentX = -width / 2 + 122; + const bg = this.add.rectangle(0, 0, width, height, 0x211624, 0.98); bg.setStrokeStyle(3, palette.gold, 0.96); - const burst = this.add.star(-width / 2 + 56, -2, 8, 14, 42, 0xffdf7b, 0.78); - const title = this.add.text(-width / 2 + 110, -34, animation.unit ? `${animation.unit.name} LEVEL UP` : 'LEVEL UP', { + const visualFrame = this.add.rectangle(visualX, -2, 66, 66, 0x0b1118, 0.92); + visualFrame.setStrokeStyle(1, palette.gold, 0.72); + const burst = this.add.star(visualX, -10, 8, 14, 42, 0xffdf7b, 0.78); + const title = this.add.text(contentX, -48, animation.unit ? `${animation.unit.name} LEVEL UP` : 'LEVEL UP', { fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif', fontSize: '27px', color: '#fff2b8', @@ -7236,13 +7246,13 @@ export class BattleScene extends Phaser.Scene { stroke: '#2b1606', strokeThickness: 5 }); - const levelText = this.add.text(-width / 2 + 110, 0, `${animation.levelPrefix}${animation.previousLevel} → ${animation.levelPrefix}${animation.level}`, { + const levelText = this.add.text(contentX, -12, `${animation.levelPrefix}${animation.previousLevel} → ${animation.levelPrefix}${animation.level}`, { fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif', fontSize: '17px', color: '#f4dfad', fontStyle: '700' }); - const statText = this.add.text(-width / 2 + 110, 22, stats ? '능력 상승' : '숙련 효과 강화', { + const statText = this.add.text(contentX, 14, stats ? '능력 상승' : '숙련 효과 강화', { fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif', fontSize: '16px', color: stats ? '#a8ffd0' : '#d8b15f', @@ -7250,16 +7260,19 @@ export class BattleScene extends Phaser.Scene { stroke: '#05070a', strokeThickness: 3 }); - container.add([bg, burst, title, levelText, statText]); + container.add([bg, visualFrame, burst, title, levelText, statText]); if (animation.statGains) { - this.addStatGainChipsToContainer(container, animation.statGains, -width / 2 + 110, 46, width - 124); + this.addStatGainChipsToContainer(container, animation.statGains, contentX, 46, width - 132); } if (animation.unit) { - const sprite = this.add.sprite(-width / 2 + 56, 10, this.unitActionTexture(animation.unit), this.unitActionFrameIndex('south', this.celebrationPose(animation.unit))); - sprite.setDisplaySize(76, 76); + const sprite = this.add.sprite(visualX, 8, this.unitActionTexture(animation.unit), this.unitActionFrameIndex('south', this.celebrationPose(animation.unit))); + sprite.setDisplaySize(82, 82); container.add(sprite); void this.playUnitActionFrames(sprite, animation.unit, 'south', this.celebrationPose(animation.unit), 105, 2); + } else { + const icon = this.createBattleUiIcon(visualX, -2, animation.icon ?? 'mastery', 54); + container.add(icon); } this.tweens.add({ targets: burst, angle: 240, scale: 1.34, alpha: 0.16, duration: 760, ease: 'Sine.easeOut' }); @@ -7286,27 +7299,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 * 12 + 50, 72, 102); + const chipWidth = Phaser.Math.Clamp(gain.label.length * 10 + 52, 72, 96); if (cursorX + chipWidth > x + maxWidth) { return; } - const bg = this.add.rectangle(cursorX, y, chipWidth, 28, 0x0b1118, 0.9); + const bg = this.add.rectangle(cursorX, y, chipWidth, 30, 0x0b1118, 0.92); bg.setOrigin(0); - bg.setStrokeStyle(1, 0x53606c, 0.5); - const icon = this.createBattleUiIcon(cursorX + 15, y + 14, gain.icon, 22); - const text = this.add.text(cursorX + 31, y + 6, `+${gain.amount}`, { + 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}`, { fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif', - fontSize: '12px', + fontSize: '13px', color: '#a8ffd0', fontStyle: '700' }); - const label = this.add.text(cursorX + 54, y + 6, gain.label, { + const label = this.add.text(cursorX + 55, y + 8, gain.label, { fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif', fontSize: '11px', color: '#d4dce6' }); container.add([bg, icon, text, label]); - cursorX += chipWidth + 6; + cursorX += chipWidth + 5; }); } @@ -9182,7 +9195,7 @@ export class BattleScene extends Phaser.Scene { } else { this.showCombatMiss(defenderX, groundY - 70, depth + 14); } - void this.playImmediateGrowthPopups(growthEntries, left + panelWidth / 2, top + 214, depth + 22); + const outcomeCard = this.renderCombatOutcomeCard(result, left + panelWidth / 2 - 198, top + 214, 396, 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}`); @@ -9202,18 +9215,7 @@ export class BattleScene extends Phaser.Scene { attackerStatus.nameText.setText(`${result.attacker.name} Lv ${result.characterGrowth.level}`); } - const resultLabel = result.hit ? `${result.critical ? '치명타! ' : ''}${result.damage} 피해` : '회피!'; - const resultText = this.trackCombatObject(this.add.text(left + panelWidth / 2, top + 252, resultLabel, { - fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif', - fontSize: '22px', - color: result.hit ? (result.critical ? '#ff8f5f' : '#ffdf7b') : '#d9f1ff', - fontStyle: '700', - stroke: '#2b0909', - strokeThickness: 4 - })); - resultText.setOrigin(0.5); - resultText.setDepth(depth + 16); - + await this.dismissCombatOutcomeCard(outcomeCard); await this.playGrowthRewardPanel({ x: left + 86, y: top + 410, @@ -9228,6 +9230,105 @@ export class BattleScene extends Phaser.Scene { this.hideCombatCutIn(); } + private renderCombatOutcomeCard(result: CombatResult, x: number, y: number, width: number, depth: number) { + const height = 66; + 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.97); + bg.setOrigin(0); + bg.setStrokeStyle(2, tone, 0.84); + + const iconFrame = this.add.rectangle(34, height / 2, 52, 52, 0x101820, 0.94); + iconFrame.setStrokeStyle(1, tone, 0.68); + const iconImage = this.createBattleUiIcon(34, height / 2, icon, 48); + + const titleText = this.add.text(70, 9, title, { + fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif', + fontSize: '21px', + color: result.hit ? '#fff2b8' : '#d9f1ff', + fontStyle: '700', + fixedWidth: 148 + }); + + const detail = result.defeated ? `${result.defender.name} 퇴각` : `${result.defender.name} HP ${result.defender.hp}/${result.defender.maxHp}`; + const detailText = this.add.text(72, 39, detail, { + fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif', + fontSize: '13px', + color: result.defeated ? '#ffb6a6' : '#9fb0bf', + fontStyle: '700', + fixedWidth: 190 + }); + + const value = result.hit ? `피해 ${result.damage}` : '피해 0'; + const valueText = this.add.text(width - 16, 15, value, { + fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif', + fontSize: '24px', + color: result.hit ? '#ffdf7b' : '#9fb0bf', + fontStyle: '700' + }); + valueText.setOrigin(1, 0); + + container.add([bg, iconFrame, iconImage, titleText, detailText, valueText]); + + if (result.defeated) { + const badge = this.add.rectangle(width - 82, 42, 66, 22, 0x101820, 0.94); + badge.setOrigin(0); + badge.setStrokeStyle(1, 0xd8732c, 0.72); + const badgeIcon = this.createBattleUiIcon(width - 69, 53, 'counter', 19); + const badgeText = this.add.text(width - 54, 47, '퇴각', { + fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif', + fontSize: '10px', + color: '#f2e3bf', + fontStyle: '700', + fixedWidth: 31 + }); + container.add([badge, badgeIcon, badgeText]); + } + + this.tweens.add({ targets: container, alpha: 1, scale: 1, y: y - 4, duration: 160, ease: 'Sine.easeOut' }); + return container; + } + + private async dismissCombatOutcomeCard(card: Phaser.GameObjects.Container) { + if (!card.active) { + return; + } + this.tweens.add({ + targets: card, + alpha: 0, + y: card.y - 10, + duration: 150, + ease: 'Sine.easeIn', + onComplete: () => card.destroy() + }); + await this.delay(170); + } + private renderCombatActionRibbon(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.92)); @@ -9406,25 +9507,11 @@ 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); 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}`); - const resultLine = - result.usable.effect === 'heal' - ? `${result.target.name} 병력 +${result.healAmount}` - : `${result.target.name} ${result.usable.name}: 공격 +${result.buff?.attackBonus ?? 0} / 명중 +${result.buff?.hitBonus ?? 0} / 치명 +${result.buff?.criticalBonus ?? 0}`; - const resultText = this.trackCombatObject(this.add.text(left + panelWidth / 2, top + 132, resultLine, { - fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif', - fontSize: '19px', - color: result.usable.effect === 'heal' ? '#a8ffd0' : '#ffdf7b', - fontStyle: '700', - stroke: '#05070a', - strokeThickness: 4 - })); - resultText.setOrigin(0.5); - resultText.setDepth(depth + 5); - void this.playImmediateGrowthPopups(growthEntries, left + panelWidth / 2, top + 178, depth + 8); - + await this.dismissCombatOutcomeCard(outcomeCard); await this.playGrowthRewardPanel({ x: left + 64, y: top + 322, @@ -9439,6 +9526,57 @@ export class BattleScene extends Phaser.Scene { this.hideCombatCutIn(); } + private renderSupportOutcomeCard(result: SupportResult, x: number, y: number, width: number, depth: number) { + const height = 66; + 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.97); + bg.setOrigin(0); + bg.setStrokeStyle(2, tone, 0.84); + + const iconFrame = this.add.rectangle(34, height / 2, 52, 52, 0x101820, 0.94); + iconFrame.setStrokeStyle(1, tone, 0.68); + const iconImage = this.createBattleUiIcon(34, height / 2, this.usablePowerIcon(result.usable), 48); + + const title = heal ? '병력 회복' : '강화 적용'; + const titleText = this.add.text(70, 9, title, { + fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif', + fontSize: '21px', + color: heal ? '#a8ffd0' : '#fff2b8', + fontStyle: '700', + fixedWidth: 148 + }); + + const detail = heal + ? `${result.target.name} HP ${result.target.hp}/${result.target.maxHp}` + : `공+${result.buff?.attackBonus ?? 0} 명+${result.buff?.hitBonus ?? 0} 치+${result.buff?.criticalBonus ?? 0}`; + const detailText = this.add.text(72, 39, detail, { + fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif', + fontSize: '13px', + color: '#d4dce6', + fontStyle: '700', + fixedWidth: 200 + }); + + const value = heal ? `+${result.healAmount}` : `${result.buff?.turns ?? result.usable.duration ?? 1}턴`; + const valueText = this.add.text(width - 16, 15, value, { + fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif', + fontSize: '24px', + color: heal ? '#a8ffd0' : '#ffdf7b', + fontStyle: '700' + }); + valueText.setOrigin(1, 0); + container.add([bg, iconFrame, iconImage, titleText, detailText, valueText]); + + this.tweens.add({ targets: container, alpha: 1, scale: 1, y: y - 4, duration: 160, ease: 'Sine.easeOut' }); + return container; + } + private renderSupportActionRibbon(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.92)); @@ -9678,88 +9816,6 @@ export class BattleScene extends Phaser.Scene { return undefined; } - private playImmediateGrowthPopups(entries: GrowthGaugeEntry[], x: number, y: number, depth: number) { - const visibleEntries = entries.filter((entry) => entry.amountLabel !== '+0' || entry.leveled).slice(0, 5); - if (visibleEntries.length === 0) { - return Promise.resolve(); - } - - soundDirector.playGrowthTick(); - visibleEntries.forEach((entry, index) => { - const label = this.growthPopupLabel(entry); - const statLine = entry.leveled ? this.statGainLine(entry.statGains) : undefined; - const width = Math.min(500, Math.max(230, label.length * 13 + (statLine ? 72 : 34))); - const rowY = y + index * 31; - const container = this.trackCombatObject(this.add.container(x, rowY)); - container.setAlpha(0); - container.setScale(0.92); - container.setDepth(depth + index); - - const bg = this.add.rectangle(0, 0, width, statLine ? 36 : 28, 0x101820, 0.94); - bg.setStrokeStyle(2, entry.leveled ? palette.gold : entry.color, entry.leveled ? 0.96 : 0.74); - const text = this.add.text(statLine ? -width / 2 + 18 : 0, statLine ? -12 : -9, label, { - fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif', - fontSize: entry.leveled ? '16px' : '15px', - color: entry.leveled ? '#fff2b8' : '#f4dfad', - fontStyle: '700', - stroke: '#05070a', - strokeThickness: 3 - }); - text.setOrigin(statLine ? 0 : 0.5, 0); - container.add([bg, text]); - - if (statLine) { - const statText = this.add.text(-width / 2 + 18, 8, statLine, { - fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif', - fontSize: '13px', - color: '#a8ffd0', - fontStyle: '700', - stroke: '#05070a', - strokeThickness: 3 - }); - container.add(statText); - } - - this.tweens.add({ - targets: container, - alpha: 1, - scale: entry.leveled ? 1.08 : 1, - y: rowY - 6, - delay: index * 82, - duration: 150, - ease: 'Back.easeOut' - }); - this.tweens.add({ - targets: container, - alpha: 0, - y: rowY - 34, - delay: 620 + index * 82, - duration: 420, - ease: 'Sine.easeIn', - onComplete: () => container.destroy() - }); - }); - - return this.delay(visibleEntries.length * 82 + 720); - } - - private growthPopupLabel(entry: GrowthGaugeEntry) { - if (entry.kind === 'character') { - return `${entry.owner} 경험치 ${entry.amountLabel}${entry.leveled ? ' LEVEL UP' : ''}`; - } - if (entry.kind === 'bond') { - return `${entry.owner} 공명 ${entry.amountLabel}${entry.leveled ? ' LEVEL UP' : ''}`; - } - - const mastery = - entry.slot === 'weapon' - ? '무기 숙련' - : entry.slot === 'armor' - ? '방어구 숙련' - : '보조구 숙련'; - return `${entry.owner} ${mastery} ${entry.amountLabel}${entry.leveled ? ' LEVEL UP' : ''}`; - } - private statGainLine(gains?: LevelUpStatGain[]) { const visibleGains = gains?.filter((gain) => gain.amount > 0) ?? []; return visibleGains.length > 0 ? visibleGains.map((gain) => `${gain.label} +${gain.amount}`).join(' ') : undefined; @@ -9827,7 +9883,9 @@ 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 = 116; + const panelHeight = 132; + const visualX = -panelWidth / 2 + 60; + const contentX = celebrant ? -panelWidth / 2 + 122 : -panelWidth / 2 + 102; const container = this.trackCombatObject(this.add.container(x, y)); container.setDepth(depth); container.setAlpha(0); @@ -9835,8 +9893,10 @@ export class BattleScene extends Phaser.Scene { const bg = this.add.rectangle(0, 0, panelWidth, panelHeight, 0x211624, 0.97); bg.setStrokeStyle(3, palette.gold, 0.96); - const glow = this.add.circle(-panelWidth / 2 + 58, 0, 42, 0xffdf7b, 0.12); - const title = this.add.text(celebrant ? -panelWidth / 2 + 112 : 0, -36, `${entry.owner} LEVEL UP`, { + const visualFrame = this.add.rectangle(visualX, 0, 66, 66, 0x0b1118, 0.92); + visualFrame.setStrokeStyle(1, palette.gold, 0.72); + const glow = this.add.circle(visualX, 0, 42, 0xffdf7b, 0.12); + const title = this.add.text(celebrant ? contentX : 0, -48, `${entry.owner} LEVEL UP`, { fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif', fontSize: '25px', color: '#fff2b8', @@ -9845,14 +9905,14 @@ export class BattleScene extends Phaser.Scene { strokeThickness: 5 }); title.setOrigin(celebrant ? 0 : 0.5, 0); - const levelText = this.add.text(celebrant ? -panelWidth / 2 + 112 : 0, -3, `Lv ${entry.previousLevel} → Lv ${entry.level}`, { + const levelText = this.add.text(celebrant ? contentX : 0, -12, `Lv ${entry.previousLevel} → Lv ${entry.level}`, { fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif', fontSize: '18px', color: '#f4dfad', fontStyle: '700' }); levelText.setOrigin(celebrant ? 0 : 0.5, 0); - const statText = this.add.text(celebrant ? -panelWidth / 2 + 112 : 0, 25, entry.statGains ? '능력 상승' : `${entry.label} 효과 강화`, { + const statText = this.add.text(celebrant ? contentX : 0, 16, entry.statGains ? '능력 상승' : `${entry.label} 효과 강화`, { fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif', fontSize: '16px', color: '#a8ffd0', @@ -9861,18 +9921,21 @@ export class BattleScene extends Phaser.Scene { strokeThickness: 3 }); statText.setOrigin(celebrant ? 0 : 0.5, 0); - container.add([bg, glow, title, levelText, statText]); + container.add([bg, visualFrame, glow, title, levelText, statText]); if (entry.statGains) { - this.addStatGainChipsToContainer(container, entry.statGains, celebrant ? -panelWidth / 2 + 112 : -panelWidth / 2 + 40, 51, panelWidth - 140); + this.addStatGainChipsToContainer(container, entry.statGains, celebrant ? contentX : -panelWidth / 2 + 44, 50, panelWidth - 132); } if (celebrant) { - const sprite = this.add.sprite(-panelWidth / 2 + 56, 6, this.unitActionTexture(celebrant), this.unitActionFrameIndex('south', this.celebrationPose(celebrant))); + const sprite = this.add.sprite(visualX, 8, this.unitActionTexture(celebrant), this.unitActionFrameIndex('south', this.celebrationPose(celebrant))); sprite.setDisplaySize(82, 82); - const burst = this.add.star(-panelWidth / 2 + 56, -12, 8, 12, 34, 0xffdf7b, 0.72); + const burst = this.add.star(visualX, -12, 8, 12, 34, 0xffdf7b, 0.72); container.add([burst, sprite]); this.tweens.add({ targets: burst, angle: 220, scale: 1.32, alpha: 0.18, duration: 760, ease: 'Sine.easeOut' }); void this.playUnitActionFrames(sprite, celebrant, 'south', this.celebrationPose(celebrant), 105, 2); + } else { + const icon = this.createBattleUiIcon(visualX, 0, this.growthEntryIcon(entry), 54); + container.add(icon); } this.tweens.add({ targets: container, alpha: 1, scale: 1.04, duration: 180, ease: 'Back.easeOut' });