Files
heros_web/src/game/scenes/EndingScene.ts

183 lines
6.2 KiB
TypeScript

import Phaser from 'phaser';
import { soundDirector } from '../audio/SoundDirector';
import { getBattleScenario } from '../data/battles';
import { storyBackgroundAssets } from '../data/storyAssets';
import { getCampaignState } from '../state/campaignState';
import { palette } from '../ui/palette';
export class EndingScene extends Phaser.Scene {
private readonly backgroundKey = 'story-wuzhang-jiangwei-inheritance';
private ready = false;
constructor() {
super('EndingScene');
}
create() {
this.ready = false;
this.createFallbackTexture();
this.ensureBackgroundLoaded(() => {
this.ready = true;
this.drawEnding();
});
}
getDebugState() {
const campaign = getCampaignState();
return {
scene: this.scene.key,
ready: this.ready,
campaignStep: campaign.step,
latestBattleId: campaign.latestBattleId ?? null,
completedBattles: Object.keys(campaign.battleHistory).length,
rosterCount: campaign.roster.length,
endingTitle: '북벌의 끝과 남은 뜻',
backgroundKey: this.backgroundKey,
backgroundReady: this.textures.exists(this.backgroundKey)
};
}
private ensureBackgroundLoaded(onReady: () => void) {
if (this.textures.exists(this.backgroundKey)) {
onReady();
return;
}
const url = storyBackgroundAssets[this.backgroundKey];
if (!url) {
onReady();
return;
}
this.load.once('complete', onReady);
this.load.once('loaderror', () => {
console.warn(`Failed to load ending background ${this.backgroundKey}`);
});
this.load.image(this.backgroundKey, url);
this.load.start();
}
private createFallbackTexture() {
if (this.textures.exists('ending-fallback')) {
return;
}
const graphics = this.make.graphics({ x: 0, y: 0 });
graphics.fillStyle(0x080b10, 1);
graphics.fillRect(0, 0, 1280, 720);
graphics.fillStyle(0x152331, 1);
graphics.fillRect(0, 0, 1280, 720);
graphics.lineStyle(5, palette.gold, 0.42);
graphics.beginPath();
graphics.moveTo(120, 570);
graphics.lineTo(1160, 170);
graphics.strokePath();
graphics.generateTexture('ending-fallback', 1280, 720);
graphics.destroy();
}
private drawEnding() {
const { width, height } = this.scale;
const campaign = getCampaignState();
const latestBattleTitle = campaign.latestBattleId ? getBattleScenario(campaign.latestBattleId).title : '기록 없음';
const textureKey = this.textures.exists(this.backgroundKey) ? this.backgroundKey : 'ending-fallback';
const background = this.add.image(width / 2, height / 2, textureKey);
const source = this.textures.get(textureKey).getSourceImage();
const coverScale = Math.max(width / source.width, height / source.height);
background.setScale(coverScale * 1.08);
background.setPosition(width / 2 - 14, height / 2 + 2);
this.tweens.add({
targets: background,
x: width / 2 + 14,
y: height / 2 - 6,
scale: coverScale * 1.12,
duration: 18000,
ease: 'Sine.easeInOut',
yoyo: true,
repeat: -1
});
this.add.rectangle(0, 0, width, height, 0x04070b, 0.34).setOrigin(0);
this.add.rectangle(0, 0, width, height, 0x05080c, 0.16).setOrigin(0);
this.add.rectangle(0, height - 220, width, 220, 0x05070a, 0.64).setOrigin(0);
const completedBattles = Object.keys(campaign.battleHistory).length;
const titleY = Math.max(96, height * 0.18);
this.add.text(width / 2, titleY, '북벌의 끝과 남은 뜻', {
fontFamily: '"Malgun Gothic", "Noto Sans KR", serif',
fontSize: '48px',
color: '#f6e6bd',
fontStyle: '700',
align: 'center',
shadow: { offsetX: 0, offsetY: 3, color: '#020408', blur: 8, fill: true }
}).setOrigin(0.5);
this.add.text(width / 2, titleY + 62, '촉한 군영 후일담 완료', {
fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif',
fontSize: '22px',
color: '#d8b15f',
fontStyle: '700',
align: 'center',
shadow: { offsetX: 0, offsetY: 2, color: '#020408', blur: 6, fill: true }
}).setOrigin(0.5);
const summary = [
`완료 전투 ${completedBattles}`,
`최종 전장 ${latestBattleTitle}`,
`합류 무장 ${campaign.roster.length}`,
`군영 공명 ${campaign.bonds.length}`
];
const startX = width / 2 - 390;
summary.forEach((line, index) => {
this.add.text(startX + index * 260, height - 178, line, {
fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif',
fontSize: '18px',
color: '#f2e3bf',
fontStyle: '700',
shadow: { offsetX: 0, offsetY: 2, color: '#020408', blur: 5, fill: true }
}).setOrigin(0.5);
});
this.add.text(width / 2, height - 122, '도원에서 시작된 선택은 촉한의 깃발과 북벌의 장부를 지나, 다음 세대가 이어 받을 뜻으로 남았습니다.', {
fontFamily: '"Malgun Gothic", "Noto Sans KR", serif',
fontSize: '22px',
color: '#e8dfca',
align: 'center',
wordWrap: { width: Math.min(920, width - 160), useAdvancedWrap: true },
lineSpacing: 8,
shadow: { offsetX: 0, offsetY: 2, color: '#020408', blur: 6, fill: true }
}).setOrigin(0.5);
this.addButton(width / 2, height - 52, '타이틀로', () => {
soundDirector.playSelect();
this.scene.start('TitleScene');
});
this.input.keyboard?.on('keydown-ENTER', () => this.scene.start('TitleScene'));
this.input.keyboard?.on('keydown-SPACE', () => this.scene.start('TitleScene'));
soundDirector.playMusic('oath-theme');
}
private addButton(x: number, y: number, label: string, action: () => void) {
const bg = this.add.rectangle(x, y, 160, 42, 0x1a2630, 0.96);
bg.setStrokeStyle(1, palette.gold, 0.82);
bg.setInteractive({ useHandCursor: true });
bg.on('pointerover', () => bg.setFillStyle(0x283947, 0.98));
bg.on('pointerout', () => bg.setFillStyle(0x1a2630, 0.96));
bg.on('pointerdown', action);
const text = this.add.text(x, y, label, {
fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif',
fontSize: '17px',
color: '#f2e3bf',
fontStyle: '700'
});
text.setOrigin(0.5);
text.setInteractive({ useHandCursor: true });
text.on('pointerdown', action);
}
}