first commmit
This commit is contained in:
@@ -0,0 +1,125 @@
|
||||
import { StorageMgr, globalStorageMgr } from '../common/StorageMgr';
|
||||
import { STORAGE_KEY } from '../common/Constants';
|
||||
|
||||
/**
|
||||
* UIFlowMgr — scene-flow state machine (task 9.2, req 12.7-12.8, 13.1).
|
||||
*
|
||||
* It does **not** perform `director.loadScene()` itself; the Cocos view layer
|
||||
* subscribes to `onSceneEnter` and performs the actual scene swap. Keeping
|
||||
* the flow engine-agnostic makes it trivial to Jest-test every boot path,
|
||||
* every settlement path, and the new story-intro gate (req 19.x).
|
||||
*
|
||||
* Decision D-4 / req 13.1 guardrail:
|
||||
* The `showDifficultyPicker()` action simply does not exist; and
|
||||
* `availableSettingsEntries()` purposefully omits it. A future contributor
|
||||
* who tries to add a "difficulty" key will hit a TypeScript compile-error
|
||||
* because the union `SettingsKey` is exhaustive.
|
||||
*/
|
||||
|
||||
export type SceneId =
|
||||
| 'boot'
|
||||
| 'story_intro'
|
||||
| 'main_menu'
|
||||
| 'level_select'
|
||||
| 'gameplay'
|
||||
| 'settlement'
|
||||
| 'settings';
|
||||
|
||||
export type SettingsKey =
|
||||
| 'audio_volume'
|
||||
| 'layout_customisation'
|
||||
| 'replay_tutorial'
|
||||
| 'replay_story_intro';
|
||||
|
||||
export interface ISceneEnter {
|
||||
scene: SceneId;
|
||||
/** Optional payload (e.g. `{ levelId: '1-1' }` for gameplay). */
|
||||
payload?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
export interface IUIFlowCallbacks {
|
||||
onSceneEnter?: (ev: ISceneEnter) => void;
|
||||
}
|
||||
|
||||
export class UIFlowMgr {
|
||||
private current: SceneId = 'boot';
|
||||
|
||||
constructor(
|
||||
private readonly storage: StorageMgr = globalStorageMgr,
|
||||
private readonly callbacks: IUIFlowCallbacks = {}
|
||||
) {}
|
||||
|
||||
public get currentScene(): SceneId {
|
||||
return this.current;
|
||||
}
|
||||
|
||||
/** Invoked by `GameBoot.start()` once engine is ready. */
|
||||
public onBoot(): void {
|
||||
if (this.storage.get<boolean>(STORAGE_KEY.StoryIntroSeen, false)) {
|
||||
this.enter('main_menu');
|
||||
} else {
|
||||
this.enter('story_intro');
|
||||
}
|
||||
}
|
||||
|
||||
/** Called by StorySceneCtrl.onFinished(). */
|
||||
public onStoryFinished(): void {
|
||||
this.enter('gameplay', { levelId: '1-1' });
|
||||
}
|
||||
|
||||
/** Main menu → level select. */
|
||||
public onPressStartGame(): void {
|
||||
// First-time "Start Game" may jump through the story again if we
|
||||
// ever reset; otherwise go to level select.
|
||||
if (!this.storage.get<boolean>(STORAGE_KEY.StoryIntroSeen, false)) {
|
||||
this.enter('story_intro');
|
||||
} else {
|
||||
this.enter('level_select');
|
||||
}
|
||||
}
|
||||
|
||||
public onPickLevel(levelId: string): void {
|
||||
this.enter('gameplay', { levelId });
|
||||
}
|
||||
|
||||
public onOpenSettings(): void {
|
||||
this.enter('settings');
|
||||
}
|
||||
|
||||
public onCloseSettings(): void {
|
||||
this.enter('main_menu');
|
||||
}
|
||||
|
||||
public onLevelCleared(nextLevelId: string | null): void {
|
||||
if (nextLevelId) {
|
||||
this.enter('settlement', { nextLevelId });
|
||||
} else {
|
||||
// After final boss settlement, back to the main menu.
|
||||
this.enter('settlement', { isChapterEnd: true });
|
||||
}
|
||||
}
|
||||
|
||||
public onSettlementContinue(nextLevelId?: string): void {
|
||||
if (nextLevelId) this.enter('gameplay', { levelId: nextLevelId });
|
||||
else this.enter('main_menu');
|
||||
}
|
||||
|
||||
public onPlayerDied(currentLevelId: string): void {
|
||||
this.enter('settlement', { levelId: currentLevelId, dead: true });
|
||||
}
|
||||
|
||||
/**
|
||||
* Exhaustive list of settings entries available in the Settings scene.
|
||||
* Purposefully omits any difficulty-selection entry (req 13.1).
|
||||
*/
|
||||
public availableSettingsEntries(): ReadonlyArray<SettingsKey> {
|
||||
return ['audio_volume', 'layout_customisation', 'replay_tutorial', 'replay_story_intro'];
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
|
||||
private enter(scene: SceneId, payload?: Record<string, unknown>): void {
|
||||
this.current = scene;
|
||||
this.callbacks.onSceneEnter?.({ scene, payload });
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user