first commmit
This commit is contained in:
@@ -0,0 +1,118 @@
|
||||
import { StorageMgr, globalStorageMgr } from '../common/StorageMgr';
|
||||
import { STORAGE_KEY } from '../common/Constants';
|
||||
|
||||
/**
|
||||
* Tutorial manager (req 11.1-11.5, task 9.3).
|
||||
*
|
||||
* Pre-defined tutorial sequences for levels 1-1, 1-2, 1-3. Each step has an
|
||||
* ID the view layer uses to drive highlight-arrows; the step is "completed"
|
||||
* when the player performs the action, which the view layer signals via
|
||||
* `reportAction()`.
|
||||
*/
|
||||
|
||||
export interface ITutorialStep {
|
||||
id: string;
|
||||
/** Human-readable hint (displayed by the view layer). */
|
||||
hint: string;
|
||||
/** Action id the player must perform to advance. */
|
||||
requiredAction: string;
|
||||
}
|
||||
|
||||
export interface ITutorialSequence {
|
||||
levelId: string;
|
||||
steps: ITutorialStep[];
|
||||
}
|
||||
|
||||
/** Built-in tutorials for Chapter 1 (req 11.1-11.3). */
|
||||
export const BUILTIN_TUTORIALS: ITutorialSequence[] = [
|
||||
{
|
||||
levelId: '1-1',
|
||||
steps: [
|
||||
{ id: 'attack', hint: '点击右下的手里剑按钮', requiredAction: 'fire_shuriken' },
|
||||
{ id: 'joystick', hint: '拖动左下摇杆移动', requiredAction: 'move' },
|
||||
{ id: 'jump', hint: '点击跳跃按钮', requiredAction: 'jump' },
|
||||
],
|
||||
},
|
||||
{
|
||||
levelId: '1-2',
|
||||
steps: [
|
||||
{ id: 'parabolic', hint: '摇杆 45° 并跳跃 — 抛物线跳跃', requiredAction: 'parabolic_jump' },
|
||||
{ id: 'exclusive', hint: '两个攻击按钮互斥,选一个用', requiredAction: 'attack_switch' },
|
||||
{ id: 'parry', hint: '忍者刀可以格挡敌人刀剑', requiredAction: 'parry' },
|
||||
{ id: 'combo', hint: '跳跃中同时攻击', requiredAction: 'jump_attack' },
|
||||
{ id: 'auto_upgrade', hint: '拾取水晶玉自动强化', requiredAction: 'pickup_crystal' },
|
||||
],
|
||||
},
|
||||
{
|
||||
levelId: '1-3',
|
||||
steps: [
|
||||
{ id: 'butterfly', hint: '先击中 BOSS 身旁的蝴蝶', requiredAction: 'hit_butterfly' },
|
||||
{ id: 'boss_identify', hint: '识别 BOSS 攻击模式', requiredAction: 'dodge_boss_attack' },
|
||||
{ id: 'one_shot', hint: '显形后一击必杀', requiredAction: 'hit_revealed_boss' },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
export class TutorialMgr {
|
||||
private currentLevelId: string | null = null;
|
||||
private currentStepIndex = 0;
|
||||
|
||||
constructor(
|
||||
private readonly storage: StorageMgr = globalStorageMgr,
|
||||
private readonly sequences: ITutorialSequence[] = BUILTIN_TUTORIALS
|
||||
) {}
|
||||
|
||||
/** Start the tutorial for `levelId` if not already completed. */
|
||||
public maybeStart(levelId: string): ITutorialStep | null {
|
||||
if (this.isCompleted(levelId)) return null;
|
||||
const seq = this.sequences.find((s) => s.levelId === levelId);
|
||||
if (!seq) return null;
|
||||
this.currentLevelId = levelId;
|
||||
this.currentStepIndex = 0;
|
||||
return seq.steps[0];
|
||||
}
|
||||
|
||||
/** Called by gameplay whenever the player performs an action. */
|
||||
public reportAction(action: string): ITutorialStep | 'finished' | 'no_op' {
|
||||
if (!this.currentLevelId) return 'no_op';
|
||||
const seq = this.sequences.find((s) => s.levelId === this.currentLevelId);
|
||||
if (!seq) return 'no_op';
|
||||
const current = seq.steps[this.currentStepIndex];
|
||||
if (action !== current.requiredAction) return 'no_op';
|
||||
this.currentStepIndex++;
|
||||
if (this.currentStepIndex >= seq.steps.length) {
|
||||
this.markCompleted(this.currentLevelId);
|
||||
this.currentLevelId = null;
|
||||
this.currentStepIndex = 0;
|
||||
return 'finished';
|
||||
}
|
||||
return seq.steps[this.currentStepIndex];
|
||||
}
|
||||
|
||||
public isCompleted(levelId: string): boolean {
|
||||
const completed = this.storage.get<string[]>(STORAGE_KEY.TutorialDone, []);
|
||||
return completed.includes(levelId);
|
||||
}
|
||||
|
||||
public resetAll(): void {
|
||||
this.storage.remove(STORAGE_KEY.TutorialDone);
|
||||
this.currentLevelId = null;
|
||||
this.currentStepIndex = 0;
|
||||
}
|
||||
|
||||
public get isActive(): boolean {
|
||||
return this.currentLevelId !== null;
|
||||
}
|
||||
|
||||
public currentStep(): ITutorialStep | null {
|
||||
if (!this.currentLevelId) return null;
|
||||
const seq = this.sequences.find((s) => s.levelId === this.currentLevelId);
|
||||
return seq?.steps[this.currentStepIndex] ?? null;
|
||||
}
|
||||
|
||||
private markCompleted(levelId: string): void {
|
||||
const current = this.storage.get<string[]>(STORAGE_KEY.TutorialDone, []);
|
||||
if (!current.includes(levelId)) current.push(levelId);
|
||||
this.storage.set(STORAGE_KEY.TutorialDone, current);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user