Add title save slot selection
This commit is contained in:
@@ -2,7 +2,13 @@ import Phaser from 'phaser';
|
|||||||
import { soundDirector } from '../audio/SoundDirector';
|
import { soundDirector } from '../audio/SoundDirector';
|
||||||
import { getBattleScenario } from '../data/battles';
|
import { getBattleScenario } from '../data/battles';
|
||||||
import { battleIdForCampaignStep, isCampCampaignStep } from '../state/campaignRouting';
|
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 { palette } from '../ui/palette';
|
||||||
import { startLazyScene } from './lazyScenes';
|
import { startLazyScene } from './lazyScenes';
|
||||||
|
|
||||||
@@ -10,6 +16,7 @@ export class TitleScene extends Phaser.Scene {
|
|||||||
private focusedButton?: Phaser.GameObjects.Text;
|
private focusedButton?: Phaser.GameObjects.Text;
|
||||||
private settingsPanel?: Phaser.GameObjects.Container;
|
private settingsPanel?: Phaser.GameObjects.Container;
|
||||||
private newGameConfirmPanel?: Phaser.GameObjects.Container;
|
private newGameConfirmPanel?: Phaser.GameObjects.Container;
|
||||||
|
private saveSlotPanel?: Phaser.GameObjects.Container;
|
||||||
private navigating = false;
|
private navigating = false;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
@@ -20,6 +27,7 @@ export class TitleScene extends Phaser.Scene {
|
|||||||
this.focusedButton = undefined;
|
this.focusedButton = undefined;
|
||||||
this.settingsPanel = undefined;
|
this.settingsPanel = undefined;
|
||||||
this.newGameConfirmPanel = undefined;
|
this.newGameConfirmPanel = undefined;
|
||||||
|
this.saveSlotPanel = undefined;
|
||||||
this.navigating = false;
|
this.navigating = false;
|
||||||
|
|
||||||
const debugBattleId = this.debugBattleId();
|
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.add.rectangle(menuX, menuY + plateHeight / 2, 186, 2, palette.gold, 0.36);
|
||||||
|
|
||||||
this.createMenuButton(menuX, menuY - 70, '새 게임', true, () => this.requestStartGame());
|
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));
|
this.createMenuButton(menuX, menuY + 70, '설정', true, () => this.openSettingsPanel(menuX, menuY));
|
||||||
|
|
||||||
if (campaign) {
|
if (campaign) {
|
||||||
@@ -369,6 +377,7 @@ export class TitleScene extends Phaser.Scene {
|
|||||||
|
|
||||||
soundDirector.playSelect();
|
soundDirector.playSelect();
|
||||||
this.closeNewGameConfirmPanel();
|
this.closeNewGameConfirmPanel();
|
||||||
|
this.closeSaveSlotPanel();
|
||||||
|
|
||||||
const panel = this.add.container(x, y + 238);
|
const panel = this.add.container(x, y + 238);
|
||||||
const backdrop = this.add.rectangle(0, 0, 286, 188, 0x0e151d, 0.9);
|
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;
|
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() {
|
private requestStartGame() {
|
||||||
if (!hasCampaignSave()) {
|
if (!hasCampaignSave()) {
|
||||||
this.startGame();
|
this.startGame();
|
||||||
@@ -448,6 +575,7 @@ export class TitleScene extends Phaser.Scene {
|
|||||||
|
|
||||||
soundDirector.playSelect();
|
soundDirector.playSelect();
|
||||||
this.closeSettingsPanel();
|
this.closeSettingsPanel();
|
||||||
|
this.closeSaveSlotPanel();
|
||||||
|
|
||||||
const panel = this.add.container(x, y + 240);
|
const panel = this.add.container(x, y + 240);
|
||||||
const backdrop = this.add.rectangle(0, 0, 286, 174, 0x0e151d, 0.94);
|
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');
|
void this.navigateTo('StoryScene');
|
||||||
}
|
}
|
||||||
|
|
||||||
private continueGame() {
|
private continueGame(slot?: number) {
|
||||||
soundDirector.start();
|
soundDirector.start();
|
||||||
soundDirector.resume();
|
soundDirector.resume();
|
||||||
soundDirector.playSelect();
|
soundDirector.playSelect();
|
||||||
void this.continueGameAsync();
|
void this.continueGameAsync(slot);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async continueGameAsync() {
|
private async continueGameAsync(slot?: number) {
|
||||||
const campaign = loadCampaignState();
|
const campaign = loadCampaignState(slot);
|
||||||
if (campaign.step === 'ending-complete') {
|
if (campaign.step === 'ending-complete') {
|
||||||
await this.navigateTo('EndingScene');
|
await this.navigateTo('EndingScene');
|
||||||
return;
|
return;
|
||||||
|
|||||||
Reference in New Issue
Block a user