Add title save slot selection
This commit is contained in:
@@ -2,7 +2,13 @@ import Phaser from 'phaser';
|
||||
import { soundDirector } from '../audio/SoundDirector';
|
||||
import { getBattleScenario } from '../data/battles';
|
||||
import { battleIdForCampaignStep, isCampCampaignStep } from '../state/campaignRouting';
|
||||
import { hasCampaignSave, loadCampaignState, startNewCampaign } from '../state/campaignState';
|
||||
import {
|
||||
hasCampaignSave,
|
||||
listCampaignSaveSlots,
|
||||
loadCampaignState,
|
||||
startNewCampaign,
|
||||
type CampaignSaveSlotSummary
|
||||
} from '../state/campaignState';
|
||||
import { palette } from '../ui/palette';
|
||||
import { startLazyScene } from './lazyScenes';
|
||||
|
||||
@@ -10,6 +16,7 @@ export class TitleScene extends Phaser.Scene {
|
||||
private focusedButton?: Phaser.GameObjects.Text;
|
||||
private settingsPanel?: Phaser.GameObjects.Container;
|
||||
private newGameConfirmPanel?: Phaser.GameObjects.Container;
|
||||
private saveSlotPanel?: Phaser.GameObjects.Container;
|
||||
private navigating = false;
|
||||
|
||||
constructor() {
|
||||
@@ -20,6 +27,7 @@ export class TitleScene extends Phaser.Scene {
|
||||
this.focusedButton = undefined;
|
||||
this.settingsPanel = undefined;
|
||||
this.newGameConfirmPanel = undefined;
|
||||
this.saveSlotPanel = undefined;
|
||||
this.navigating = false;
|
||||
|
||||
const debugBattleId = this.debugBattleId();
|
||||
@@ -213,7 +221,7 @@ export class TitleScene extends Phaser.Scene {
|
||||
this.add.rectangle(menuX, menuY + plateHeight / 2, 186, 2, palette.gold, 0.36);
|
||||
|
||||
this.createMenuButton(menuX, menuY - 70, '새 게임', true, () => this.requestStartGame());
|
||||
this.createMenuButton(menuX, menuY, isEndingComplete ? '엔딩 보기' : '이어하기', canContinue, () => this.continueGame());
|
||||
this.createMenuButton(menuX, menuY, isEndingComplete ? '엔딩 보기' : '이어하기', canContinue, () => this.requestContinueGame());
|
||||
this.createMenuButton(menuX, menuY + 70, '설정', true, () => this.openSettingsPanel(menuX, menuY));
|
||||
|
||||
if (campaign) {
|
||||
@@ -369,6 +377,7 @@ export class TitleScene extends Phaser.Scene {
|
||||
|
||||
soundDirector.playSelect();
|
||||
this.closeNewGameConfirmPanel();
|
||||
this.closeSaveSlotPanel();
|
||||
|
||||
const panel = this.add.container(x, y + 238);
|
||||
const backdrop = this.add.rectangle(0, 0, 286, 188, 0x0e151d, 0.9);
|
||||
@@ -427,6 +436,124 @@ export class TitleScene extends Phaser.Scene {
|
||||
this.settingsPanel = undefined;
|
||||
}
|
||||
|
||||
private requestContinueGame() {
|
||||
if (!hasCampaignSave()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const slots = listCampaignSaveSlots().filter((slot) => slot.exists);
|
||||
if (slots.length <= 1) {
|
||||
this.continueGame(slots[0]?.slot);
|
||||
return;
|
||||
}
|
||||
|
||||
const { width, height } = this.scale;
|
||||
const compact = width < 720;
|
||||
const menuX = compact ? width / 2 : width - 318;
|
||||
const menuY = height * (compact ? 0.36 : 0.43);
|
||||
this.openSaveSlotPanel(menuX, menuY);
|
||||
}
|
||||
|
||||
private openSaveSlotPanel(x: number, y: number) {
|
||||
if (this.saveSlotPanel) {
|
||||
this.closeSaveSlotPanel();
|
||||
return;
|
||||
}
|
||||
|
||||
soundDirector.playSelect();
|
||||
this.closeSettingsPanel();
|
||||
this.closeNewGameConfirmPanel();
|
||||
|
||||
const panel = this.add.container(x, y + 252);
|
||||
const backdrop = this.add.rectangle(0, 0, 318, 256, 0x0e151d, 0.95);
|
||||
backdrop.setStrokeStyle(1, palette.gold, 0.58);
|
||||
|
||||
const title = this.add.text(0, -104, '이어갈 기록 선택', {
|
||||
fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif',
|
||||
fontSize: '22px',
|
||||
color: '#f1e3c2',
|
||||
fontStyle: '700',
|
||||
fixedWidth: 266,
|
||||
align: 'center'
|
||||
});
|
||||
title.setOrigin(0.5);
|
||||
|
||||
const rows = listCampaignSaveSlots().map((slot, index) => this.createSaveSlotRow(slot, -74 + index * 58));
|
||||
const close = this.createSettingsButton(0, 104, '닫기');
|
||||
close.on('pointerdown', () => {
|
||||
soundDirector.playSelect();
|
||||
this.closeSaveSlotPanel();
|
||||
});
|
||||
|
||||
panel.add([backdrop, title, ...rows, close]);
|
||||
panel.setAlpha(0);
|
||||
this.saveSlotPanel = panel;
|
||||
this.tweens.add({ targets: panel, alpha: 1, y: y + 242, duration: 160, ease: 'Sine.easeOut' });
|
||||
}
|
||||
|
||||
private createSaveSlotRow(slot: CampaignSaveSlotSummary, y: number) {
|
||||
const row = this.add.container(0, y);
|
||||
const enabled = slot.exists;
|
||||
const background = this.add.rectangle(0, 0, 270, 48, enabled ? 0x1b2530 : 0x121820, enabled ? 0.9 : 0.56);
|
||||
background.setStrokeStyle(1, enabled ? palette.gold : 0x56616d, enabled ? 0.42 : 0.2);
|
||||
|
||||
const campaign = enabled ? loadCampaignState(slot.slot) : undefined;
|
||||
const isEndingComplete = campaign?.step === 'ending-complete';
|
||||
const title = this.add.text(-122, -12, `슬롯 ${slot.slot}`, {
|
||||
fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif',
|
||||
fontSize: '16px',
|
||||
color: enabled ? '#f6e6bd' : '#7d8189',
|
||||
fontStyle: '700',
|
||||
fixedWidth: 70,
|
||||
align: 'left'
|
||||
});
|
||||
title.setOrigin(0, 0.5);
|
||||
|
||||
const detail = this.add.text(-42, -12, campaign ? this.campaignSaveDetail(campaign, isEndingComplete) : '저장 없음', {
|
||||
fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif',
|
||||
fontSize: '13px',
|
||||
color: enabled ? '#d8b15f' : '#747c86',
|
||||
fixedWidth: 154,
|
||||
align: 'left'
|
||||
});
|
||||
detail.setOrigin(0, 0.5);
|
||||
|
||||
const meta = this.add.text(-42, 12, campaign ? this.campaignSaveMeta(campaign) : '빈 슬롯', {
|
||||
fontFamily: '"Malgun Gothic", "Noto Sans KR", sans-serif',
|
||||
fontSize: '11px',
|
||||
color: enabled ? '#9fb0bf' : '#626b75',
|
||||
fixedWidth: 154,
|
||||
align: 'left'
|
||||
});
|
||||
meta.setOrigin(0, 0.5);
|
||||
|
||||
row.add([background, title, detail, meta]);
|
||||
|
||||
if (enabled) {
|
||||
background.setInteractive({ useHandCursor: true });
|
||||
background.on('pointerover', () => {
|
||||
background.setFillStyle(0xd8b15f, 0.22);
|
||||
background.setStrokeStyle(1, palette.gold, 0.72);
|
||||
soundDirector.playHover();
|
||||
});
|
||||
background.on('pointerout', () => {
|
||||
background.setFillStyle(0x1b2530, 0.9);
|
||||
background.setStrokeStyle(1, palette.gold, 0.42);
|
||||
});
|
||||
background.on('pointerdown', () => {
|
||||
this.closeSaveSlotPanel();
|
||||
this.continueGame(slot.slot);
|
||||
});
|
||||
}
|
||||
|
||||
return row;
|
||||
}
|
||||
|
||||
private closeSaveSlotPanel() {
|
||||
this.saveSlotPanel?.destroy();
|
||||
this.saveSlotPanel = undefined;
|
||||
}
|
||||
|
||||
private requestStartGame() {
|
||||
if (!hasCampaignSave()) {
|
||||
this.startGame();
|
||||
@@ -448,6 +575,7 @@ export class TitleScene extends Phaser.Scene {
|
||||
|
||||
soundDirector.playSelect();
|
||||
this.closeSettingsPanel();
|
||||
this.closeSaveSlotPanel();
|
||||
|
||||
const panel = this.add.container(x, y + 240);
|
||||
const backdrop = this.add.rectangle(0, 0, 286, 174, 0x0e151d, 0.94);
|
||||
@@ -514,15 +642,15 @@ export class TitleScene extends Phaser.Scene {
|
||||
void this.navigateTo('StoryScene');
|
||||
}
|
||||
|
||||
private continueGame() {
|
||||
private continueGame(slot?: number) {
|
||||
soundDirector.start();
|
||||
soundDirector.resume();
|
||||
soundDirector.playSelect();
|
||||
void this.continueGameAsync();
|
||||
void this.continueGameAsync(slot);
|
||||
}
|
||||
|
||||
private async continueGameAsync() {
|
||||
const campaign = loadCampaignState();
|
||||
private async continueGameAsync(slot?: number) {
|
||||
const campaign = loadCampaignState(slot);
|
||||
if (campaign.step === 'ending-complete') {
|
||||
await this.navigateTo('EndingScene');
|
||||
return;
|
||||
|
||||
Reference in New Issue
Block a user