From d8177c88dcb2dc8fac8775411b1c8a9cc44d6e1d Mon Sep 17 00:00:00 2001 From: Wickedness Date: Mon, 29 Jun 2026 01:47:53 +0900 Subject: [PATCH] Improve battle unit feedback and detail panel --- src/game/audio/SoundDirector.ts | 68 +++++++++ src/game/scenes/BattleScene.ts | 262 ++++++++++++++++++++++++++++---- 2 files changed, 301 insertions(+), 29 deletions(-) diff --git a/src/game/audio/SoundDirector.ts b/src/game/audio/SoundDirector.ts index 15a4acf..de9b883 100644 --- a/src/game/audio/SoundDirector.ts +++ b/src/game/audio/SoundDirector.ts @@ -17,6 +17,8 @@ class SoundDirector { private currentMusicKey?: string; private pendingMusicKey?: string; private musicFadeTimer?: number; + private lastEffect?: { key: string; at: number; volume: number; rate: number }; + private lastMovementStep?: { key: string; mounted: boolean; stepIndex: number; at: number; volume?: number; rate?: number }; private readonly musicVolume = 0.22; private readonly effectVolume = 0.42; @@ -118,6 +120,14 @@ class SoundDirector { this.playEffect('footstep-walk', { volume: 0.2, rate: 1.35, stopAfterMs: 320 }); } + playMovementStep(mounted: boolean, stepIndex: number, options: PlayEffectOptions = {}) { + const key = mounted ? 'horse-gallop' : 'footstep-walk'; + this.lastMovementStep = { key, mounted, stepIndex, at: performance.now(), volume: options.volume, rate: options.rate }; + const played = this.playEffect(key, options); + this.playStepPulse(mounted, stepIndex, options.volume); + return played; + } + playMeleeSwing(hit: boolean, critical: boolean) { this.playEffect('sword-slash', { volume: critical ? 0.62 : hit ? 0.52 : 0.38, @@ -169,6 +179,7 @@ class SoundDirector { effect.volume = options.volume ?? this.effectVolume; effect.playbackRate = options.rate ?? 1; effect.muted = this.muted; + this.lastEffect = { key, at: performance.now(), volume: effect.volume, rate: effect.playbackRate }; effect.addEventListener( 'ended', () => { @@ -188,6 +199,18 @@ class SoundDirector { return true; } + getDebugState() { + return { + started: this.started, + muted: this.muted, + contextState: this.context?.state ?? null, + currentMusicKey: this.currentMusicKey ?? null, + pendingMusicKey: this.pendingMusicKey ?? null, + lastEffect: this.lastEffect ?? null, + lastMovementStep: this.lastMovementStep ?? null + }; + } + playMusic(key?: string) { if (!key) { return; @@ -248,6 +271,51 @@ class SoundDirector { oscillator.stop(now + duration + 0.04); } + private playStepPulse(mounted: boolean, stepIndex: number, volume = 0.2) { + if (!this.context || this.muted) { + return; + } + + const scale = Math.max(0.35, Math.min(1.2, volume / 0.24)); + const start = this.context.currentTime; + const primaryFrequency = mounted ? 112 + (stepIndex % 2) * 26 : 148 + (stepIndex % 2) * 34; + const duration = mounted ? 0.09 : 0.075; + const gainValue = (mounted ? 0.022 : 0.016) * scale; + + this.playPercussivePulse(primaryFrequency, start, duration, gainValue, mounted ? 'square' : 'triangle'); + + if (mounted) { + this.playPercussivePulse(82 + (stepIndex % 2) * 18, start + 0.055, 0.075, gainValue * 0.78, 'square'); + } + } + + private playPercussivePulse( + frequency: number, + start: number, + duration: number, + gainValue: number, + type: OscillatorType + ) { + if (!this.context || this.muted) { + return; + } + + const oscillator = this.context.createOscillator(); + const gain = this.context.createGain(); + + oscillator.type = type; + oscillator.frequency.setValueAtTime(frequency, start); + oscillator.frequency.exponentialRampToValueAtTime(Math.max(40, frequency * 0.62), start + duration); + gain.gain.setValueAtTime(0.0001, start); + gain.gain.linearRampToValueAtTime(gainValue, start + 0.006); + gain.gain.exponentialRampToValueAtTime(0.0001, start + duration); + + oscillator.connect(gain); + gain.connect(this.master ?? this.context.destination); + oscillator.start(start); + oscillator.stop(start + duration + 0.025); + } + private fadeMusic(previous: HTMLAudioElement | undefined, next: HTMLAudioElement, targetVolume: number) { if (this.musicFadeTimer) { window.clearInterval(this.musicFadeTimer); diff --git a/src/game/scenes/BattleScene.ts b/src/game/scenes/BattleScene.ts index 3b4689a..b3b5978 100644 --- a/src/game/scenes/BattleScene.ts +++ b/src/game/scenes/BattleScene.ts @@ -2880,6 +2880,8 @@ export class BattleScene extends Phaser.Scene { private turnPromptObjects: Phaser.GameObjects.GameObject[] = []; private turnPromptMode?: TurnPromptMode; private combatCutInObjects: Phaser.GameObjects.GameObject[] = []; + private facingIndicatorObjects: Phaser.GameObjects.GameObject[] = []; + private facingIndicatorUnitId?: string; private resultObjects: Phaser.GameObjects.GameObject[] = []; private resultGaugeAnimations: ResultGaugeAnimation[] = []; private resultAnimationToken = 0; @@ -2905,6 +2907,7 @@ export class BattleScene extends Phaser.Scene { private miniMapObjects: Phaser.GameObjects.GameObject[] = []; private miniMapUnitDots = new Map(); private miniMapViewport?: Phaser.GameObjects.Rectangle; + private miniMapVisible = true; private cameraTileX = 0; private cameraTileY = 0; private edgeScrollElapsed = 0; @@ -2952,6 +2955,7 @@ export class BattleScene extends Phaser.Scene { this.phase = 'idle'; soundDirector.playMusic('battle-prep'); this.input.mouse?.disableContextMenu(); + this.installBattleAudioUnlock(); this.input.on('pointerdown', (pointer: Phaser.Input.Pointer) => { if (pointer.rightButtonDown()) { this.handleRightClick(pointer); @@ -2968,6 +2972,16 @@ export class BattleScene extends Phaser.Scene { this.ensureScenarioAssets(() => this.drawBattleScene()); } + private installBattleAudioUnlock() { + const unlock = () => { + soundDirector.start(); + soundDirector.resume(); + }; + + this.input.once('pointerdown', unlock); + this.input.keyboard?.once('keydown', unlock); + } + private drawBattleScene() { this.drawMap(); this.drawUnits(); @@ -3415,12 +3429,19 @@ export class BattleScene extends Phaser.Scene { battleUnits.forEach((unit) => this.positionUnitView(unit)); this.updateMiniMap(); + this.refreshFacingIndicator(); } private updateMiniMap() { if (!this.miniMapLayout) { return; } + if (!this.miniMapVisible) { + this.miniMapObjects.forEach((object) => this.setMiniMapObjectVisible(object, false)); + return; + } + + this.miniMapObjects.forEach((object) => this.setMiniMapObjectVisible(object, true)); const { x, y, cellSize } = this.miniMapLayout; this.miniMapUnitDots.forEach((dot, unitId) => { @@ -3436,6 +3457,22 @@ export class BattleScene extends Phaser.Scene { this.miniMapViewport?.setPosition(x + this.cameraTileX * cellSize, y + this.cameraTileY * cellSize); } + private setMiniMapVisible(visible: boolean) { + this.miniMapVisible = visible; + this.updateMiniMap(); + } + + private setMiniMapObjectVisible(object: Phaser.GameObjects.GameObject, visible: boolean) { + const visibleObject = object as Phaser.GameObjects.GameObject & { + setVisible: (value: boolean) => Phaser.GameObjects.GameObject; + input?: { enabled: boolean }; + }; + visibleObject.setVisible(visible); + if (visibleObject.input) { + visibleObject.input.enabled = visible; + } + } + private positionTileObject(object: Phaser.GameObjects.Rectangle, x: number, y: number) { const screenX = this.layout.gridX + (x - this.cameraTileX) * this.layout.tileSize; const screenY = this.layout.gridY + (y - this.cameraTileY) * this.layout.tileSize; @@ -7138,13 +7175,14 @@ export class BattleScene extends Phaser.Scene { ) ); attackerSprite.setDepth(depth + 6); - attackerSprite.setDisplaySize(126, 126); + this.applyCombatSpriteDisplaySize(attackerSprite, result.attacker); + this.applyActionSpriteBlend(attackerSprite); const defenderSprite = this.trackCombatObject( this.add.sprite(defenderX, groundY, this.unitTextureKey(result.defender), this.unitFrameIndex(defenderDirection)) ); defenderSprite.setDepth(depth + 6); - defenderSprite.setDisplaySize(126, 126); + this.applyCombatSpriteDisplaySize(defenderSprite, result.defender); const attackerStatus = this.renderCombatStatusPanel(result.attacker, left + 26, top + 286, 392, result.characterGrowth); const defenderStatus = this.renderCombatStatusPanel(result.defender, left + panelWidth - 418, top + 286, 392); @@ -7211,6 +7249,28 @@ export class BattleScene extends Phaser.Scene { this.hideCombatCutIn(); } + private applyCombatSpriteDisplaySize(sprite: Phaser.GameObjects.Sprite, unit: UnitData, compact = false) { + const size = this.combatSpriteDisplaySize(unit, compact); + sprite.setDisplaySize(size, size); + } + + private combatSpriteDisplaySize(unit: UnitData, compact = false) { + const baseSizeByClass: Record = { + lord: 128, + infantry: 124, + cavalry: 136, + spearman: 126, + archer: 120, + strategist: 116, + quartermaster: 116, + bandit: 124, + yellowTurban: 124, + rebelLeader: 128 + }; + const size = baseSizeByClass[unit.classKey] ?? 124; + return Math.round(size * (compact ? 0.9 : 1)); + } + private async playSupportCutIn(result: SupportResult) { this.hideCombatCutIn(); @@ -7245,12 +7305,13 @@ export class BattleScene extends Phaser.Scene { this.add.sprite(left + 160, top + 146, this.unitActionTexture(result.user), this.unitActionFrameIndex('east', supportPose)) ); userSprite.setDepth(depth + 3); - userSprite.setDisplaySize(112, 112); + this.applyCombatSpriteDisplaySize(userSprite, result.user, true); + this.applyActionSpriteBlend(userSprite); void this.playUnitActionFrames(userSprite, result.user, 'east', supportPose, 100, 2); const targetSprite = this.trackCombatObject(this.add.sprite(left + panelWidth - 160, top + 146, this.unitTextureKey(result.target), this.unitFrameIndex('west'))); targetSprite.setDepth(depth + 3); - targetSprite.setDisplaySize(112, 112); + this.applyCombatSpriteDisplaySize(targetSprite, result.target, true); const effectColor = result.usable.effect === 'heal' ? 0x59d18c : 0xd8b15f; const effect = this.trackCombatObject(this.add.circle(left + panelWidth / 2, top + 145, 30, effectColor, 0.76)); @@ -7776,6 +7837,7 @@ export class BattleScene extends Phaser.Scene { this.add.sprite(x, y, this.unitActionTexture(unit), this.unitActionFrameIndex('south', this.celebrationPose(unit))) ); sprite.setDisplaySize(82, 82); + this.applyActionSpriteBlend(sprite); sprite.setDepth(depth); return sprite; } @@ -7855,6 +7917,8 @@ export class BattleScene extends Phaser.Scene { const mounted = result.attacker.classKey === 'cavalry'; const windupX = attackerX - direction * 28; const contactX = defenderX - direction * 126; + const baseScaleX = attackerSprite.scaleX; + const baseScaleY = attackerSprite.scaleY; soundDirector.playMeleeRush(mounted); void this.playUnitActionFrames(attackerSprite, result.attacker, 'east', 'attack', mounted ? 72 : 82); @@ -7863,7 +7927,8 @@ export class BattleScene extends Phaser.Scene { targets: attackerSprite, x: windupX, y: groundY + 5, - scaleX: 0.96, + scaleX: baseScaleX * 0.96, + scaleY: baseScaleY, duration: 110, ease: 'Sine.easeIn' }); @@ -7875,7 +7940,8 @@ export class BattleScene extends Phaser.Scene { targets: attackerSprite, x: contactX, y: groundY - (mounted ? 4 : 2), - scaleX: 1.05, + scaleX: baseScaleX * 1.05, + scaleY: baseScaleY, duration: mounted ? 170 : 205, ease: 'Cubic.easeIn' }); @@ -7906,7 +7972,8 @@ export class BattleScene extends Phaser.Scene { targets: attackerSprite, x: attackerX, y: groundY, - scaleX: 1, + scaleX: baseScaleX, + scaleY: baseScaleY, duration: 180, ease: 'Sine.easeOut' }); @@ -7991,10 +8058,13 @@ export class BattleScene extends Phaser.Scene { } void this.playUnitActionFrames(attackerSprite, result.attacker, 'east', this.actionPoseForCommand(result.action), 92); + const baseScaleX = attackerSprite.scaleX; + const baseScaleY = attackerSprite.scaleY; this.createCasterPulse(attackerX + direction * 48, groundY - 50, depth + 2, result.action === 'item' ? 0xd8b15f : 0x6cc5ff); this.tweens.add({ targets: attackerSprite, - scale: 1.08, + scaleX: baseScaleX * 1.08, + scaleY: baseScaleY * 1.08, y: groundY - 4, duration: 150, yoyo: true, @@ -8486,6 +8556,7 @@ export class BattleScene extends Phaser.Scene { this.setUnitBasePosition(view, targetX, targetY); this.syncUnitMotion(unit, view, direction); this.updateMiniMap(); + this.refreshFacingIndicator(unit.id); resolve(); } }); @@ -9019,6 +9090,7 @@ export class BattleScene extends Phaser.Scene { this.setUnitBasePosition(view, targetX, targetY); this.syncUnitMotion(unit, view, direction); this.updateMiniMap(); + this.refreshFacingIndicator(unit.id); } }); this.tweens.add({ @@ -9047,7 +9119,6 @@ export class BattleScene extends Phaser.Scene { private playMovementSound(unit: UnitData, duration: number, tileDistance = 1) { const isMounted = this.isMountedUnit(unit); - const key = isMounted ? 'horse-gallop' : 'footstep-walk'; const pulseCount = isMounted ? Math.max(3, Math.min(10, tileDistance * 2 + 1)) : Math.max(2, Math.min(8, tileDistance + 1)); @@ -9055,7 +9126,7 @@ export class BattleScene extends Phaser.Scene { for (let index = 0; index < pulseCount; index += 1) { this.time.delayedCall(Math.round(index * interval), () => { - soundDirector.playEffect(key, { + soundDirector.playMovementStep(isMounted, index, { volume: isMounted ? 0.24 : 0.18, rate: isMounted ? 0.98 + (index % 2) * 0.12 : 0.9 + (index % 2) * 0.12, stopAfterMs: Math.round(Math.min(isMounted ? 210 : 260, interval + 80)) @@ -9071,6 +9142,107 @@ export class BattleScene extends Phaser.Scene { return deltaY >= 0 ? 'south' : 'north'; } + private currentUnitDirection(unit: UnitData): UnitDirection { + return this.unitViews.get(unit.id)?.direction ?? 'south'; + } + + private unitDirectionLabel(direction: UnitDirection) { + const labels: Record = { + south: '아래', + east: '오른쪽', + north: '위', + west: '왼쪽' + }; + return labels[direction]; + } + + private directionSymbol(direction: UnitDirection) { + const symbols: Record = { + south: '▼', + east: '▶', + north: '▲', + west: '◀' + }; + return symbols[direction]; + } + + private directionVector(direction: UnitDirection) { + const vectors: Record = { + south: { x: 0, y: 1 }, + east: { x: 1, y: 0 }, + north: { x: 0, y: -1 }, + west: { x: -1, y: 0 } + }; + return vectors[direction]; + } + + private refreshFacingIndicator(unitId = this.facingIndicatorUnitId) { + if (!unitId) { + return; + } + + const unit = battleUnits.find((candidate) => candidate.id === unitId); + if (!unit) { + this.clearFacingIndicator(); + return; + } + + this.showFacingIndicator(unit); + } + + private showFacingIndicator(unit: UnitData) { + this.clearFacingIndicator(); + this.facingIndicatorUnitId = unit.id; + + const view = this.unitViews.get(unit.id); + if (!view || unit.hp <= 0 || !this.isTileVisible(unit.x, unit.y)) { + return; + } + + const direction = this.currentUnitDirection(unit); + const vector = this.directionVector(direction); + const x = view.sprite.x + vector.x * this.layout.tileSize * 0.34; + const y = view.sprite.y + vector.y * this.layout.tileSize * 0.34 - this.layout.tileSize * 0.28; + const marker = this.add.circle(x, y, 13, 0x101820, 0.86); + marker.setStrokeStyle(1, palette.gold, 0.88); + marker.setDepth(34); + + const arrow = this.add.text(x, y - 1, this.directionSymbol(direction), { + fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif', + fontSize: '18px', + color: '#ffe28a', + fontStyle: '700', + stroke: '#05070a', + strokeThickness: 3 + }); + arrow.setOrigin(0.5); + arrow.setDepth(35); + + const label = this.add.text(x, y + 16, this.unitDirectionLabel(direction), { + fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif', + fontSize: '10px', + color: '#f4dfad', + stroke: '#05070a', + strokeThickness: 3 + }); + label.setOrigin(0.5, 0); + label.setDepth(35); + + if (this.mapMask) { + marker.setMask(this.mapMask); + arrow.setMask(this.mapMask); + label.setMask(this.mapMask); + } + + this.facingIndicatorObjects = [marker, arrow, label]; + } + + private clearFacingIndicator() { + this.facingIndicatorObjects.forEach((object) => object.destroy()); + this.facingIndicatorObjects = []; + this.facingIndicatorUnitId = undefined; + } + private playUnitWalk(view: UnitView, direction: UnitDirection) { view.direction = direction; this.stopUnitIdle(view); @@ -9110,6 +9282,7 @@ export class BattleScene extends Phaser.Scene { private setUnitDirectionFrame(view: UnitView, direction: UnitDirection) { view.sprite.setTexture(view.textureBase, this.unitFrameIndex(direction)); view.sprite.setFlipX(false); + this.applyBaseSpriteBlend(view.sprite); } private setUnitBasePosition(view: UnitView, x: number, y: number) { @@ -9327,6 +9500,15 @@ export class BattleScene extends Phaser.Scene { private setUnitActionFrame(sprite: Phaser.GameObjects.Sprite, unit: UnitData, direction: UnitDirection, pose: UnitActionPose, frame = 0) { sprite.setTexture(this.unitActionTexture(unit), this.unitActionFrameIndex(direction, pose, frame)); + this.applyActionSpriteBlend(sprite); + } + + private applyActionSpriteBlend(sprite: Phaser.GameObjects.Sprite) { + sprite.setBlendMode(Phaser.BlendModes.SCREEN); + } + + private applyBaseSpriteBlend(sprite: Phaser.GameObjects.Sprite) { + sprite.setBlendMode(Phaser.BlendModes.NORMAL); } private playUnitActionFrames( @@ -9663,6 +9845,7 @@ export class BattleScene extends Phaser.Scene { private renderSituationPanel(message?: string) { this.clearSidePanelContent(); + this.setMiniMapVisible(true); const { panelX, panelWidth } = this.layout; const left = panelX + 24; const width = panelWidth - 48; @@ -9943,6 +10126,7 @@ export class BattleScene extends Phaser.Scene { private renderRosterPanel(tab: RosterTab = this.rosterTab, message?: string) { this.rosterTab = tab; this.clearSidePanelContent(); + this.setMiniMapVisible(true); const { panelX, panelY, panelWidth, panelHeight } = this.layout; const left = panelX + 24; @@ -10028,6 +10212,7 @@ export class BattleScene extends Phaser.Scene { private renderUnitDetail(unit: UnitData, message?: string) { this.clearSidePanelContent(); + this.setMiniMapVisible(false); const { panelX, panelWidth } = this.layout; const left = panelX + 24; @@ -10039,6 +10224,7 @@ export class BattleScene extends Phaser.Scene { const terrain = battleMap.terrain[unit.y][unit.x]; const terrainRule = getTerrainRule(terrain); const terrainRating = unitClass.terrainRatings[terrain]; + const direction = this.currentUnitDirection(unit); const header = this.trackSideObject(this.add.rectangle(left, top, width, 60, 0x101820, 0.94)); header.setOrigin(0); @@ -10056,6 +10242,13 @@ export class BattleScene extends Phaser.Scene { color: acted ? '#bdbdbd' : '#f2e3bf', fontStyle: '700' })); + const growthText = this.trackSideObject(this.add.text(left + width - 14, top + 42, `Lv ${unit.level} EXP ${unit.exp}/100`, { + fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif', + fontSize: '13px', + color: acted ? '#a8a8a8' : '#f4dfad', + fontStyle: '700' + })); + growthText.setOrigin(1, 0); if (this.phase !== 'command') { const backBg = this.trackSideObject(this.add.rectangle(left + width - 66, top + 10, 52, 28, 0x1b2834, 0.9)); @@ -10089,31 +10282,40 @@ export class BattleScene extends Phaser.Scene { this.drawGauge(left + 76, top + 84, width - 160, 10, unit.hp / unit.maxHp, 0x59d18c); const attackBonus = this.equipmentAttackBonus(unit); - this.renderSmallValueBox(left, top + 112, '공격', `${unit.attack + attackBonus}`); - this.renderSmallValueBox(left + width / 2 + 6, top + 112, '이동', `${unit.move}`); + 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 + (summaryBoxWidth + summaryGap) * 2, + top + 112, + summaryBoxWidth, + '방향', + `${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); }); - if (message) { - this.renderPanelMessage(message, left, top + 306, width, 66); - return; - } - - const positionText = `위치 ${unit.x + 1}, ${unit.y + 1} / ${terrainRule.label} ${terrainRating}%${acted ? ' / 행동완료' : ''}`; + const positionText = `위치 ${unit.x + 1}, ${unit.y + 1} / ${terrainRule.label} ${terrainRating}% / 이동 ${unit.move}${acted ? ' / 행동완료' : ''}`; this.trackSideObject(this.add.text(left, top + 306, positionText, { fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif', - fontSize: '15px', - color: acted ? '#bdbdbd' : '#9fb0bf' + fontSize: '14px', + color: acted ? '#bdbdbd' : '#9fb0bf', + wordWrap: { width, useAdvancedWrap: true } })); this.renderEquipmentSummary(unit, left, top + 330, width); + if (message) { + this.renderPanelMessage(message, left, top + 444, width, 66); + } + this.showFacingIndicator(unit); } private renderEquipmentSummary(unit: UnitData, x: number, y: number, width: number) { - this.trackSideObject(this.add.text(x, y, '장비', { + this.trackSideObject(this.add.text(x, y, '장비 / 숙련도', { fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif', fontSize: '16px', color: '#f2e3bf', @@ -10216,23 +10418,22 @@ export class BattleScene extends Phaser.Scene { }, 0); } - private renderSmallValueBox(x: number, y: number, label: string, value: string) { - const boxWidth = (this.layout.panelWidth - 60) / 2; - const bg = this.trackSideObject(this.add.rectangle(x, y, boxWidth, 38, 0x16212d, 0.92)); + private renderCompactValueBox(x: number, y: number, width: number, 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 + 12, y + 9, label, { + this.trackSideObject(this.add.text(x + 8, y + 6, label, { fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif', - fontSize: '14px', + fontSize: '12px', color: '#9fb0bf' })); - const valueText = this.trackSideObject(this.add.text(x + boxWidth - 12, y + 7, value, { + const valueText = this.trackSideObject(this.add.text(x + width - 8, y + 20, value, { fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif', - fontSize: '18px', + fontSize: '14px', color: '#f2e3bf', fontStyle: '700' })); - valueText.setOrigin(1, 0); + valueText.setOrigin(1, 0.5); } private renderStatRow(label: string, value: number, x: number, y: number, width: number) { @@ -10292,6 +10493,7 @@ export class BattleScene extends Phaser.Scene { private clearSidePanelContent() { this.sidePanelObjects.forEach((object) => object.destroy()); this.sidePanelObjects = []; + this.clearFacingIndicator(); } private trackSideObject(object: T) { @@ -10351,6 +10553,7 @@ export class BattleScene extends Phaser.Scene { battleStats: this.serializeBattleStats(), triggeredBattleEvents: Array.from(this.triggeredBattleEvents), selectedUsable: this.selectedUsable?.id ?? null, + audio: soundDirector.getDebugState(), itemStocks: this.serializeItemStocks(), battleBuffs: Array.from(this.battleBuffs.values()).map((buff) => ({ ...buff })), treasureEffectPreviews: this.debugTreasureEffectPreviews(), @@ -10371,6 +10574,7 @@ export class BattleScene extends Phaser.Scene { maxHp: unit.maxHp, x: unit.x, y: unit.y, + direction: this.unitViews.get(unit.id)?.direction ?? null, acted: this.actedUnitIds.has(unit.id), animating: this.unitViews.get(unit.id)?.sprite.anims?.isPlaying ?? false, animationKey: this.unitViews.get(unit.id)?.sprite.anims?.currentAnim?.key ?? null