feat: 支持本地存储关卡进度
This commit is contained in:
@@ -94,12 +94,20 @@ export class PageLevel extends BaseView {
|
||||
*/
|
||||
onViewLoad(): void {
|
||||
console.log('[PageLevel] onViewLoad');
|
||||
// 从本地存储恢复关卡进度
|
||||
this.currentLevelIndex = StorageManager.getCurrentLevelIndex();
|
||||
console.log(`[PageLevel] 恢复关卡进度: 第 ${this.currentLevelIndex + 1} 关`);
|
||||
this.updateLiveLabel();
|
||||
this.initLevel();
|
||||
this.initIconSetting();
|
||||
this.initUnlockButtons();
|
||||
this.initSubmitButton();
|
||||
this.startCountdown();
|
||||
|
||||
// 异步加载关卡资源,完成后启动倒计时
|
||||
this.initLevel().then(() => {
|
||||
this.startCountdown();
|
||||
}).catch(err => {
|
||||
console.error('[PageLevel] 加载关卡失败:', err);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -127,10 +135,18 @@ export class PageLevel extends BaseView {
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化关卡(从 API 数据加载)
|
||||
* 初始化关卡(从 API 数据加载,异步确保资源就绪)
|
||||
*/
|
||||
private initLevel(): void {
|
||||
const config = LevelDataManager.instance.getLevelConfig(this.currentLevelIndex);
|
||||
private async initLevel(): Promise<void> {
|
||||
// 先尝试从缓存获取
|
||||
let config = LevelDataManager.instance.getLevelConfig(this.currentLevelIndex);
|
||||
|
||||
if (!config) {
|
||||
// 缓存中没有,异步加载
|
||||
console.log(`[PageLevel] 关卡 ${this.currentLevelIndex + 1} 资源未缓存,开始加载...`);
|
||||
config = await LevelDataManager.instance.ensureLevelReady(this.currentLevelIndex);
|
||||
}
|
||||
|
||||
if (!config) {
|
||||
console.warn(`[PageLevel] 没有找到关卡配置,索引: ${this.currentLevelIndex}`);
|
||||
return;
|
||||
@@ -673,7 +689,10 @@ export class PageLevel extends BaseView {
|
||||
/**
|
||||
* 进入下一关
|
||||
*/
|
||||
private nextLevel(): void {
|
||||
private async nextLevel(): Promise<void> {
|
||||
// 保存当前关卡进度
|
||||
StorageManager.onLevelCompleted(this.currentLevelIndex);
|
||||
|
||||
this.currentLevelIndex++;
|
||||
|
||||
// 检查是否还有关卡
|
||||
@@ -688,7 +707,7 @@ export class PageLevel extends BaseView {
|
||||
}
|
||||
|
||||
// 重置并加载下一关,重新开始倒计时
|
||||
this.initLevel();
|
||||
await this.initLevel();
|
||||
this.startCountdown();
|
||||
console.log(`[PageLevel] 进入关卡 ${this.currentLevelIndex + 1}`);
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
BIN
assets/resources/images/pageLevel/ButtonBg.png
Normal file
BIN
assets/resources/images/pageLevel/ButtonBg.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.3 KiB |
134
assets/resources/images/pageLevel/ButtonBg.png.meta
Normal file
134
assets/resources/images/pageLevel/ButtonBg.png.meta
Normal file
@@ -0,0 +1,134 @@
|
||||
{
|
||||
"ver": "1.0.27",
|
||||
"importer": "image",
|
||||
"imported": true,
|
||||
"uuid": "e1267c1b-ceb0-4483-b36d-bc9cb4d2fd26",
|
||||
"files": [
|
||||
".json",
|
||||
".png"
|
||||
],
|
||||
"subMetas": {
|
||||
"6c48a": {
|
||||
"importer": "texture",
|
||||
"uuid": "e1267c1b-ceb0-4483-b36d-bc9cb4d2fd26@6c48a",
|
||||
"displayName": "ButtonBg",
|
||||
"id": "6c48a",
|
||||
"name": "texture",
|
||||
"userData": {
|
||||
"wrapModeS": "clamp-to-edge",
|
||||
"wrapModeT": "clamp-to-edge",
|
||||
"imageUuidOrDatabaseUri": "e1267c1b-ceb0-4483-b36d-bc9cb4d2fd26",
|
||||
"isUuid": true,
|
||||
"visible": false,
|
||||
"minfilter": "linear",
|
||||
"magfilter": "linear",
|
||||
"mipfilter": "none",
|
||||
"anisotropy": 0
|
||||
},
|
||||
"ver": "1.0.22",
|
||||
"imported": true,
|
||||
"files": [
|
||||
".json"
|
||||
],
|
||||
"subMetas": {}
|
||||
},
|
||||
"f9941": {
|
||||
"importer": "sprite-frame",
|
||||
"uuid": "e1267c1b-ceb0-4483-b36d-bc9cb4d2fd26@f9941",
|
||||
"displayName": "ButtonBg",
|
||||
"id": "f9941",
|
||||
"name": "spriteFrame",
|
||||
"userData": {
|
||||
"trimThreshold": 1,
|
||||
"rotated": false,
|
||||
"offsetX": 0,
|
||||
"offsetY": 0.5,
|
||||
"trimX": 1,
|
||||
"trimY": 0,
|
||||
"width": 306,
|
||||
"height": 77,
|
||||
"rawWidth": 308,
|
||||
"rawHeight": 78,
|
||||
"borderTop": 0,
|
||||
"borderBottom": 0,
|
||||
"borderLeft": 0,
|
||||
"borderRight": 0,
|
||||
"packable": true,
|
||||
"pixelsToUnit": 100,
|
||||
"pivotX": 0.5,
|
||||
"pivotY": 0.5,
|
||||
"meshType": 0,
|
||||
"vertices": {
|
||||
"rawPosition": [
|
||||
-153,
|
||||
-38.5,
|
||||
0,
|
||||
153,
|
||||
-38.5,
|
||||
0,
|
||||
-153,
|
||||
38.5,
|
||||
0,
|
||||
153,
|
||||
38.5,
|
||||
0
|
||||
],
|
||||
"indexes": [
|
||||
0,
|
||||
1,
|
||||
2,
|
||||
2,
|
||||
1,
|
||||
3
|
||||
],
|
||||
"uv": [
|
||||
1,
|
||||
78,
|
||||
307,
|
||||
78,
|
||||
1,
|
||||
1,
|
||||
307,
|
||||
1
|
||||
],
|
||||
"nuv": [
|
||||
0.003246753246753247,
|
||||
0.01282051282051282,
|
||||
0.9967532467532467,
|
||||
0.01282051282051282,
|
||||
0.003246753246753247,
|
||||
1,
|
||||
0.9967532467532467,
|
||||
1
|
||||
],
|
||||
"minPos": [
|
||||
-153,
|
||||
-38.5,
|
||||
0
|
||||
],
|
||||
"maxPos": [
|
||||
153,
|
||||
38.5,
|
||||
0
|
||||
]
|
||||
},
|
||||
"isUuid": true,
|
||||
"imageUuidOrDatabaseUri": "e1267c1b-ceb0-4483-b36d-bc9cb4d2fd26@6c48a",
|
||||
"atlasUuid": "",
|
||||
"trimType": "auto"
|
||||
},
|
||||
"ver": "1.0.12",
|
||||
"imported": true,
|
||||
"files": [
|
||||
".json"
|
||||
],
|
||||
"subMetas": {}
|
||||
}
|
||||
},
|
||||
"userData": {
|
||||
"type": "sprite-frame",
|
||||
"fixAlphaTransparencyArtifacts": false,
|
||||
"hasAlpha": true,
|
||||
"redirect": "e1267c1b-ceb0-4483-b36d-bc9cb4d2fd26@6c48a"
|
||||
}
|
||||
}
|
||||
BIN
assets/resources/images/pageLevel/test.png
Normal file
BIN
assets/resources/images/pageLevel/test.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 110 KiB |
134
assets/resources/images/pageLevel/test.png.meta
Normal file
134
assets/resources/images/pageLevel/test.png.meta
Normal file
@@ -0,0 +1,134 @@
|
||||
{
|
||||
"ver": "1.0.27",
|
||||
"importer": "image",
|
||||
"imported": true,
|
||||
"uuid": "d46acd4d-66e2-423b-8015-334ff99dd9f1",
|
||||
"files": [
|
||||
".json",
|
||||
".png"
|
||||
],
|
||||
"subMetas": {
|
||||
"6c48a": {
|
||||
"importer": "texture",
|
||||
"uuid": "d46acd4d-66e2-423b-8015-334ff99dd9f1@6c48a",
|
||||
"displayName": "test",
|
||||
"id": "6c48a",
|
||||
"name": "texture",
|
||||
"userData": {
|
||||
"wrapModeS": "clamp-to-edge",
|
||||
"wrapModeT": "clamp-to-edge",
|
||||
"imageUuidOrDatabaseUri": "d46acd4d-66e2-423b-8015-334ff99dd9f1",
|
||||
"isUuid": true,
|
||||
"visible": false,
|
||||
"minfilter": "linear",
|
||||
"magfilter": "linear",
|
||||
"mipfilter": "none",
|
||||
"anisotropy": 0
|
||||
},
|
||||
"ver": "1.0.22",
|
||||
"imported": true,
|
||||
"files": [
|
||||
".json"
|
||||
],
|
||||
"subMetas": {}
|
||||
},
|
||||
"f9941": {
|
||||
"importer": "sprite-frame",
|
||||
"uuid": "d46acd4d-66e2-423b-8015-334ff99dd9f1@f9941",
|
||||
"displayName": "test",
|
||||
"id": "f9941",
|
||||
"name": "spriteFrame",
|
||||
"userData": {
|
||||
"trimThreshold": 1,
|
||||
"rotated": false,
|
||||
"offsetX": 0,
|
||||
"offsetY": 0,
|
||||
"trimX": 0,
|
||||
"trimY": 0,
|
||||
"width": 536,
|
||||
"height": 548,
|
||||
"rawWidth": 536,
|
||||
"rawHeight": 548,
|
||||
"borderTop": 0,
|
||||
"borderBottom": 0,
|
||||
"borderLeft": 0,
|
||||
"borderRight": 0,
|
||||
"packable": true,
|
||||
"pixelsToUnit": 100,
|
||||
"pivotX": 0.5,
|
||||
"pivotY": 0.5,
|
||||
"meshType": 0,
|
||||
"vertices": {
|
||||
"rawPosition": [
|
||||
-268,
|
||||
-274,
|
||||
0,
|
||||
268,
|
||||
-274,
|
||||
0,
|
||||
-268,
|
||||
274,
|
||||
0,
|
||||
268,
|
||||
274,
|
||||
0
|
||||
],
|
||||
"indexes": [
|
||||
0,
|
||||
1,
|
||||
2,
|
||||
2,
|
||||
1,
|
||||
3
|
||||
],
|
||||
"uv": [
|
||||
0,
|
||||
548,
|
||||
536,
|
||||
548,
|
||||
0,
|
||||
0,
|
||||
536,
|
||||
0
|
||||
],
|
||||
"nuv": [
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
1,
|
||||
1
|
||||
],
|
||||
"minPos": [
|
||||
-268,
|
||||
-274,
|
||||
0
|
||||
],
|
||||
"maxPos": [
|
||||
268,
|
||||
274,
|
||||
0
|
||||
]
|
||||
},
|
||||
"isUuid": true,
|
||||
"imageUuidOrDatabaseUri": "d46acd4d-66e2-423b-8015-334ff99dd9f1@6c48a",
|
||||
"atlasUuid": "",
|
||||
"trimType": "auto"
|
||||
},
|
||||
"ver": "1.0.12",
|
||||
"imported": true,
|
||||
"files": [
|
||||
".json"
|
||||
],
|
||||
"subMetas": {}
|
||||
}
|
||||
},
|
||||
"userData": {
|
||||
"type": "sprite-frame",
|
||||
"fixAlphaTransparencyArtifacts": false,
|
||||
"hasAlpha": false,
|
||||
"redirect": "d46acd4d-66e2-423b-8015-334ff99dd9f1@6c48a"
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,15 @@
|
||||
import { sys } from 'cc';
|
||||
|
||||
/**
|
||||
* 用户进度数据结构
|
||||
*/
|
||||
interface UserProgress {
|
||||
/** 当前关卡索引(0-based) */
|
||||
currentLevelIndex: number;
|
||||
/** 已通关的最高关卡索引 */
|
||||
maxUnlockedLevelIndex: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 本地存储管理器
|
||||
* 统一管理用户数据的本地持久化存储
|
||||
@@ -8,12 +18,24 @@ export class StorageManager {
|
||||
/** 生命值存储键 */
|
||||
private static readonly KEY_LIVES = 'game_lives';
|
||||
|
||||
/** 用户进度存储键 */
|
||||
private static readonly KEY_PROGRESS = 'game_progress';
|
||||
|
||||
/** 默认生命值 */
|
||||
private static readonly DEFAULT_LIVES = 10;
|
||||
|
||||
/** 最小生命值 */
|
||||
private static readonly MIN_LIVES = 0;
|
||||
|
||||
/** 默认进度 */
|
||||
private static readonly DEFAULT_PROGRESS: UserProgress = {
|
||||
currentLevelIndex: 0,
|
||||
maxUnlockedLevelIndex: 0
|
||||
};
|
||||
|
||||
/** 进度缓存(避免重复读取 localStorage) */
|
||||
private static _progressCache: UserProgress | null = null;
|
||||
|
||||
// ==================== 生命值管理 ====================
|
||||
|
||||
/**
|
||||
@@ -84,4 +106,134 @@ export class StorageManager {
|
||||
StorageManager.setLives(StorageManager.DEFAULT_LIVES);
|
||||
console.log('[StorageManager] 生命值已重置为默认值');
|
||||
}
|
||||
|
||||
// ==================== 关卡进度管理 ====================
|
||||
|
||||
/**
|
||||
* 获取用户进度数据(带缓存)
|
||||
* @returns 用户进度对象的副本
|
||||
*/
|
||||
private static _getProgress(): UserProgress {
|
||||
// 返回缓存副本
|
||||
if (StorageManager._progressCache !== null) {
|
||||
return { ...StorageManager._progressCache };
|
||||
}
|
||||
|
||||
const stored = sys.localStorage.getItem(StorageManager.KEY_PROGRESS);
|
||||
if (stored === null || stored === '') {
|
||||
// 新用户,返回默认进度
|
||||
StorageManager._progressCache = { ...StorageManager.DEFAULT_PROGRESS };
|
||||
return { ...StorageManager._progressCache };
|
||||
}
|
||||
|
||||
try {
|
||||
const progress = JSON.parse(stored) as UserProgress;
|
||||
// 验证数据有效性
|
||||
if (typeof progress.currentLevelIndex !== 'number' ||
|
||||
typeof progress.maxUnlockedLevelIndex !== 'number' ||
|
||||
progress.currentLevelIndex < 0 ||
|
||||
progress.maxUnlockedLevelIndex < 0) {
|
||||
console.warn('[StorageManager] 进度数据无效,使用默认值');
|
||||
StorageManager._progressCache = { ...StorageManager.DEFAULT_PROGRESS };
|
||||
} else {
|
||||
StorageManager._progressCache = progress;
|
||||
}
|
||||
return { ...StorageManager._progressCache };
|
||||
} catch (e) {
|
||||
console.warn('[StorageManager] 解析进度数据失败,使用默认值');
|
||||
StorageManager._progressCache = { ...StorageManager.DEFAULT_PROGRESS };
|
||||
return { ...StorageManager._progressCache };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存用户进度数据
|
||||
* @param progress 进度对象
|
||||
*/
|
||||
private static _saveProgress(progress: UserProgress): void {
|
||||
StorageManager._progressCache = progress;
|
||||
sys.localStorage.setItem(StorageManager.KEY_PROGRESS, JSON.stringify(progress));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前关卡索引
|
||||
* @returns 当前关卡索引(0-based)
|
||||
*/
|
||||
static getCurrentLevelIndex(): number {
|
||||
return StorageManager._getProgress().currentLevelIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置当前关卡索引
|
||||
* @param index 关卡索引
|
||||
*/
|
||||
static setCurrentLevelIndex(index: number): void {
|
||||
if (index < 0) {
|
||||
console.warn('[StorageManager] 关卡索引不能为负数');
|
||||
return;
|
||||
}
|
||||
const progress = StorageManager._getProgress();
|
||||
progress.currentLevelIndex = index;
|
||||
StorageManager._saveProgress(progress);
|
||||
console.log(`[StorageManager] 当前关卡已更新: ${progress.currentLevelIndex}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取已解锁的最高关卡索引
|
||||
* @returns 最高关卡索引(0-based)
|
||||
*/
|
||||
static getMaxUnlockedLevelIndex(): number {
|
||||
return StorageManager._getProgress().maxUnlockedLevelIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
* 通关后更新进度
|
||||
* 当玩家通关第 N 关后,设置当前关卡为 N+1,解锁关卡更新为 max(N, 已解锁)
|
||||
* @param completedLevelIndex 刚通关的关卡索引
|
||||
*/
|
||||
static onLevelCompleted(completedLevelIndex: number): void {
|
||||
if (completedLevelIndex < 0) {
|
||||
console.warn('[StorageManager] 通关关卡索引不能为负数');
|
||||
return;
|
||||
}
|
||||
const progress = StorageManager._getProgress();
|
||||
const nextLevelIndex = completedLevelIndex + 1;
|
||||
|
||||
// 更新当前关卡为下一关
|
||||
progress.currentLevelIndex = nextLevelIndex;
|
||||
|
||||
// 更新最高解锁关卡
|
||||
progress.maxUnlockedLevelIndex = Math.max(progress.maxUnlockedLevelIndex, completedLevelIndex);
|
||||
|
||||
StorageManager._saveProgress(progress);
|
||||
console.log(`[StorageManager] 通关第 ${completedLevelIndex + 1} 关,下一关: ${nextLevelIndex + 1}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查指定关卡是否已解锁
|
||||
* @param levelIndex 关卡索引
|
||||
* @returns 是否已解锁
|
||||
*/
|
||||
static isLevelUnlocked(levelIndex: number): boolean {
|
||||
const progress = StorageManager._getProgress();
|
||||
return levelIndex <= progress.maxUnlockedLevelIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置所有进度
|
||||
*/
|
||||
static resetProgress(): void {
|
||||
StorageManager._progressCache = null;
|
||||
sys.localStorage.removeItem(StorageManager.KEY_PROGRESS);
|
||||
console.log('[StorageManager] 进度已重置');
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置所有数据(生命值 + 进度)
|
||||
*/
|
||||
static resetAll(): void {
|
||||
StorageManager.resetLives();
|
||||
StorageManager.resetProgress();
|
||||
console.log('[StorageManager] 所有数据已重置');
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user