From cb40be99400f7d4c2c40d5d74885d07867881989 Mon Sep 17 00:00:00 2001 From: Wickedness Date: Thu, 25 Jun 2026 03:47:41 +0900 Subject: [PATCH] Add campaign ending epilogue --- scripts/verify-flow.mjs | 74 ++++++++++--- src/game/data/campaignFlow.ts | 14 +-- src/game/data/scenario.ts | 35 +++++++ src/game/scenes/CampScene.ts | 13 ++- src/game/scenes/EndingScene.ts | 178 ++++++++++++++++++++++++++++++++ src/game/scenes/TitleScene.ts | 5 + src/game/state/campaignState.ts | 1 + src/main.ts | 3 +- 8 files changed, 298 insertions(+), 25 deletions(-) create mode 100644 src/game/scenes/EndingScene.ts diff --git a/scripts/verify-flow.mjs b/scripts/verify-flow.mjs index 84aad26..4747147 100644 --- a/scripts/verify-flow.mjs +++ b/scripts/verify-flow.mjs @@ -9430,22 +9430,64 @@ try { postSixtyFifthProgressState.campaignProgress?.totalKnown !== 65 || postSixtyFifthProgressState.campaignProgress?.activeChapter?.id !== 'northern-campaign' || postSixtyFifthProgressState.campaignProgress?.latestBattleTitle !== '위수 북안 압박전' || - postSixtyFifthProgressState.campaignProgress?.nextBattleTitle !== '준비 중' + postSixtyFifthProgressState.campaignProgress?.nextBattleTitle !== '북벌의 끝과 남은 뜻' ) { - throw new Error(`Expected post-sixty-fifth progress tab to complete Weishui north bank pressure and leave the next northern step pending: ${JSON.stringify(postSixtyFifthProgressState?.campaignProgress)}`); + throw new Error(`Expected post-sixty-fifth progress tab to complete Weishui north bank pressure and expose the final epilogue: ${JSON.stringify(postSixtyFifthProgressState?.campaignProgress)}`); } await page.screenshot({ path: 'dist/verification-post-sixty-fifth-progress.png', fullPage: true }); + await page.mouse.click(1160, 38); + await page.waitForFunction(() => { + const activeScenes = window.__HEROS_DEBUG__?.activeScenes() ?? []; + return activeScenes.includes('StoryScene'); + }); + await page.waitForFunction(() => { + const story = window.__HEROS_DEBUG__?.scene('StoryScene')?.getDebugState?.(); + return story?.currentPageId === 'sixty-fifth-victory-delay-line-pressed' && story?.backgroundReady === true; + }); + await page.screenshot({ path: 'dist/verification-ending-epilogue-story.png', fullPage: true }); + await page.evaluate(async () => { + const delay = (ms) => new Promise((resolve) => window.setTimeout(resolve, ms)); + for (let i = 0; i < 16; i += 1) { + const activeScenes = window.__HEROS_DEBUG__?.activeScenes() ?? []; + if (activeScenes.includes('EndingScene')) { + break; + } + window.__HEROS_DEBUG__?.scene('StoryScene')?.advance?.(); + await delay(520); + } + }); + await page.waitForFunction(() => { + const activeScenes = window.__HEROS_DEBUG__?.activeScenes() ?? []; + const ending = window.__HEROS_DEBUG__?.scene('EndingScene')?.getDebugState?.(); + return activeScenes.includes('EndingScene') && ending?.ready === true && ending?.campaignStep === 'ending-complete'; + }); + const endingState = await page.evaluate(() => ({ + activeScenes: window.__HEROS_DEBUG__?.activeScenes() ?? [], + ending: window.__HEROS_DEBUG__?.scene('EndingScene')?.getDebugState?.() ?? null + })); + if ( + !endingState.activeScenes.includes('EndingScene') || + endingState.ending?.campaignStep !== 'ending-complete' || + endingState.ending?.latestBattleId !== 'sixty-fifth-battle-weishui-northbank' || + endingState.ending?.completedBattles !== 65 || + endingState.ending?.endingTitle !== '북벌의 끝과 남은 뜻' + ) { + throw new Error(`Expected final epilogue to mark ending-complete and show EndingScene: ${JSON.stringify(endingState)}`); + } + await page.screenshot({ path: 'dist/verification-ending-screen.png', fullPage: true }); + await page.evaluate(() => { const game = window.__HEROS_GAME__; game?.scene.stop('CampScene'); game?.scene.stop('BattleScene'); game?.scene.stop('StoryScene'); + game?.scene.stop('EndingScene'); game?.scene.start('TitleScene'); }); await page.waitForFunction(() => { const activeScenes = window.__HEROS_DEBUG__?.activeScenes() ?? []; - return activeScenes.includes('TitleScene') && !activeScenes.includes('CampScene'); + return activeScenes.includes('TitleScene') && !activeScenes.includes('CampScene') && !activeScenes.includes('EndingScene'); }); await page.waitForTimeout(800); for (const [x, y] of [ @@ -9455,21 +9497,21 @@ try { ]) { await page.mouse.click(x, y); await page.waitForTimeout(300); - const continuedToCamp = await page.evaluate(() => { + const continuedToEnding = await page.evaluate(() => { const activeScenes = window.__HEROS_DEBUG__?.activeScenes() ?? []; - return activeScenes.includes('CampScene'); + return activeScenes.includes('EndingScene'); }); - if (continuedToCamp) { + if (continuedToEnding) { break; } } for (let attempt = 0; attempt < 24; attempt += 1) { - const continuedToNorthbankCamp = await page.evaluate(() => { + const continuedToEnding = await page.evaluate(() => { const activeScenes = window.__HEROS_DEBUG__?.activeScenes() ?? []; - const camp = window.__HEROS_DEBUG__?.camp?.() ?? null; - return activeScenes.includes('CampScene') && camp?.campaign?.step === 'sixty-fifth-camp'; + const ending = window.__HEROS_DEBUG__?.scene('EndingScene')?.getDebugState?.(); + return activeScenes.includes('EndingScene') && ending?.campaignStep === 'ending-complete'; }); - if (continuedToNorthbankCamp) { + if (continuedToEnding) { break; } await page.keyboard.press('Space'); @@ -9477,18 +9519,18 @@ try { } await page.waitForFunction(() => { const activeScenes = window.__HEROS_DEBUG__?.activeScenes() ?? []; - const camp = window.__HEROS_DEBUG__?.camp?.() ?? null; - return activeScenes.includes('CampScene') && camp?.campaign?.step === 'sixty-fifth-camp'; + const ending = window.__HEROS_DEBUG__?.scene('EndingScene')?.getDebugState?.(); + return activeScenes.includes('EndingScene') && ending?.campaignStep === 'ending-complete'; }); const titleContinueState = await page.evaluate(() => ({ activeScenes: window.__HEROS_DEBUG__?.activeScenes() ?? [], - camp: window.__HEROS_DEBUG__?.camp?.() ?? null + ending: window.__HEROS_DEBUG__?.scene('EndingScene')?.getDebugState?.() ?? null })); - if (!titleContinueState.activeScenes.includes('CampScene') || titleContinueState.camp?.campaign?.step !== 'sixty-fifth-camp') { - throw new Error(`Expected title continue to reopen the Weishui north bank aftermath camp: ${JSON.stringify(titleContinueState)}`); + if (!titleContinueState.activeScenes.includes('EndingScene') || titleContinueState.ending?.campaignStep !== 'ending-complete') { + throw new Error(`Expected title continue to reopen the ending screen: ${JSON.stringify(titleContinueState)}`); } - console.log(`Verified title-to-Weishui-northbank flow, recruited officer sortie selection, Ma Liang, Yi Ji, Gong Zhi, Huang Zhong, Wei Yan, Pang Tong, Fa Zheng, Wu Yi, Yan Yan, Li Yan, Huang Quan, Ma Chao, Ma Dai, Wang Ping joins, Luofeng, Luo main gate, Mianzhu, Chengdu surrender, Jiameng, Yangping, Dingjun, Hanzhong decisive battle, King of Hanzhong council, Shu-Han foundation state, Jing Province defense vanguard, Fan Castle vanguard, Han River flood, Fan siege, Jing rear crisis, Gongan collapse, Maicheng isolation, Yiling vanguard, Yiling fire attack, Baidi entrustment, Nanzhong stabilization, Meng Huo pacification, second capture, third capture, fourth capture, fifth capture, sixth capture, final capture, northern campaign preparation, Qishan road first northern expedition, Tianshui advance, Jieting crisis, Jiang Wei joining, Qishan retreat, Chencang siege, Wudu/Yinping commanderies, Hanzhong rainy-pass defense, Qishan renewed offensive, Lucheng pursuit, Weishui camps, Weishui north bank pressure, and pending next northern step state at ${targetUrl}`); + console.log(`Verified title-to-ending flow, recruited officer sortie selection, Ma Liang, Yi Ji, Gong Zhi, Huang Zhong, Wei Yan, Pang Tong, Fa Zheng, Wu Yi, Yan Yan, Li Yan, Huang Quan, Ma Chao, Ma Dai, Wang Ping joins, Luofeng, Luo main gate, Mianzhu, Chengdu surrender, Jiameng, Yangping, Dingjun, Hanzhong decisive battle, King of Hanzhong council, Shu-Han foundation state, Jing Province defense vanguard, Fan Castle vanguard, Han River flood, Fan siege, Jing rear crisis, Gongan collapse, Maicheng isolation, Yiling vanguard, Yiling fire attack, Baidi entrustment, Nanzhong stabilization, Meng Huo pacification, second capture, third capture, fourth capture, fifth capture, sixth capture, final capture, northern campaign preparation, Qishan road first northern expedition, Tianshui advance, Jieting crisis, Jiang Wei joining, Qishan retreat, Chencang siege, Wudu/Yinping commanderies, Hanzhong rainy-pass defense, Qishan renewed offensive, Lucheng pursuit, Weishui camps, Weishui north bank pressure, final epilogue, ending-complete save state, and title continue to ending screen at ${targetUrl}`); } finally { await browser?.close(); if (serverProcess && !serverProcess.killed) { diff --git a/src/game/data/campaignFlow.ts b/src/game/data/campaignFlow.ts index a18c543..002e8ce 100644 --- a/src/game/data/campaignFlow.ts +++ b/src/game/data/campaignFlow.ts @@ -75,6 +75,7 @@ import { eighteenthBattleVictoryPages, eleventhBattleIntroPages, eleventhBattleVictoryPages, + endingEpiloguePages, fiftiethBattleIntroPages, fiftiethBattleVictoryPages, fiftyEighthBattleIntroPages, @@ -953,13 +954,14 @@ const sortieFlows: Record = { }, [sixtyFifthBattleScenario.id]: { afterBattleId: sixtyFifthBattleScenario.id, - eyebrow: '다음 장 준비', - title: '위수 북안 이후', + eyebrow: '최종 후일담', + title: '북벌의 끝과 남은 뜻', description: - '위수 북안 지연선은 눌렸지만, 북방의 길은 아직 끝나지 않았습니다. 다음 계산에서는 오래 붙들 진영, 물릴 진영, 더 깊이 들어갈 길을 다시 나누어야 합니다.', - rewardHint: '다음 목표: 북안 전선 재정비 / 다음 북방 공세 검토', - pages: sixtyFifthBattleVictoryPages, - unavailableNotice: '위수 북안 이후의 다음 북방 전투는 이어지는 작업에서 확장합니다. 현재는 북안 전진 진영 압박, 장합 재추격 저지, 곽회 봉화 차단과 강북 보급 정비를 확인할 수 있습니다.' + '위수 북안 지연선을 누른 뒤, 촉한군은 더 깊은 전장보다 살아 돌아갈 길과 이어질 뜻을 정리합니다. 마지막 후일담은 새 전투가 아니라 제갈량과 강유, 남은 장수들이 북벌의 의미를 정리하는 장면입니다.', + rewardHint: '최종 보상: 북벌 후일담 / 엔딩 개방', + campaignStep: 'ending-complete', + pages: [...sixtyFifthBattleVictoryPages, ...endingEpiloguePages], + unavailableNotice: '최종 후일담과 엔딩을 이미 확인했습니다. 타이틀에서 이어하기를 선택하면 엔딩 화면으로 돌아갑니다.' }, 'northern-campaign-prep-camp-legacy': { afterBattleId: fiftyFourthBattleScenario.id, diff --git a/src/game/data/scenario.ts b/src/game/data/scenario.ts index 6fcee8e..620cc79 100644 --- a/src/game/data/scenario.ts +++ b/src/game/data/scenario.ts @@ -4119,6 +4119,41 @@ export const sixtyFifthBattleVictoryPages: StoryPage[] = [ } ]; +export const endingEpiloguePages: StoryPage[] = [ + { + id: 'ending-wuzhang-council', + bgm: 'story-dark', + chapter: '오장원의 밤', + background: 'story-weishui-northbank', + speaker: '제갈량', + text: '위수의 바람은 오래 불었고, 북방의 길은 한 사람의 뜻만으로 끝낼 수 없을 만큼 멀었습니다. 승상은 남은 장수들을 불러 더 밀고 들어갈 길보다 돌아갈 병사와 지킬 나라를 먼저 적었습니다.' + }, + { + id: 'ending-jiang-wei-inherits-map', + bgm: 'battle-prep', + chapter: '이어 받은 지도', + background: 'story-northern', + speaker: '강유', + text: '강유는 낡은 지도를 접어 품에 넣었습니다. 오늘의 북벌은 멈추지만, 촉한이 백성을 버리지 않았다는 증거와 다시 길을 볼 눈은 다음 세대의 군문에 남았습니다.' + }, + { + id: 'ending-roster-returns', + bgm: 'militia-theme', + chapter: '돌아오는 깃발', + background: 'story-hanzhong-rain', + speaker: '왕평', + text: '조운이 세운 회수선, 왕평의 표식, 황권과 이엄의 장부, 마량의 고시문은 병사들이 돌아오는 길을 잃지 않게 했습니다. 이긴 전장보다 잃지 않은 사람들이 촉한의 마지막 장부에 남았습니다.' + }, + { + id: 'ending-shu-han-legacy', + bgm: 'oath-theme', + chapter: '촉한의 뜻', + background: 'story-yiling-baidi', + speaker: '나레이션', + text: '도원에서 시작된 맹세는 전장을 지나 나라가 되었고, 나라는 다시 사람들의 기억이 되었습니다. 촉한의 북벌은 끝을 보지 못했으나, 의와 선택으로 버틴 시간은 다음 이야기가 설 자리를 남겼습니다.' + } +]; + export const firstBattleMap: BattleMap = { width: 20, height: 18, diff --git a/src/game/scenes/CampScene.ts b/src/game/scenes/CampScene.ts index 13c4870..ef0a282 100644 --- a/src/game/scenes/CampScene.ts +++ b/src/game/scenes/CampScene.ts @@ -10954,6 +10954,11 @@ export class CampScene extends Phaser.Scene { }); this.addCommandButton('다음 이야기', 1160, 38, 142, () => { soundDirector.playSelect(); + const flow = this.currentSortieFlow(); + if (this.isFinalEpilogueFlow(flow)) { + this.startVictoryStory(); + return; + } this.showSortiePrep(); }); } @@ -11795,7 +11800,7 @@ export class CampScene extends Phaser.Scene { private startVictoryStory() { const flow = this.currentSortieFlow(); - if (!this.ensureSortieSelectionSaved()) { + if (!this.isFinalEpilogueFlow(flow) && !this.ensureSortieSelectionSaved()) { const availableIds = new Set(this.sortieAllies().map((unit) => unit.id)); const requiredNames = [...this.requiredSortieUnitIdsFor()] .filter((unitId) => availableIds.has(unitId)) @@ -11819,7 +11824,7 @@ export class CampScene extends Phaser.Scene { this.campaign = getCampaignState(); this.scene.start('StoryScene', { pages: flow.pages, - nextScene: 'CampScene' + nextScene: flow.campaignStep === 'ending-complete' ? 'EndingScene' : 'CampScene' }); return; } @@ -11850,6 +11855,10 @@ export class CampScene extends Phaser.Scene { }); } + private isFinalEpilogueFlow(flow = this.currentSortieFlow()) { + return flow.campaignStep === 'ending-complete' && !flow.nextBattleId; + } + private hideSortiePrep() { this.sortieObjects.forEach((object) => object.destroy()); this.sortieObjects = []; diff --git a/src/game/scenes/EndingScene.ts b/src/game/scenes/EndingScene.ts new file mode 100644 index 0000000..2fc49e4 --- /dev/null +++ b/src/game/scenes/EndingScene.ts @@ -0,0 +1,178 @@ +import Phaser from 'phaser'; +import { soundDirector } from '../audio/SoundDirector'; +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-weishui-northbank'; + 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: '북벌의 끝과 남은 뜻' + }; + } + + 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 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}개`, + `최종 전장 ${campaign.latestBattleId ? '위수 북안 압박전' : '기록 없음'}`, + `합류 무장 ${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); + } +} diff --git a/src/game/scenes/TitleScene.ts b/src/game/scenes/TitleScene.ts index 1b61ee6..4462180 100644 --- a/src/game/scenes/TitleScene.ts +++ b/src/game/scenes/TitleScene.ts @@ -364,6 +364,11 @@ export class TitleScene extends Phaser.Scene { soundDirector.playSelect(); const campaign = loadCampaignState(); + if (campaign.step === 'ending-complete') { + this.scene.start('EndingScene'); + return; + } + if ( campaign.step === 'first-camp' || campaign.step === 'second-camp' || diff --git a/src/game/state/campaignState.ts b/src/game/state/campaignState.ts index 51164a2..7edb108 100644 --- a/src/game/state/campaignState.ts +++ b/src/game/state/campaignState.ts @@ -172,6 +172,7 @@ export type CampaignStep = | 'sixty-fourth-camp' | 'sixty-fifth-battle' | 'sixty-fifth-camp' + | 'ending-complete' | 'hanzhong-king-camp' | 'shu-han-foundation-camp' | 'baidi-entrustment-camp' diff --git a/src/main.ts b/src/main.ts index edc42fc..6136c4d 100644 --- a/src/main.ts +++ b/src/main.ts @@ -4,6 +4,7 @@ import { soundDirector } from './game/audio/SoundDirector'; import { BattleScene } from './game/scenes/BattleScene'; import { BootScene } from './game/scenes/BootScene'; import { CampScene } from './game/scenes/CampScene'; +import { EndingScene } from './game/scenes/EndingScene'; import { StoryScene } from './game/scenes/StoryScene'; import { TitleScene } from './game/scenes/TitleScene'; import './styles/global.css'; @@ -47,7 +48,7 @@ const config: Phaser.Types.Core.GameConfig = { mode: Phaser.Scale.RESIZE, autoCenter: Phaser.Scale.CENTER_BOTH }, - scene: [BootScene, TitleScene, StoryScene, BattleScene, CampScene] + scene: [BootScene, TitleScene, StoryScene, BattleScene, CampScene, EndingScene] }; soundDirector.registerMusicTracks(musicTracks);