Compare commits
2 Commits
45bb6b35ae
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a62eb2319c | ||
|
|
9899f696b2 |
@@ -478,6 +478,10 @@
|
|||||||
"__uuid__": "cff2809d-6daa-4749-a911-bb99e97b4b54",
|
"__uuid__": "cff2809d-6daa-4749-a911-bb99e97b4b54",
|
||||||
"__expectedType__": "cc.Prefab"
|
"__expectedType__": "cc.Prefab"
|
||||||
},
|
},
|
||||||
|
"passModalPrefab": {
|
||||||
|
"__uuid__": "29ff0bfc-d5cf-4ad1-b8cb-61bdfd4850ef",
|
||||||
|
"__expectedType__": "cc.Prefab"
|
||||||
|
},
|
||||||
"_id": "c2b3nbzv9JuZmP2jxQyN72"
|
"_id": "c2b3nbzv9JuZmP2jxQyN72"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -404,10 +404,7 @@
|
|||||||
"b": 255,
|
"b": 255,
|
||||||
"a": 255
|
"a": 255
|
||||||
},
|
},
|
||||||
"_spriteFrame": {
|
"_spriteFrame": null,
|
||||||
"__uuid__": "388a4fd2-4c46-46ae-b796-10ab85c39e04@f9941",
|
|
||||||
"__expectedType__": "cc.SpriteFrame"
|
|
||||||
},
|
|
||||||
"_type": 0,
|
"_type": 0,
|
||||||
"_fillType": 0,
|
"_fillType": 0,
|
||||||
"_sizeMode": 0,
|
"_sizeMode": 0,
|
||||||
@@ -6274,6 +6271,10 @@
|
|||||||
"__uuid__": "be83ca42-3579-46e8-821f-7a0f0b9d8464",
|
"__uuid__": "be83ca42-3579-46e8-821f-7a0f0b9d8464",
|
||||||
"__expectedType__": "cc.AudioClip"
|
"__expectedType__": "cc.AudioClip"
|
||||||
},
|
},
|
||||||
|
"passModalPrefab": {
|
||||||
|
"__uuid__": "29ff0bfc-d5cf-4ad1-b8cb-61bdfd4850ef",
|
||||||
|
"__expectedType__": "cc.Prefab"
|
||||||
|
},
|
||||||
"_id": ""
|
"_id": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { _decorator, Node, EditBox, instantiate, Vec3, Button, Label, Sprite, SpriteFrame, AudioClip, AudioSource, UITransform } from 'cc';
|
import { _decorator, Node, EditBox, instantiate, Vec3, Button, Label, Sprite, SpriteFrame, AudioClip, AudioSource, UITransform, Prefab } from 'cc';
|
||||||
import { BaseView } from 'db://assets/scripts/core/BaseView';
|
import { BaseView } from 'db://assets/scripts/core/BaseView';
|
||||||
import { ViewManager } from 'db://assets/scripts/core/ViewManager';
|
import { ViewManager } from 'db://assets/scripts/core/ViewManager';
|
||||||
import { StorageManager } from 'db://assets/scripts/utils/StorageManager';
|
import { StorageManager } from 'db://assets/scripts/utils/StorageManager';
|
||||||
@@ -6,6 +6,7 @@ import { WxSDK } from 'db://assets/scripts/utils/WxSDK';
|
|||||||
import { LevelDataManager } from 'db://assets/scripts/utils/LevelDataManager';
|
import { LevelDataManager } from 'db://assets/scripts/utils/LevelDataManager';
|
||||||
import { RuntimeLevelConfig } from 'db://assets/scripts/types/LevelTypes';
|
import { RuntimeLevelConfig } from 'db://assets/scripts/types/LevelTypes';
|
||||||
import { ToastManager } from 'db://assets/scripts/utils/ToastManager';
|
import { ToastManager } from 'db://assets/scripts/utils/ToastManager';
|
||||||
|
import { PassModal } from 'db://assets/prefabs/PassModal';
|
||||||
const { ccclass, property } = _decorator;
|
const { ccclass, property } = _decorator;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -14,6 +15,9 @@ const { ccclass, property } = _decorator;
|
|||||||
*/
|
*/
|
||||||
@ccclass('PageLevel')
|
@ccclass('PageLevel')
|
||||||
export class PageLevel extends BaseView {
|
export class PageLevel extends BaseView {
|
||||||
|
/** 静态常量:零位置 */
|
||||||
|
private static readonly ZERO_POS = new Vec3(0, 0, 0);
|
||||||
|
|
||||||
// ========== 节点引用 ==========
|
// ========== 节点引用 ==========
|
||||||
@property(Node)
|
@property(Node)
|
||||||
inputLayout: Node | null = null;
|
inputLayout: Node | null = null;
|
||||||
@@ -73,6 +77,9 @@ export class PageLevel extends BaseView {
|
|||||||
@property(AudioClip)
|
@property(AudioClip)
|
||||||
failAudio: AudioClip | null = null;
|
failAudio: AudioClip | null = null;
|
||||||
|
|
||||||
|
@property(Prefab)
|
||||||
|
passModalPrefab: Prefab | null = null;
|
||||||
|
|
||||||
// ========== 内部状态 ==========
|
// ========== 内部状态 ==========
|
||||||
/** 当前创建的输入框节点数组 */
|
/** 当前创建的输入框节点数组 */
|
||||||
private _inputNodes: Node[] = [];
|
private _inputNodes: Node[] = [];
|
||||||
@@ -89,17 +96,28 @@ export class PageLevel extends BaseView {
|
|||||||
/** 是否正在切换关卡(防止重复提交) */
|
/** 是否正在切换关卡(防止重复提交) */
|
||||||
private _isTransitioning: boolean = false;
|
private _isTransitioning: boolean = false;
|
||||||
|
|
||||||
|
/** 通关弹窗实例 */
|
||||||
|
private _passModalNode: Node | null = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 页面首次加载时调用
|
* 页面首次加载时调用
|
||||||
*/
|
*/
|
||||||
onViewLoad(): void {
|
onViewLoad(): void {
|
||||||
console.log('[PageLevel] onViewLoad');
|
console.log('[PageLevel] onViewLoad');
|
||||||
|
// 从本地存储恢复关卡进度
|
||||||
|
this.currentLevelIndex = StorageManager.getCurrentLevelIndex();
|
||||||
|
console.log(`[PageLevel] 恢复关卡进度: 第 ${this.currentLevelIndex + 1} 关`);
|
||||||
this.updateLiveLabel();
|
this.updateLiveLabel();
|
||||||
this.initLevel();
|
|
||||||
this.initIconSetting();
|
this.initIconSetting();
|
||||||
this.initUnlockButtons();
|
this.initUnlockButtons();
|
||||||
this.initSubmitButton();
|
this.initSubmitButton();
|
||||||
|
|
||||||
|
// 异步加载关卡资源,完成后启动倒计时
|
||||||
|
this.initLevel().then(() => {
|
||||||
this.startCountdown();
|
this.startCountdown();
|
||||||
|
}).catch(err => {
|
||||||
|
console.error('[PageLevel] 加载关卡失败:', err);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -124,13 +142,22 @@ export class PageLevel extends BaseView {
|
|||||||
console.log('[PageLevel] onViewDestroy');
|
console.log('[PageLevel] onViewDestroy');
|
||||||
this.clearInputNodes();
|
this.clearInputNodes();
|
||||||
this.stopCountdown();
|
this.stopCountdown();
|
||||||
|
this._closePassModal();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 初始化关卡(从 API 数据加载)
|
* 初始化关卡(从 API 数据加载,异步确保资源就绪)
|
||||||
*/
|
*/
|
||||||
private initLevel(): void {
|
private async initLevel(): Promise<void> {
|
||||||
const config = LevelDataManager.instance.getLevelConfig(this.currentLevelIndex);
|
// 先尝试从缓存获取
|
||||||
|
let config = LevelDataManager.instance.getLevelConfig(this.currentLevelIndex);
|
||||||
|
|
||||||
|
if (!config) {
|
||||||
|
// 缓存中没有,异步加载
|
||||||
|
console.log(`[PageLevel] 关卡 ${this.currentLevelIndex + 1} 资源未缓存,开始加载...`);
|
||||||
|
config = await LevelDataManager.instance.ensureLevelReady(this.currentLevelIndex);
|
||||||
|
}
|
||||||
|
|
||||||
if (!config) {
|
if (!config) {
|
||||||
console.warn(`[PageLevel] 没有找到关卡配置,索引: ${this.currentLevelIndex}`);
|
console.warn(`[PageLevel] 没有找到关卡配置,索引: ${this.currentLevelIndex}`);
|
||||||
return;
|
return;
|
||||||
@@ -201,7 +228,7 @@ export class PageLevel extends BaseView {
|
|||||||
inputNode.name = 'singleInput';
|
inputNode.name = 'singleInput';
|
||||||
|
|
||||||
// 设置位置
|
// 设置位置
|
||||||
inputNode.setPosition(new Vec3(0, 0, 0));
|
inputNode.setPosition(PageLevel.ZERO_POS);
|
||||||
|
|
||||||
// 获取 EditBox 组件
|
// 获取 EditBox 组件
|
||||||
const editBox = inputNode.getComponent(EditBox);
|
const editBox = inputNode.getComponent(EditBox);
|
||||||
@@ -648,10 +675,71 @@ export class PageLevel extends BaseView {
|
|||||||
// 通关奖励:增加一颗生命值
|
// 通关奖励:增加一颗生命值
|
||||||
this.addLife();
|
this.addLife();
|
||||||
|
|
||||||
// 延迟后进入下一关
|
// 显示通关弹窗
|
||||||
this.scheduleOnce(() => {
|
this._showPassModal();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 显示通关弹窗
|
||||||
|
* 将弹窗添加到 Canvas 根节点下(而非 PageLevel 子节点)
|
||||||
|
* 这样 Widget 可以正确对齐到全屏
|
||||||
|
*/
|
||||||
|
private _showPassModal(): void {
|
||||||
|
if (!this.passModalPrefab) {
|
||||||
|
console.warn('[PageLevel] passModalPrefab 未设置');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果弹窗已显示,不再重复创建
|
||||||
|
if (this._passModalNode && this._passModalNode.isValid) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 实例化弹窗
|
||||||
|
const modalNode = instantiate(this.passModalPrefab);
|
||||||
|
modalNode.setPosition(PageLevel.ZERO_POS);
|
||||||
|
modalNode.setSiblingIndex(PassModal.MODAL_Z_INDEX);
|
||||||
|
|
||||||
|
// 找到 Canvas 根节点并添加弹窗
|
||||||
|
const canvasNode = this.node.parent;
|
||||||
|
if (canvasNode) {
|
||||||
|
canvasNode.addChild(modalNode);
|
||||||
|
} else {
|
||||||
|
this.node.addChild(modalNode);
|
||||||
|
}
|
||||||
|
this._passModalNode = modalNode;
|
||||||
|
|
||||||
|
// 获取 PassModal 组件并设置回调
|
||||||
|
const passModal = modalNode.getComponent(PassModal);
|
||||||
|
if (passModal) {
|
||||||
|
passModal.setParams({ levelIndex: this.currentLevelIndex + 1 });
|
||||||
|
passModal.setCallbacks({
|
||||||
|
onNextLevel: () => {
|
||||||
|
this._closePassModal();
|
||||||
this.nextLevel();
|
this.nextLevel();
|
||||||
}, 1.0);
|
},
|
||||||
|
onShare: () => {
|
||||||
|
// 分享后不关闭弹窗,用户可继续点击下一关
|
||||||
|
console.log('[PageLevel] 分享完成');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// 手动调用 onViewLoad 和 onViewShow
|
||||||
|
passModal.onViewLoad();
|
||||||
|
passModal.onViewShow();
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('[PageLevel] 显示通关弹窗');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 关闭通关弹窗
|
||||||
|
*/
|
||||||
|
private _closePassModal(): void {
|
||||||
|
if (this._passModalNode && this._passModalNode.isValid) {
|
||||||
|
this._passModalNode.destroy();
|
||||||
|
this._passModalNode = null;
|
||||||
|
console.log('[PageLevel] 关闭通关弹窗');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -673,7 +761,10 @@ export class PageLevel extends BaseView {
|
|||||||
/**
|
/**
|
||||||
* 进入下一关
|
* 进入下一关
|
||||||
*/
|
*/
|
||||||
private nextLevel(): void {
|
private async nextLevel(): Promise<void> {
|
||||||
|
// 保存当前关卡进度
|
||||||
|
StorageManager.onLevelCompleted(this.currentLevelIndex);
|
||||||
|
|
||||||
this.currentLevelIndex++;
|
this.currentLevelIndex++;
|
||||||
|
|
||||||
// 检查是否还有关卡
|
// 检查是否还有关卡
|
||||||
@@ -688,7 +779,7 @@ export class PageLevel extends BaseView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 重置并加载下一关,重新开始倒计时
|
// 重置并加载下一关,重新开始倒计时
|
||||||
this.initLevel();
|
await this.initLevel();
|
||||||
this.startCountdown();
|
this.startCountdown();
|
||||||
console.log(`[PageLevel] 进入关卡 ${this.currentLevelIndex + 1}`);
|
console.log(`[PageLevel] 进入关卡 ${this.currentLevelIndex + 1}`);
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,14 +1,154 @@
|
|||||||
import { _decorator, Component, Node } from 'cc';
|
import { _decorator, Node, Label, AudioClip, AudioSource, view, UITransform, Size } from 'cc';
|
||||||
|
import { BaseView } from 'db://assets/scripts/core/BaseView';
|
||||||
|
import { WxSDK } from 'db://assets/scripts/utils/WxSDK';
|
||||||
const { ccclass, property } = _decorator;
|
const { ccclass, property } = _decorator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PassModal 回调接口
|
||||||
|
*/
|
||||||
|
export interface PassModalCallbacks {
|
||||||
|
/** 点击下一关回调 */
|
||||||
|
onNextLevel?: () => void;
|
||||||
|
/** 点击分享回调 */
|
||||||
|
onShare?: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通关弹窗组件
|
||||||
|
* 继承 BaseView,显示通关成功弹窗,提供"下一关"和"分享给好友"两个按钮
|
||||||
|
*/
|
||||||
@ccclass('PassModal')
|
@ccclass('PassModal')
|
||||||
export class PassModal extends Component {
|
export class PassModal extends BaseView {
|
||||||
start() {
|
/** 静态常量:弹窗层级 */
|
||||||
|
public static readonly MODAL_Z_INDEX = 999;
|
||||||
|
|
||||||
|
/** 下一关按钮 */
|
||||||
|
@property(Node)
|
||||||
|
nextLevelButton: Node | null = null;
|
||||||
|
|
||||||
|
/** 分享按钮 */
|
||||||
|
@property(Node)
|
||||||
|
shareButton: Node | null = null;
|
||||||
|
|
||||||
|
/** 提示Label(如 +1 生命) */
|
||||||
|
@property(Label)
|
||||||
|
tipLabel: Label | null = null;
|
||||||
|
|
||||||
|
/** 通关音效 */
|
||||||
|
@property(AudioClip)
|
||||||
|
successAudio: AudioClip | null = null;
|
||||||
|
|
||||||
|
/** 回调函数 */
|
||||||
|
private _callbacks: PassModalCallbacks = {};
|
||||||
|
|
||||||
|
/** 缓存的屏幕尺寸 */
|
||||||
|
private _screenSize: Size | null = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置回调函数
|
||||||
|
*/
|
||||||
|
setCallbacks(callbacks: PassModalCallbacks): void {
|
||||||
|
this._callbacks = callbacks;
|
||||||
}
|
}
|
||||||
|
|
||||||
update(deltaTime: number) {
|
/**
|
||||||
|
* 页面首次加载时调用
|
||||||
|
*/
|
||||||
|
onViewLoad(): void {
|
||||||
|
console.log('[PassModal] onViewLoad');
|
||||||
|
this._bindButtonEvents();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 页面每次显示时调用
|
||||||
|
*/
|
||||||
|
onViewShow(): void {
|
||||||
|
this._updateWidget();
|
||||||
|
this._playSuccessSound();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 页面销毁时调用
|
||||||
|
*/
|
||||||
|
onViewDestroy(): void {
|
||||||
|
this._unbindButtonEvents();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置弹窗尺寸为全屏
|
||||||
|
* 动态实例化后,手动设置节点尺寸覆盖整个屏幕
|
||||||
|
*/
|
||||||
|
private _updateWidget(): void {
|
||||||
|
// 缓存屏幕尺寸,避免重复计算
|
||||||
|
if (!this._screenSize) {
|
||||||
|
this._screenSize = view.getVisibleSize();
|
||||||
|
}
|
||||||
|
|
||||||
|
const uiTransform = this.node.getComponent(UITransform);
|
||||||
|
if (uiTransform) {
|
||||||
|
uiTransform.setContentSize(this._screenSize.width, this._screenSize.height);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 绑定按钮事件
|
||||||
|
*/
|
||||||
|
private _bindButtonEvents(): void {
|
||||||
|
if (this.nextLevelButton) {
|
||||||
|
this.nextLevelButton.on(Node.EventType.TOUCH_END, this._onNextLevelClick, this);
|
||||||
|
}
|
||||||
|
if (this.shareButton) {
|
||||||
|
this.shareButton.on(Node.EventType.TOUCH_END, this._onShareClick, this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解除按钮事件绑定
|
||||||
|
*/
|
||||||
|
private _unbindButtonEvents(): void {
|
||||||
|
// 节点可能在销毁过程中已被置空,需要检查 isValid
|
||||||
|
if (this.nextLevelButton && this.nextLevelButton.isValid) {
|
||||||
|
this.nextLevelButton.off(Node.EventType.TOUCH_END, this._onNextLevelClick, this);
|
||||||
|
}
|
||||||
|
if (this.shareButton && this.shareButton.isValid) {
|
||||||
|
this.shareButton.off(Node.EventType.TOUCH_END, this._onShareClick, this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 播放通关音效
|
||||||
|
*/
|
||||||
|
private _playSuccessSound(): void {
|
||||||
|
if (!this.successAudio) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const audioSource = this.node.getComponent(AudioSource);
|
||||||
|
if (audioSource) {
|
||||||
|
audioSource.playOneShot(this.successAudio);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 下一关按钮点击
|
||||||
|
*/
|
||||||
|
private _onNextLevelClick(): void {
|
||||||
|
console.log('[PassModal] 点击下一关');
|
||||||
|
this._callbacks.onNextLevel?.();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分享按钮点击
|
||||||
|
*/
|
||||||
|
private _onShareClick(): void {
|
||||||
|
console.log('[PassModal] 点击分享');
|
||||||
|
|
||||||
|
// 调用微信分享
|
||||||
|
WxSDK.shareAppMessage({
|
||||||
|
title: '快来一起玩这款游戏吧',
|
||||||
|
query: `level=${this._params?.levelIndex ?? 1}`
|
||||||
|
});
|
||||||
|
|
||||||
|
this._callbacks.onShare?.();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
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';
|
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_LIVES = 'game_lives';
|
||||||
|
|
||||||
|
/** 用户进度存储键 */
|
||||||
|
private static readonly KEY_PROGRESS = 'game_progress';
|
||||||
|
|
||||||
/** 默认生命值 */
|
/** 默认生命值 */
|
||||||
private static readonly DEFAULT_LIVES = 10;
|
private static readonly DEFAULT_LIVES = 10;
|
||||||
|
|
||||||
/** 最小生命值 */
|
/** 最小生命值 */
|
||||||
private static readonly MIN_LIVES = 0;
|
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);
|
StorageManager.setLives(StorageManager.DEFAULT_LIVES);
|
||||||
console.log('[StorageManager] 生命值已重置为默认值');
|
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