fix: harden turn flow interaction UX
This commit is contained in:
@@ -204,7 +204,7 @@ import {
|
||||
import { clearCampaignBattleSavesForSlot } from '../state/battleSaveKeys';
|
||||
import { releaseTextureSource } from '../render/loaderLifecycle';
|
||||
import { palette } from '../ui/palette';
|
||||
import { startLazyScene, type LazySceneKey } from './lazyScenes';
|
||||
import { ensureLazyScene, type LazySceneKey } from './lazyScenes';
|
||||
|
||||
const campLegacyCanvasWidth = 1280;
|
||||
const campLegacyCanvasHeight = 720;
|
||||
@@ -11321,6 +11321,7 @@ export class CampScene extends Phaser.Scene {
|
||||
private sortieObjects: Phaser.GameObjects.GameObject[] = [];
|
||||
private saveSlotObjects: Phaser.GameObjects.GameObject[] = [];
|
||||
private saveSlotConfirmObjects: Phaser.GameObjects.GameObject[] = [];
|
||||
private navigationBlockerObjects: Phaser.GameObjects.GameObject[] = [];
|
||||
private pendingSaveSlot?: number;
|
||||
private pendingVictoryRewardReport?: CampaignVictoryRewardReport;
|
||||
private victoryRewardObjects: Phaser.GameObjects.GameObject[] = [];
|
||||
@@ -11442,6 +11443,7 @@ export class CampScene extends Phaser.Scene {
|
||||
this.sortieOrderToggleButton = undefined;
|
||||
this.saveSlotObjects = [];
|
||||
this.saveSlotConfirmObjects = [];
|
||||
this.navigationBlockerObjects = [];
|
||||
this.pendingSaveSlot = undefined;
|
||||
this.pendingVictoryRewardReport = undefined;
|
||||
this.victoryRewardObjects = [];
|
||||
@@ -12739,6 +12741,10 @@ export class CampScene extends Phaser.Scene {
|
||||
}
|
||||
|
||||
private showCampSaveSlotPanel() {
|
||||
if (this.navigationPending) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.saveSlotObjects.length > 0) {
|
||||
this.hideCampSaveSlotPanel();
|
||||
return;
|
||||
@@ -12752,6 +12758,35 @@ export class CampScene extends Phaser.Scene {
|
||||
const activeSlot = this.campaign?.activeSaveSlot ?? getCampaignState().activeSaveSlot;
|
||||
const slots = listCampaignSaveSlots();
|
||||
|
||||
const shade = this.add.rectangle(0, 0, campLegacyCanvasWidth, campLegacyCanvasHeight, 0x020406, 0.28);
|
||||
shade.setOrigin(0);
|
||||
shade.setDepth(depth - 1);
|
||||
shade.setInteractive();
|
||||
shade.on(
|
||||
'pointerdown',
|
||||
(
|
||||
_pointer: Phaser.Input.Pointer,
|
||||
_localX: number,
|
||||
_localY: number,
|
||||
event?: { stopPropagation: () => void }
|
||||
) => {
|
||||
event?.stopPropagation();
|
||||
soundDirector.playSelect();
|
||||
this.hideCampSaveSlotPanel();
|
||||
}
|
||||
);
|
||||
shade.on(
|
||||
'wheel',
|
||||
(
|
||||
_pointer: Phaser.Input.Pointer,
|
||||
_deltaX: number,
|
||||
_deltaY: number,
|
||||
_deltaZ: number,
|
||||
event?: { stopPropagation: () => void }
|
||||
) => event?.stopPropagation()
|
||||
);
|
||||
this.trackCampSaveSlot(shade);
|
||||
|
||||
const panel = this.add.rectangle(left, top, width, height, 0x0f1720, 0.98);
|
||||
panel.setOrigin(0);
|
||||
panel.setDepth(depth);
|
||||
@@ -12980,6 +13015,11 @@ export class CampScene extends Phaser.Scene {
|
||||
}
|
||||
|
||||
private handleSaveSlotKey(event: KeyboardEvent) {
|
||||
if (this.navigationPending) {
|
||||
event.preventDefault();
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.victoryRewardObjects.length > 0) {
|
||||
return;
|
||||
}
|
||||
@@ -13491,6 +13531,10 @@ export class CampScene extends Phaser.Scene {
|
||||
}
|
||||
|
||||
private handleEscapeKey() {
|
||||
if (this.navigationPending) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.victoryRewardObjects.length > 0) {
|
||||
this.completeVictoryRewardAcknowledgement('close');
|
||||
return;
|
||||
@@ -19877,19 +19921,26 @@ export class CampScene extends Phaser.Scene {
|
||||
}
|
||||
|
||||
const previousStep = getCampaignState().step;
|
||||
markCampaignStep(step);
|
||||
this.campaign = getCampaignState();
|
||||
this.startCampNavigation(key, data, () => {
|
||||
if (getCampaignState().step === step) {
|
||||
markCampaignStep(previousStep);
|
||||
this.startCampNavigation(
|
||||
key,
|
||||
data,
|
||||
() => {
|
||||
markCampaignStep(step);
|
||||
this.campaign = getCampaignState();
|
||||
},
|
||||
() => {
|
||||
if (getCampaignState().step === step) {
|
||||
markCampaignStep(previousStep);
|
||||
}
|
||||
this.campaign = getCampaignState();
|
||||
}
|
||||
this.campaign = getCampaignState();
|
||||
});
|
||||
);
|
||||
}
|
||||
|
||||
private startCampNavigation(
|
||||
key: LazySceneKey,
|
||||
data?: Record<string, unknown>,
|
||||
commitState?: () => void,
|
||||
recoverState?: () => void
|
||||
) {
|
||||
if (this.navigationPending) {
|
||||
@@ -19897,14 +19948,94 @@ export class CampScene extends Phaser.Scene {
|
||||
}
|
||||
|
||||
this.navigationPending = true;
|
||||
void startLazyScene(this, key, data).catch((error: unknown) => {
|
||||
this.navigationPending = false;
|
||||
recoverState?.();
|
||||
console.error(`[CampScene] ${key} 전환에 실패했습니다.`, error);
|
||||
if (this.sys.isActive()) {
|
||||
this.showCampNotice('다음 장면을 불러오지 못했습니다. 잠시 후 다시 시도하십시오.');
|
||||
this.showCampNavigationBlocker();
|
||||
let stateCommitted = false;
|
||||
void (async () => {
|
||||
try {
|
||||
await ensureLazyScene(this.game, key);
|
||||
if (!this.sys.isActive()) {
|
||||
this.navigationPending = false;
|
||||
this.hideCampNavigationBlocker();
|
||||
return;
|
||||
}
|
||||
|
||||
if (commitState) {
|
||||
stateCommitted = true;
|
||||
commitState();
|
||||
}
|
||||
this.scene.start(key, data);
|
||||
} catch (error: unknown) {
|
||||
this.navigationPending = false;
|
||||
this.hideCampNavigationBlocker();
|
||||
if (stateCommitted) {
|
||||
recoverState?.();
|
||||
}
|
||||
console.error(`[CampScene] ${key} 전환에 실패했습니다.`, error);
|
||||
if (this.sys.isActive()) {
|
||||
this.showCampNotice('다음 장면을 불러오지 못했습니다. 잠시 후 다시 시도하십시오.');
|
||||
}
|
||||
}
|
||||
});
|
||||
})();
|
||||
}
|
||||
|
||||
private showCampNavigationBlocker() {
|
||||
this.hideCampNavigationBlocker();
|
||||
const depth = 120;
|
||||
const shade = this.add.rectangle(0, 0, campLegacyCanvasWidth, campLegacyCanvasHeight, 0x020406, 0.68);
|
||||
shade.setOrigin(0);
|
||||
shade.setDepth(depth);
|
||||
shade.setInteractive();
|
||||
shade.on(
|
||||
'pointerdown',
|
||||
(
|
||||
_pointer: Phaser.Input.Pointer,
|
||||
_localX: number,
|
||||
_localY: number,
|
||||
event?: { stopPropagation: () => void }
|
||||
) => event?.stopPropagation()
|
||||
);
|
||||
shade.on(
|
||||
'wheel',
|
||||
(
|
||||
_pointer: Phaser.Input.Pointer,
|
||||
_deltaX: number,
|
||||
_deltaY: number,
|
||||
_deltaZ: number,
|
||||
event?: { stopPropagation: () => void }
|
||||
) => event?.stopPropagation()
|
||||
);
|
||||
this.trackCampNavigationBlocker(shade);
|
||||
|
||||
const panel = this.add.rectangle(campLegacyCanvasWidth / 2, campLegacyCanvasHeight / 2, 330, 104, 0x111a24, 0.99);
|
||||
panel.setDepth(depth + 1);
|
||||
panel.setStrokeStyle(2, palette.gold, 0.9);
|
||||
panel.setInteractive();
|
||||
this.trackCampNavigationBlocker(panel);
|
||||
|
||||
const title = this.add.text(
|
||||
campLegacyCanvasWidth / 2,
|
||||
campLegacyCanvasHeight / 2 - 18,
|
||||
'불러오는 중…',
|
||||
this.textStyle(22, '#f2e3bf', true)
|
||||
);
|
||||
title.setOrigin(0.5);
|
||||
title.setDepth(depth + 2);
|
||||
this.trackCampNavigationBlocker(title);
|
||||
|
||||
const guide = this.add.text(
|
||||
campLegacyCanvasWidth / 2,
|
||||
campLegacyCanvasHeight / 2 + 20,
|
||||
'편성과 진행을 안전하게 준비하고 있습니다.',
|
||||
this.textStyle(12, '#aeb7c2')
|
||||
);
|
||||
guide.setOrigin(0.5);
|
||||
guide.setDepth(depth + 2);
|
||||
this.trackCampNavigationBlocker(guide);
|
||||
}
|
||||
|
||||
private hideCampNavigationBlocker() {
|
||||
this.navigationBlockerObjects.forEach((object) => object.destroy());
|
||||
this.navigationBlockerObjects = [];
|
||||
}
|
||||
|
||||
private isFinalEpilogueFlow(flow = this.currentSortieFlow()) {
|
||||
@@ -23745,6 +23876,12 @@ export class CampScene extends Phaser.Scene {
|
||||
return object;
|
||||
}
|
||||
|
||||
private trackCampNavigationBlocker<T extends Phaser.GameObjects.GameObject>(object: T) {
|
||||
this.scaleLegacyCampUi(object);
|
||||
this.navigationBlockerObjects.push(object);
|
||||
return object;
|
||||
}
|
||||
|
||||
private trackVictoryReward<T extends Phaser.GameObjects.GameObject>(object: T) {
|
||||
this.scaleLegacyCampUi(object);
|
||||
this.victoryRewardObjects.push(object);
|
||||
|
||||
Reference in New Issue
Block a user