Polish battle viewport and victory flow

This commit is contained in:
2026-06-22 15:21:34 +09:00
parent f30b99a78e
commit ce8050c5cc
4 changed files with 130 additions and 41 deletions

View File

@@ -4,6 +4,7 @@ import {
firstBattleBonds,
firstBattleMap,
firstBattleUnits,
firstBattleVictoryPages,
type BattleBond,
type UnitData,
type UnitStats
@@ -523,9 +524,10 @@ export class BattleScene extends Phaser.Scene {
const panelWidth = Phaser.Math.Clamp(Math.floor(width * 0.27), 320, 360);
const mapWidth = width - panelWidth - gap - margin * 2;
const mapHeight = height - margin * 2;
const visibleColumns = Math.min(12, firstBattleMap.width);
const visibleRows = Math.min(12, firstBattleMap.height);
const tileSize = Math.floor(Math.min((mapHeight - 36) / visibleRows, (mapWidth - 72) / visibleColumns));
const rowBasedTileSize = Math.floor((mapHeight - 36) / visibleRows);
const visibleColumns = Math.min(firstBattleMap.width, Math.max(12, Math.floor((mapWidth - 16) / rowBasedTileSize)));
const tileSize = Math.floor(Math.min((mapHeight - 36) / visibleRows, (mapWidth - 16) / visibleColumns));
const gridWidth = tileSize * visibleColumns;
const gridHeight = tileSize * visibleRows;
const gridSize = Math.min(gridWidth, gridHeight);
@@ -1662,8 +1664,8 @@ export class BattleScene extends Phaser.Scene {
this.hideBattleResult();
const depth = 120;
const panelWidth = 920;
const panelHeight = 590;
const panelWidth = 980;
const panelHeight = 620;
const left = Math.floor((this.scale.width - panelWidth) / 2);
const top = Math.floor((this.scale.height - panelHeight) / 2);
const allies = firstBattleUnits.filter((unit) => unit.faction === 'ally');
@@ -1706,14 +1708,14 @@ export class BattleScene extends Phaser.Scene {
subtitle.setDepth(depth + 2);
const metricTop = top + 112;
const metricWidth = 196;
const metricWidth = 210;
const metricGap = 18;
this.renderResultMetric('소요 턴', `${this.turnNumber}`, left + 44, metricTop, metricWidth, depth + 2);
this.renderResultMetric('생존 아군', `${aliveAllies} / ${allies.length}`, left + 44 + (metricWidth + metricGap), metricTop, metricWidth, depth + 2);
this.renderResultMetric('격파 적군', `${defeatedEnemies} / ${totalEnemies}`, left + 44 + (metricWidth + metricGap) * 2, metricTop, metricWidth, depth + 2);
this.renderResultMetric('전투 보상', outcome === 'victory' ? '획득' : '없음', left + 44 + (metricWidth + metricGap) * 3, metricTop, metricWidth, depth + 2);
this.renderResultMetric('전투 보상', outcome === 'victory' ? '군자금 300' : '없음', left + 44 + (metricWidth + metricGap) * 3, metricTop, metricWidth, depth + 2);
const unitTitle = this.trackResultObject(this.add.text(left + 44, top + 178, '장수 성장', {
const unitTitle = this.trackResultObject(this.add.text(left + 44, top + 184, '장수 성장', {
fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif',
fontSize: '21px',
color: '#f2e3bf',
@@ -1722,10 +1724,10 @@ export class BattleScene extends Phaser.Scene {
unitTitle.setDepth(depth + 2);
allies.forEach((unit, index) => {
this.renderResultUnitRow(unit, left + 44, top + 214 + index * 66, panelWidth - 88, depth + 2);
this.renderResultUnitRow(unit, left + 44, top + 220 + index * 72, panelWidth - 88, depth + 2);
});
const bondTitleTop = top + 424;
const bondTitleTop = top + 452;
const bondTitle = this.trackResultObject(this.add.text(left + 44, bondTitleTop, '공명 변화', {
fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif',
fontSize: '21px',
@@ -1738,24 +1740,17 @@ export class BattleScene extends Phaser.Scene {
this.renderResultBondRow(bond, left + 44 + index * 278, bondTitleTop + 38, 258, depth + 2);
});
const rewardLines =
outcome === 'victory'
? ['군자금 300', '마을 민심 +1', '의용군 명망 +1']
: ['보상 없음', '유비를 보호하며 다시 진군하세요.'];
const reward = this.trackResultObject(this.add.text(left + panelWidth - 44, top + 520, rewardLines.join(' '), {
fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif',
fontSize: '15px',
color: outcome === 'victory' ? '#d8b15f' : '#aeb7c2',
fontStyle: '700'
}));
reward.setOrigin(1, 0);
reward.setDepth(depth + 2);
this.addResultButton('다시 하기', left + panelWidth - 286, top + 552, 124, () => {
if (outcome === 'victory') {
this.addResultButton('이야기 계속', left + panelWidth - 422, top + 572, 132, () => {
soundDirector.playSelect();
this.scene.start('StoryScene', { pages: firstBattleVictoryPages, nextScene: 'TitleScene' });
}, depth + 3);
}
this.addResultButton('다시 하기', left + panelWidth - 276, top + 572, 124, () => {
soundDirector.playSelect();
this.scene.restart();
}, depth + 3);
this.addResultButton('타이틀로', left + panelWidth - 150, top + 552, 124, () => {
this.addResultButton('타이틀로', left + panelWidth - 140, top + 572, 124, () => {
soundDirector.playSelect();
this.scene.start('TitleScene');
}, depth + 3);
@@ -1791,7 +1786,7 @@ export class BattleScene extends Phaser.Scene {
private renderResultUnitRow(unit: UnitData, x: number, y: number, width: number, depth: number) {
const alive = unit.hp > 0;
const bg = this.trackResultObject(this.add.rectangle(x, y, width, 56, 0x101820, alive ? 0.94 : 0.66));
const bg = this.trackResultObject(this.add.rectangle(x, y, width, 64, 0x101820, alive ? 0.94 : 0.66));
bg.setOrigin(0);
bg.setDepth(depth);
bg.setStrokeStyle(1, alive ? palette.blue : 0x646464, alive ? 0.62 : 0.5);
@@ -1819,7 +1814,7 @@ export class BattleScene extends Phaser.Scene {
}));
hpText.setDepth(depth + 1);
const expText = this.trackResultObject(this.add.text(x + 392, y + 9, `경험 ${unit.exp}/100`, {
const expText = this.trackResultObject(this.add.text(x + 392, y + 9, this.characterResultText(unit), {
fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif',
fontSize: '14px',
color: '#d8b15f',
@@ -1827,14 +1822,7 @@ export class BattleScene extends Phaser.Scene {
}));
expText.setDepth(depth + 1);
const equipmentText = equipmentSlots
.map((slot) => {
const state = unit.equipment[slot];
const item = getItem(state.itemId);
return `${equipmentSlotLabels[slot]} ${item.name} Lv${state.level} ${state.exp}/${equipmentExpToNext(state.level)}`;
})
.join(' ');
const equipment = this.trackResultObject(this.add.text(x + 14, y + 32, equipmentText, {
const equipment = this.trackResultObject(this.add.text(x + 14, y + 32, this.equipmentResultText(unit), {
fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif',
fontSize: '12px',
color: alive ? '#c8d2dd' : '#7e858d',
@@ -1843,6 +1831,41 @@ export class BattleScene extends Phaser.Scene {
equipment.setDepth(depth + 1);
}
private characterResultText(unit: UnitData) {
const initial = initialFirstBattleUnits.find((candidate) => candidate.id === unit.id);
const gained = initial ? Math.max(0, this.characterProgressScore(unit.level, unit.exp) - this.characterProgressScore(initial.level, initial.exp)) : 0;
const levelDelta = initial ? unit.level - initial.level : 0;
const levelText = levelDelta > 0 ? ` / Lv +${levelDelta}` : '';
return `경험 +${gained} (${unit.exp}/100)${levelText}`;
}
private equipmentResultText(unit: UnitData) {
const initial = initialFirstBattleUnits.find((candidate) => candidate.id === unit.id);
return equipmentSlots
.map((slot) => {
const state = unit.equipment[slot];
const item = getItem(state.itemId);
const initialState = initial?.equipment[slot];
const gained = initialState ? Math.max(0, this.equipmentProgressScore(state.level, state.exp) - this.equipmentProgressScore(initialState.level, initialState.exp)) : 0;
const levelDelta = initialState ? state.level - initialState.level : 0;
const levelText = levelDelta > 0 ? `(+${levelDelta})` : '';
return `${equipmentSlotLabels[slot]} ${item.name} Lv${state.level}${levelText} +${gained}`;
})
.join(' ');
}
private characterProgressScore(level: number, exp: number) {
return (level - 1) * 100 + exp;
}
private equipmentProgressScore(level: number, exp: number) {
let score = exp;
for (let currentLevel = 1; currentLevel < level; currentLevel += 1) {
score += equipmentExpToNext(currentLevel);
}
return score;
}
private renderResultBondRow(bond: BondState, x: number, y: number, width: number, depth: number) {
const bg = this.trackResultObject(this.add.rectangle(x, y, width, 58, 0x101820, 0.88));
bg.setOrigin(0);
@@ -1858,7 +1881,7 @@ export class BattleScene extends Phaser.Scene {
}));
title.setDepth(depth + 1);
const value = this.trackResultObject(this.add.text(x + 12, y + 31, `공명 ${bond.level} 경험 ${bond.exp}/100 전투 +${bond.battleExp}`, {
const value = this.trackResultObject(this.add.text(x + 12, y + 31, this.bondResultText(bond), {
fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif',
fontSize: '13px',
color: bond.battleExp > 0 ? '#d8b15f' : '#9fb0bf'
@@ -1866,6 +1889,14 @@ export class BattleScene extends Phaser.Scene {
value.setDepth(depth + 1);
}
private bondResultText(bond: BondState) {
const initial = initialFirstBattleBonds.find((candidate) => candidate.id === bond.id);
const gained = initial ? Math.max(0, this.characterProgressScore(bond.level, bond.exp) - this.characterProgressScore(initial.level, initial.exp)) : bond.battleExp;
const levelDelta = initial ? bond.level - initial.level : 0;
const levelText = levelDelta > 0 ? ` / Lv +${levelDelta}` : '';
return `공명 ${bond.level} 경험 +${gained}${levelText} 전투 +${bond.battleExp}`;
}
private addResultButton(label: string, x: number, y: number, width: number, action: () => void, depth: number) {
const bg = this.trackResultObject(this.add.rectangle(x, y, width, 34, 0x1a2630, 0.96));
bg.setDepth(depth);