197 lines
6.5 KiB
TypeScript
197 lines
6.5 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.renderEnding();
|
|
this.ensureBackgroundLoaded(() => this.renderEnding());
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
let finished = false;
|
|
const finish = () => {
|
|
if (finished) {
|
|
return;
|
|
}
|
|
finished = true;
|
|
onReady();
|
|
};
|
|
this.load.once('complete', finish);
|
|
this.load.once('loaderror', () => {
|
|
console.warn(`Failed to load ending background ${this.backgroundKey}`);
|
|
finish();
|
|
});
|
|
this.load.image(this.backgroundKey, url);
|
|
this.load.start();
|
|
}
|
|
|
|
private renderEnding() {
|
|
this.tweens.killAll();
|
|
this.children.removeAll(true);
|
|
this.drawEnding();
|
|
this.ready = true;
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|