Explorar el Código

菜单功能实现

181404010226 hace 4 meses
padre
commit
bd30ab5a7f
Se han modificado 66 ficheros con 4868 adiciones y 510 borrados
  1. 183 0
      MenuUI音频控制系统实现说明.md
  2. 450 287
      assets/Scenes/GameLevel.scene
  3. 0 9
      assets/Scenes/游戏管理.txt
  4. 0 11
      assets/Scenes/游戏管理.txt.meta
  5. 142 10
      assets/assets/Prefabs/Ball.prefab
  6. 6 6
      assets/assets/Prefabs/Enemy.prefab
  7. 9 0
      assets/resources/images/Menu.meta
  8. BIN
      assets/resources/images/Menu/001.png
  9. 134 0
      assets/resources/images/Menu/001.png.meta
  10. BIN
      assets/resources/images/Menu/002.png
  11. 134 0
      assets/resources/images/Menu/002.png.meta
  12. BIN
      assets/resources/images/Menu/003.png
  13. 134 0
      assets/resources/images/Menu/003.png.meta
  14. BIN
      assets/resources/images/Menu/004.png
  15. 134 0
      assets/resources/images/Menu/004.png.meta
  16. BIN
      assets/resources/images/Menu/005.png
  17. 134 0
      assets/resources/images/Menu/005.png.meta
  18. BIN
      assets/resources/images/Menu/006.png
  19. 134 0
      assets/resources/images/Menu/006.png.meta
  20. BIN
      assets/resources/images/Menu/sz.png
  21. 134 0
      assets/resources/images/Menu/sz.png.meta
  22. BIN
      assets/resources/images/Menu/ttt.png
  23. 134 0
      assets/resources/images/Menu/ttt.png.meta
  24. BIN
      assets/resources/images/Menu/xgt.jpg
  25. 134 0
      assets/resources/images/Menu/xgt.jpg.meta
  26. BIN
      assets/resources/images/Menu/zt-xgt.jpg
  27. 134 0
      assets/resources/images/Menu/zt-xgt.jpg.meta
  28. BIN
      assets/resources/images/Menu/底版.png
  29. 134 0
      assets/resources/images/Menu/底版.png.meta
  30. BIN
      assets/resources/images/Menu/底版111.png
  31. 134 0
      assets/resources/images/Menu/底版111.png.meta
  32. 1 1
      assets/resources/images/PlantsSprite/001-1.png.meta
  33. 1 1
      assets/resources/images/PlantsSprite/002.png.meta
  34. 1 1
      assets/resources/images/PlantsSprite/003.png.meta
  35. 1 1
      assets/resources/images/PlantsSprite/004.png.meta
  36. 1 1
      assets/resources/images/PlantsSprite/005.png.meta
  37. 1 1
      assets/resources/images/PlantsSprite/006.png.meta
  38. 1 1
      assets/resources/images/PlantsSprite/007.png.meta
  39. 1 1
      assets/resources/images/PlantsSprite/008.png.meta
  40. 1 1
      assets/resources/images/PlantsSprite/009.png.meta
  41. 1 1
      assets/resources/images/UI/主界面/xtb-2001.png.meta
  42. 31 0
      assets/resources/shaders/scan-effect.mtl
  43. 11 0
      assets/resources/shaders/scan-effect.mtl.meta
  44. 126 0
      assets/resources/shaders/scan-effects.effect
  45. 11 0
      assets/resources/shaders/scan-effects.effect.meta
  46. 66 23
      assets/scripts/Animations/GameStartMove.ts
  47. 210 0
      assets/scripts/Animations/PopUPAni.ts
  48. 9 0
      assets/scripts/Animations/PopUPAni.ts.meta
  49. 4 0
      assets/scripts/CombatSystem/GamePause.ts
  50. 9 0
      assets/scripts/CombatSystem/MenuSystem.meta
  51. 237 0
      assets/scripts/CombatSystem/MenuSystem/MenuAni.ts
  52. 9 0
      assets/scripts/CombatSystem/MenuSystem/MenuAni.ts.meta
  53. 292 0
      assets/scripts/CombatSystem/MenuSystem/MenuController.ts
  54. 9 0
      assets/scripts/CombatSystem/MenuSystem/MenuController.ts.meta
  55. 490 0
      assets/scripts/CombatSystem/MenuSystem/SoundController.ts
  56. 9 0
      assets/scripts/CombatSystem/MenuSystem/SoundController.ts.meta
  57. 319 0
      assets/scripts/Core/AudioManager.ts
  58. 9 0
      assets/scripts/Core/AudioManager.ts.meta
  59. 9 0
      assets/scripts/Examples.meta
  60. 200 0
      assets/scripts/Examples/AudioExample.ts
  61. 9 0
      assets/scripts/Examples/AudioExample.ts.meta
  62. 66 1
      assets/scripts/FourUI/NavBarController.ts
  63. 61 134
      assets/scripts/FourUI/UpgradeSystem/UpgradeAni.ts
  64. 12 17
      assets/scripts/FourUI/UpgradeSystem/UpgradeController.ts
  65. 10 2
      assets/scripts/LevelSystem/IN_game.ts
  66. 242 0
      docs/AudioSystemGuide.md

+ 183 - 0
MenuUI音频控制系统实现说明.md

@@ -0,0 +1,183 @@
+# MenuUI音频控制系统实现说明
+
+## 功能概述
+
+本次实现了完整的MenuUI音频控制系统,包括滑动条音量控制、开关按钮状态管理,以及完整的音频管理架构。
+
+## 实现的功能
+
+### 1. 滑动条音量控制
+- ✅ **音效滑动条**: Canvas/MenuUI/sound/soundEffect 的 Slider 组件
+- ✅ **音乐滑动条**: Canvas/MenuUI/sound/music 的 Slider 组件
+- ✅ **绿色进度条**: 跟随Handle的Progress变化实时更新
+- ✅ **音量应用**: 滑动时实时应用到音频系统
+
+### 2. 开关按钮功能
+- ✅ **音效开关**: Canvas/MenuUI/sound/soundEffect/checkbox 按钮控制
+- ✅ **音乐开关**: Canvas/MenuUI/sound/music/checkbox 按钮控制
+- ✅ **勾选状态**: 通过 Canvas/MenuUI/sound/soundEffect/checkbox/check 节点显隐控制
+- ✅ **状态记忆**: 取消勾选时记录当前progress,再次勾选时恢复
+- ✅ **滑动条联动**: 开关状态与滑动条位置同步
+
+### 3. 数据持久化
+- ✅ **本地存储**: 音频设置自动保存到localStorage
+- ✅ **设置恢复**: 游戏重启后自动恢复之前的音频设置
+- ✅ **状态同步**: UI状态与保存的设置保持同步
+
+## 创建的文件
+
+### 核心系统文件
+
+1. **SoundController.ts** (`assets/scripts/CombatSystem/MenuSystem/SoundController.ts`)
+   - 音频控制器主要逻辑
+   - 管理滑动条和开关按钮交互
+   - 处理进度条视觉更新
+   - 实现数据持久化
+
+2. **AudioManager.ts** (`assets/scripts/Core/AudioManager.ts`)
+   - 统一音频管理系统
+   - 单例模式,全局访问
+   - 提供音乐和音效播放控制
+   - 包含便捷的静态Audio类
+
+### 文档和示例
+
+3. **AudioSystemGuide.md** (`docs/AudioSystemGuide.md`)
+   - 完整的音频系统设置指南
+   - 详细的集成步骤说明
+   - 代码使用示例
+   - 扩展功能建议
+
+4. **AudioExample.ts** (`assets/scripts/Examples/AudioExample.ts`)
+   - 音频系统使用示例代码
+   - 展示各种场景下的音频调用
+   - 包含武器音效、UI交互音效等示例
+
+### 更新的文件
+
+5. **MenuController.ts** (已更新)
+   - 添加了SoundController组件引用
+   - 集成音频控制功能
+
+## 组件配置说明
+
+### SoundController组件属性
+
+在Canvas/MenuUI节点上添加SoundController组件,需要配置以下属性:
+
+#### 音效控制
+- `soundEffectSlider`: 音效滑动条 (Slider组件)
+- `soundEffectCheckbox`: 音效开关按钮 (Button组件)
+- `soundEffectCheck`: 音效勾选标记节点 (Node)
+- `soundEffectProgressBg`: 音效进度条背景 (Sprite组件)
+
+#### 音乐控制
+- `musicSlider`: 音乐滑动条 (Slider组件)
+- `musicCheckbox`: 音乐开关按钮 (Button组件)
+- `musicCheck`: 音乐勾选标记节点 (Node)
+- `musicProgressBg`: 音乐进度条背景 (Sprite组件)
+
+#### 视觉资源
+- `greenProgressSprite`: 绿色进度条材质 (SpriteFrame)
+
+## 使用方法
+
+### 1. 在场景中设置AudioManager
+
+```typescript
+// 在主场景中创建AudioManager节点
+Canvas/
+├── AudioManager (添加AudioManager组件)
+```
+
+### 2. 配置MenuUI的SoundController
+
+```typescript
+// 在MenuUI节点上添加SoundController组件
+// 并在Inspector中配置所有必要的属性引用
+```
+
+### 3. 在代码中使用音频功能
+
+```typescript
+import { Audio } from '../Core/AudioManager';
+
+// 播放背景音乐
+Audio.playMusic('audio/music/background_music');
+
+// 播放音效
+Audio.playSFX('audio/sfx/button_click');
+
+// 控制音量
+Audio.setMusicVolume(0.7);
+Audio.setSFXVolume(0.8);
+```
+
+## 技术特点
+
+### 1. 模块化设计
+- 音频管理与UI控制分离
+- 单一职责原则
+- 易于扩展和维护
+
+### 2. 用户体验优化
+- 实时音量反馈
+- 视觉进度条跟随
+- 状态记忆功能
+- 平滑的交互体验
+
+### 3. 数据管理
+- 自动保存设置
+- 跨会话状态恢复
+- 异常处理机制
+
+### 4. 性能考虑
+- 单例模式减少资源消耗
+- 事件驱动架构
+- 资源懒加载
+
+## 后续集成步骤
+
+### 1. 添加音频资源
+
+将音频文件放置在 `assets/resources/audio/` 目录下:
+
+```
+assets/resources/audio/
+├── music/
+│   ├── background_music.mp3
+│   └── menu_music.mp3
+└── sfx/
+    ├── button_click.mp3
+    ├── weapon_fire.mp3
+    └── explosion.mp3
+```
+
+### 2. 在游戏逻辑中集成音效
+
+参考 `AudioExample.ts` 中的示例,在相应的游戏逻辑中添加音效调用。
+
+### 3. 测试和调优
+
+- 测试所有音频控制功能
+- 调整音量平衡
+- 优化音频文件大小
+- 验证跨平台兼容性
+
+## 扩展建议
+
+1. **音频淡入淡出**: 添加平滑的音量过渡效果
+2. **音效池管理**: 对频繁播放的音效实现对象池
+3. **3D音效支持**: 为空间音效添加位置信息
+4. **音频压缩**: 根据平台选择合适的音频格式
+5. **动态加载**: 实现音频资源的动态加载和卸载
+
+## 总结
+
+本次实现提供了完整的MenuUI音频控制系统,包括:
+- 直观的滑动条音量控制
+- 智能的开关按钮状态管理
+- 完整的音频管理架构
+- 详细的使用文档和示例
+
+系统设计遵循了良好的软件工程原则,具有良好的可扩展性和可维护性,为后续的音频功能扩展奠定了坚实的基础。

La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 450 - 287
assets/Scenes/GameLevel.scene


+ 0 - 9
assets/Scenes/游戏管理.txt

@@ -1,9 +0,0 @@
-游戏中各种资源数据的管理:
-一、实体型数据例如小球、方块、敌人等。应当随着关卡结束销毁。
-二、战斗数据:例如敌人波数、敌人数量、子弹伤害、武器类型等级伤害、墙体血量等。当关卡重置时应该从本地json中读取数据,结合脚本计算数值进行更新。
-三、UI:现在的ui分为游戏内的Canvas/GameLevelUI为游戏游玩界面ui、Canvas/GameLevelUI/BlockSelectionUI方块选择ui、Canvas/SelectSkillUI技能选择ui、Canvas/GameEnd游戏结束ui
-游戏外的:Canvas/MainUI主界面ui、Canvas/ShopUI商店ui、Canvas/SkillUpUI游戏外的技能升级ui(和游戏内的技能不同!)、Canvas/UpgradeUI(暂未设计)
-当关卡重置时,应该按要求显示ui:1.备战阶段block_selection:进入关卡游戏开始动画(只要是触发onBattle方法),都应该先显示Canvas/GameLevelUI为游戏游玩界面ui和Canvas/GameLevelUI/BlockSelectionUI方块选择ui即备战阶段,其它ui都是关闭的。
-2.playing游玩状态,是正式开始游戏,方块选择ui隐藏,游戏开始动画结束。敌人和小球都开始运动,子弹开始发射。若能量满弹出Canvas/SelectSkillUI技能选择ui,若每波结束弹出BlockSelectionUI方块选择ui。
-3.游戏结束:胜利或者失败,游戏进程都暂停,敌人、小球都停止运动,正在发射的子弹可以发射出去。
-游戏结束后点击继续触发onMainMenuClick()方法返回游戏外主界面ui。

+ 0 - 11
assets/Scenes/游戏管理.txt.meta

@@ -1,11 +0,0 @@
-{
-  "ver": "1.0.1",
-  "importer": "text",
-  "imported": true,
-  "uuid": "b35c354b-1532-4ab5-ade2-05a9d3e417b2",
-  "files": [
-    ".json"
-  ],
-  "subMetas": {},
-  "userData": {}
-}

+ 142 - 10
assets/assets/Prefabs/Ball.prefab

@@ -17,24 +17,28 @@
     "_objFlags": 0,
     "__editorExtras__": {},
     "_parent": null,
-    "_children": [],
+    "_children": [
+      {
+        "__id__": 2
+      }
+    ],
     "_active": true,
     "_components": [
       {
-        "__id__": 2
+        "__id__": 8
       },
       {
-        "__id__": 4
+        "__id__": 10
       },
       {
-        "__id__": 6
+        "__id__": 12
       },
       {
-        "__id__": 8
+        "__id__": 14
       }
     ],
     "_prefab": {
-      "__id__": 10
+      "__id__": 16
     },
     "_lpos": {
       "__type__": "cc.Vec3",
@@ -65,6 +69,134 @@
     },
     "_id": ""
   },
+  {
+    "__type__": "cc.Node",
+    "_name": "Trail",
+    "_objFlags": 0,
+    "__editorExtras__": {},
+    "_parent": {
+      "__id__": 1
+    },
+    "_children": [],
+    "_active": true,
+    "_components": [
+      {
+        "__id__": 3
+      },
+      {
+        "__id__": 5
+      }
+    ],
+    "_prefab": {
+      "__id__": 7
+    },
+    "_lpos": {
+      "__type__": "cc.Vec3",
+      "x": 0,
+      "y": -67.034,
+      "z": 0
+    },
+    "_lrot": {
+      "__type__": "cc.Quat",
+      "x": 0,
+      "y": 0,
+      "z": 0,
+      "w": 1
+    },
+    "_lscale": {
+      "__type__": "cc.Vec3",
+      "x": 1,
+      "y": 1,
+      "z": 1
+    },
+    "_mobility": 0,
+    "_layer": 1073741824,
+    "_euler": {
+      "__type__": "cc.Vec3",
+      "x": 0,
+      "y": 0,
+      "z": 0
+    },
+    "_id": ""
+  },
+  {
+    "__type__": "cc.UITransform",
+    "_name": "",
+    "_objFlags": 0,
+    "__editorExtras__": {},
+    "node": {
+      "__id__": 2
+    },
+    "_enabled": true,
+    "__prefab": {
+      "__id__": 4
+    },
+    "_contentSize": {
+      "__type__": "cc.Size",
+      "width": 100,
+      "height": 100
+    },
+    "_anchorPoint": {
+      "__type__": "cc.Vec2",
+      "x": 0.5,
+      "y": 0.5
+    },
+    "_id": ""
+  },
+  {
+    "__type__": "cc.CompPrefabInfo",
+    "fileId": "e6D+a5LJhNMbmfbUg+FdOC"
+  },
+  {
+    "__type__": "cc.MotionStreak",
+    "_name": "",
+    "_objFlags": 0,
+    "__editorExtras__": {},
+    "node": {
+      "__id__": 2
+    },
+    "_enabled": true,
+    "__prefab": {
+      "__id__": 6
+    },
+    "_customMaterial": {
+      "__uuid__": "76a76f1d-d1a0-4d66-b032-e1b0a442bf0c",
+      "__expectedType__": "cc.Material"
+    },
+    "_srcBlendFactor": 2,
+    "_dstBlendFactor": 4,
+    "_color": {
+      "__type__": "cc.Color",
+      "r": 255,
+      "g": 255,
+      "b": 255,
+      "a": 255
+    },
+    "_preview": true,
+    "_fadeTime": 1,
+    "_minSeg": 1,
+    "_stroke": 64,
+    "_texture": null,
+    "_fastMode": false,
+    "_id": ""
+  },
+  {
+    "__type__": "cc.CompPrefabInfo",
+    "fileId": "8ajZW0twxE7Ii+Rdoz3CRI"
+  },
+  {
+    "__type__": "cc.PrefabInfo",
+    "root": {
+      "__id__": 1
+    },
+    "asset": {
+      "__id__": 0
+    },
+    "fileId": "fa/A8AQ9hN/pzSCYZ03Md5",
+    "instance": null,
+    "targetOverrides": null,
+    "nestedPrefabInstanceRoots": null
+  },
   {
     "__type__": "cc.UITransform",
     "_name": "",
@@ -75,7 +207,7 @@
     },
     "_enabled": true,
     "__prefab": {
-      "__id__": 3
+      "__id__": 9
     },
     "_contentSize": {
       "__type__": "cc.Size",
@@ -103,7 +235,7 @@
     },
     "_enabled": true,
     "__prefab": {
-      "__id__": 5
+      "__id__": 11
     },
     "_customMaterial": null,
     "_srcBlendFactor": 2,
@@ -148,7 +280,7 @@
     },
     "_enabled": true,
     "__prefab": {
-      "__id__": 7
+      "__id__": 13
     },
     "enabledContactListener": true,
     "bullet": true,
@@ -182,7 +314,7 @@
     },
     "_enabled": true,
     "__prefab": {
-      "__id__": 9
+      "__id__": 15
     },
     "tag": 0,
     "_group": 2,

+ 6 - 6
assets/assets/Prefabs/Enemy.prefab

@@ -60,7 +60,7 @@
       "z": 1
     },
     "_mobility": 0,
-    "_layer": 33554432,
+    "_layer": 1073741824,
     "_euler": {
       "__type__": "cc.Vec3",
       "x": 0,
@@ -120,7 +120,7 @@
       "z": 1
     },
     "_mobility": 0,
-    "_layer": 33554432,
+    "_layer": 1073741824,
     "_euler": {
       "__type__": "cc.Vec3",
       "x": 0,
@@ -177,7 +177,7 @@
       "z": 1
     },
     "_mobility": 0,
-    "_layer": 33554432,
+    "_layer": 1073741824,
     "_euler": {
       "__type__": "cc.Vec3",
       "x": 0,
@@ -227,7 +227,7 @@
       "z": 1
     },
     "_mobility": 0,
-    "_layer": 33554432,
+    "_layer": 1073741824,
     "_euler": {
       "__type__": "cc.Vec3",
       "x": 0,
@@ -481,7 +481,7 @@
       "z": 1
     },
     "_mobility": 0,
-    "_layer": 33554432,
+    "_layer": 1073741824,
     "_euler": {
       "__type__": "cc.Vec3",
       "x": 0,
@@ -531,7 +531,7 @@
       "z": 1
     },
     "_mobility": 0,
-    "_layer": 33554432,
+    "_layer": 1073741824,
     "_euler": {
       "__type__": "cc.Vec3",
       "x": 0,

+ 9 - 0
assets/resources/images/Menu.meta

@@ -0,0 +1,9 @@
+{
+  "ver": "1.2.0",
+  "importer": "directory",
+  "imported": true,
+  "uuid": "7dabd910-5be6-4a6a-ad16-a02d4414ab76",
+  "files": [],
+  "subMetas": {},
+  "userData": {}
+}

BIN
assets/resources/images/Menu/001.png


+ 134 - 0
assets/resources/images/Menu/001.png.meta

@@ -0,0 +1,134 @@
+{
+  "ver": "1.0.27",
+  "importer": "image",
+  "imported": true,
+  "uuid": "6a713561-5dff-494e-b8c9-e4a480dcf9b6",
+  "files": [
+    ".json",
+    ".png"
+  ],
+  "subMetas": {
+    "6c48a": {
+      "importer": "texture",
+      "uuid": "6a713561-5dff-494e-b8c9-e4a480dcf9b6@6c48a",
+      "displayName": "001",
+      "id": "6c48a",
+      "name": "texture",
+      "userData": {
+        "wrapModeS": "clamp-to-edge",
+        "wrapModeT": "clamp-to-edge",
+        "imageUuidOrDatabaseUri": "6a713561-5dff-494e-b8c9-e4a480dcf9b6",
+        "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": "6a713561-5dff-494e-b8c9-e4a480dcf9b6@f9941",
+      "displayName": "001",
+      "id": "f9941",
+      "name": "spriteFrame",
+      "userData": {
+        "trimThreshold": 1,
+        "rotated": false,
+        "offsetX": 0,
+        "offsetY": 0,
+        "trimX": 0,
+        "trimY": 0,
+        "width": 53,
+        "height": 53,
+        "rawWidth": 53,
+        "rawHeight": 53,
+        "borderTop": 0,
+        "borderBottom": 0,
+        "borderLeft": 0,
+        "borderRight": 0,
+        "packable": true,
+        "pixelsToUnit": 100,
+        "pivotX": 0.5,
+        "pivotY": 0.5,
+        "meshType": 0,
+        "vertices": {
+          "rawPosition": [
+            -26.5,
+            -26.5,
+            0,
+            26.5,
+            -26.5,
+            0,
+            -26.5,
+            26.5,
+            0,
+            26.5,
+            26.5,
+            0
+          ],
+          "indexes": [
+            0,
+            1,
+            2,
+            2,
+            1,
+            3
+          ],
+          "uv": [
+            0,
+            53,
+            53,
+            53,
+            0,
+            0,
+            53,
+            0
+          ],
+          "nuv": [
+            0,
+            0,
+            1,
+            0,
+            0,
+            1,
+            1,
+            1
+          ],
+          "minPos": [
+            -26.5,
+            -26.5,
+            0
+          ],
+          "maxPos": [
+            26.5,
+            26.5,
+            0
+          ]
+        },
+        "isUuid": true,
+        "imageUuidOrDatabaseUri": "6a713561-5dff-494e-b8c9-e4a480dcf9b6@6c48a",
+        "atlasUuid": "",
+        "trimType": "auto"
+      },
+      "ver": "1.0.12",
+      "imported": true,
+      "files": [
+        ".json"
+      ],
+      "subMetas": {}
+    }
+  },
+  "userData": {
+    "type": "sprite-frame",
+    "hasAlpha": true,
+    "fixAlphaTransparencyArtifacts": false,
+    "redirect": "6a713561-5dff-494e-b8c9-e4a480dcf9b6@6c48a"
+  }
+}

BIN
assets/resources/images/Menu/002.png


+ 134 - 0
assets/resources/images/Menu/002.png.meta

@@ -0,0 +1,134 @@
+{
+  "ver": "1.0.27",
+  "importer": "image",
+  "imported": true,
+  "uuid": "8b080b37-4761-4f1d-a351-90760c7fc73b",
+  "files": [
+    ".json",
+    ".png"
+  ],
+  "subMetas": {
+    "6c48a": {
+      "importer": "texture",
+      "uuid": "8b080b37-4761-4f1d-a351-90760c7fc73b@6c48a",
+      "displayName": "002",
+      "id": "6c48a",
+      "name": "texture",
+      "userData": {
+        "wrapModeS": "clamp-to-edge",
+        "wrapModeT": "clamp-to-edge",
+        "imageUuidOrDatabaseUri": "8b080b37-4761-4f1d-a351-90760c7fc73b",
+        "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": "8b080b37-4761-4f1d-a351-90760c7fc73b@f9941",
+      "displayName": "002",
+      "id": "f9941",
+      "name": "spriteFrame",
+      "userData": {
+        "trimThreshold": 1,
+        "rotated": false,
+        "offsetX": 0,
+        "offsetY": 0,
+        "trimX": 0,
+        "trimY": 0,
+        "width": 343,
+        "height": 55,
+        "rawWidth": 343,
+        "rawHeight": 55,
+        "borderTop": 0,
+        "borderBottom": 0,
+        "borderLeft": 0,
+        "borderRight": 0,
+        "packable": true,
+        "pixelsToUnit": 100,
+        "pivotX": 0.5,
+        "pivotY": 0.5,
+        "meshType": 0,
+        "vertices": {
+          "rawPosition": [
+            -171.5,
+            -27.5,
+            0,
+            171.5,
+            -27.5,
+            0,
+            -171.5,
+            27.5,
+            0,
+            171.5,
+            27.5,
+            0
+          ],
+          "indexes": [
+            0,
+            1,
+            2,
+            2,
+            1,
+            3
+          ],
+          "uv": [
+            0,
+            55,
+            343,
+            55,
+            0,
+            0,
+            343,
+            0
+          ],
+          "nuv": [
+            0,
+            0,
+            1,
+            0,
+            0,
+            1,
+            1,
+            1
+          ],
+          "minPos": [
+            -171.5,
+            -27.5,
+            0
+          ],
+          "maxPos": [
+            171.5,
+            27.5,
+            0
+          ]
+        },
+        "isUuid": true,
+        "imageUuidOrDatabaseUri": "8b080b37-4761-4f1d-a351-90760c7fc73b@6c48a",
+        "atlasUuid": "",
+        "trimType": "auto"
+      },
+      "ver": "1.0.12",
+      "imported": true,
+      "files": [
+        ".json"
+      ],
+      "subMetas": {}
+    }
+  },
+  "userData": {
+    "type": "sprite-frame",
+    "hasAlpha": true,
+    "fixAlphaTransparencyArtifacts": false,
+    "redirect": "8b080b37-4761-4f1d-a351-90760c7fc73b@6c48a"
+  }
+}

BIN
assets/resources/images/Menu/003.png


+ 134 - 0
assets/resources/images/Menu/003.png.meta

@@ -0,0 +1,134 @@
+{
+  "ver": "1.0.27",
+  "importer": "image",
+  "imported": true,
+  "uuid": "af1deab4-6784-4608-839f-f45cbd93ff36",
+  "files": [
+    ".json",
+    ".png"
+  ],
+  "subMetas": {
+    "6c48a": {
+      "importer": "texture",
+      "uuid": "af1deab4-6784-4608-839f-f45cbd93ff36@6c48a",
+      "displayName": "003",
+      "id": "6c48a",
+      "name": "texture",
+      "userData": {
+        "wrapModeS": "clamp-to-edge",
+        "wrapModeT": "clamp-to-edge",
+        "imageUuidOrDatabaseUri": "af1deab4-6784-4608-839f-f45cbd93ff36",
+        "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": "af1deab4-6784-4608-839f-f45cbd93ff36@f9941",
+      "displayName": "003",
+      "id": "f9941",
+      "name": "spriteFrame",
+      "userData": {
+        "trimThreshold": 1,
+        "rotated": false,
+        "offsetX": 0,
+        "offsetY": 0,
+        "trimX": 0,
+        "trimY": 0,
+        "width": 263,
+        "height": 48,
+        "rawWidth": 263,
+        "rawHeight": 48,
+        "borderTop": 0,
+        "borderBottom": 0,
+        "borderLeft": 0,
+        "borderRight": 0,
+        "packable": true,
+        "pixelsToUnit": 100,
+        "pivotX": 0.5,
+        "pivotY": 0.5,
+        "meshType": 0,
+        "vertices": {
+          "rawPosition": [
+            -131.5,
+            -24,
+            0,
+            131.5,
+            -24,
+            0,
+            -131.5,
+            24,
+            0,
+            131.5,
+            24,
+            0
+          ],
+          "indexes": [
+            0,
+            1,
+            2,
+            2,
+            1,
+            3
+          ],
+          "uv": [
+            0,
+            48,
+            263,
+            48,
+            0,
+            0,
+            263,
+            0
+          ],
+          "nuv": [
+            0,
+            0,
+            1,
+            0,
+            0,
+            1,
+            1,
+            1
+          ],
+          "minPos": [
+            -131.5,
+            -24,
+            0
+          ],
+          "maxPos": [
+            131.5,
+            24,
+            0
+          ]
+        },
+        "isUuid": true,
+        "imageUuidOrDatabaseUri": "af1deab4-6784-4608-839f-f45cbd93ff36@6c48a",
+        "atlasUuid": "",
+        "trimType": "auto"
+      },
+      "ver": "1.0.12",
+      "imported": true,
+      "files": [
+        ".json"
+      ],
+      "subMetas": {}
+    }
+  },
+  "userData": {
+    "type": "sprite-frame",
+    "hasAlpha": true,
+    "fixAlphaTransparencyArtifacts": false,
+    "redirect": "af1deab4-6784-4608-839f-f45cbd93ff36@6c48a"
+  }
+}

BIN
assets/resources/images/Menu/004.png


+ 134 - 0
assets/resources/images/Menu/004.png.meta

@@ -0,0 +1,134 @@
+{
+  "ver": "1.0.27",
+  "importer": "image",
+  "imported": true,
+  "uuid": "1f2d0f95-da9b-4067-81cf-a0e8bbfc3377",
+  "files": [
+    ".json",
+    ".png"
+  ],
+  "subMetas": {
+    "6c48a": {
+      "importer": "texture",
+      "uuid": "1f2d0f95-da9b-4067-81cf-a0e8bbfc3377@6c48a",
+      "displayName": "004",
+      "id": "6c48a",
+      "name": "texture",
+      "userData": {
+        "wrapModeS": "clamp-to-edge",
+        "wrapModeT": "clamp-to-edge",
+        "imageUuidOrDatabaseUri": "1f2d0f95-da9b-4067-81cf-a0e8bbfc3377",
+        "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": "1f2d0f95-da9b-4067-81cf-a0e8bbfc3377@f9941",
+      "displayName": "004",
+      "id": "f9941",
+      "name": "spriteFrame",
+      "userData": {
+        "trimThreshold": 1,
+        "rotated": false,
+        "offsetX": 0,
+        "offsetY": 0,
+        "trimX": 0,
+        "trimY": 0,
+        "width": 92,
+        "height": 51,
+        "rawWidth": 92,
+        "rawHeight": 51,
+        "borderTop": 0,
+        "borderBottom": 0,
+        "borderLeft": 0,
+        "borderRight": 0,
+        "packable": true,
+        "pixelsToUnit": 100,
+        "pivotX": 0.5,
+        "pivotY": 0.5,
+        "meshType": 0,
+        "vertices": {
+          "rawPosition": [
+            -46,
+            -25.5,
+            0,
+            46,
+            -25.5,
+            0,
+            -46,
+            25.5,
+            0,
+            46,
+            25.5,
+            0
+          ],
+          "indexes": [
+            0,
+            1,
+            2,
+            2,
+            1,
+            3
+          ],
+          "uv": [
+            0,
+            51,
+            92,
+            51,
+            0,
+            0,
+            92,
+            0
+          ],
+          "nuv": [
+            0,
+            0,
+            1,
+            0,
+            0,
+            1,
+            1,
+            1
+          ],
+          "minPos": [
+            -46,
+            -25.5,
+            0
+          ],
+          "maxPos": [
+            46,
+            25.5,
+            0
+          ]
+        },
+        "isUuid": true,
+        "imageUuidOrDatabaseUri": "1f2d0f95-da9b-4067-81cf-a0e8bbfc3377@6c48a",
+        "atlasUuid": "",
+        "trimType": "auto"
+      },
+      "ver": "1.0.12",
+      "imported": true,
+      "files": [
+        ".json"
+      ],
+      "subMetas": {}
+    }
+  },
+  "userData": {
+    "type": "sprite-frame",
+    "hasAlpha": true,
+    "fixAlphaTransparencyArtifacts": false,
+    "redirect": "1f2d0f95-da9b-4067-81cf-a0e8bbfc3377@6c48a"
+  }
+}

BIN
assets/resources/images/Menu/005.png


+ 134 - 0
assets/resources/images/Menu/005.png.meta

@@ -0,0 +1,134 @@
+{
+  "ver": "1.0.27",
+  "importer": "image",
+  "imported": true,
+  "uuid": "4c9b0b24-b5a1-4b8c-92f8-5d46b598735a",
+  "files": [
+    ".json",
+    ".png"
+  ],
+  "subMetas": {
+    "6c48a": {
+      "importer": "texture",
+      "uuid": "4c9b0b24-b5a1-4b8c-92f8-5d46b598735a@6c48a",
+      "displayName": "005",
+      "id": "6c48a",
+      "name": "texture",
+      "userData": {
+        "wrapModeS": "clamp-to-edge",
+        "wrapModeT": "clamp-to-edge",
+        "imageUuidOrDatabaseUri": "4c9b0b24-b5a1-4b8c-92f8-5d46b598735a",
+        "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": "4c9b0b24-b5a1-4b8c-92f8-5d46b598735a@f9941",
+      "displayName": "005",
+      "id": "f9941",
+      "name": "spriteFrame",
+      "userData": {
+        "trimThreshold": 1,
+        "rotated": false,
+        "offsetX": 0,
+        "offsetY": 0,
+        "trimX": 0,
+        "trimY": 0,
+        "width": 195,
+        "height": 56,
+        "rawWidth": 195,
+        "rawHeight": 56,
+        "borderTop": 0,
+        "borderBottom": 0,
+        "borderLeft": 0,
+        "borderRight": 0,
+        "packable": true,
+        "pixelsToUnit": 100,
+        "pivotX": 0.5,
+        "pivotY": 0.5,
+        "meshType": 0,
+        "vertices": {
+          "rawPosition": [
+            -97.5,
+            -28,
+            0,
+            97.5,
+            -28,
+            0,
+            -97.5,
+            28,
+            0,
+            97.5,
+            28,
+            0
+          ],
+          "indexes": [
+            0,
+            1,
+            2,
+            2,
+            1,
+            3
+          ],
+          "uv": [
+            0,
+            56,
+            195,
+            56,
+            0,
+            0,
+            195,
+            0
+          ],
+          "nuv": [
+            0,
+            0,
+            1,
+            0,
+            0,
+            1,
+            1,
+            1
+          ],
+          "minPos": [
+            -97.5,
+            -28,
+            0
+          ],
+          "maxPos": [
+            97.5,
+            28,
+            0
+          ]
+        },
+        "isUuid": true,
+        "imageUuidOrDatabaseUri": "4c9b0b24-b5a1-4b8c-92f8-5d46b598735a@6c48a",
+        "atlasUuid": "",
+        "trimType": "auto"
+      },
+      "ver": "1.0.12",
+      "imported": true,
+      "files": [
+        ".json"
+      ],
+      "subMetas": {}
+    }
+  },
+  "userData": {
+    "type": "sprite-frame",
+    "hasAlpha": true,
+    "fixAlphaTransparencyArtifacts": false,
+    "redirect": "4c9b0b24-b5a1-4b8c-92f8-5d46b598735a@6c48a"
+  }
+}

BIN
assets/resources/images/Menu/006.png


+ 134 - 0
assets/resources/images/Menu/006.png.meta

@@ -0,0 +1,134 @@
+{
+  "ver": "1.0.27",
+  "importer": "image",
+  "imported": true,
+  "uuid": "08ff727a-219b-4de9-b172-a51bc8ebdede",
+  "files": [
+    ".json",
+    ".png"
+  ],
+  "subMetas": {
+    "6c48a": {
+      "importer": "texture",
+      "uuid": "08ff727a-219b-4de9-b172-a51bc8ebdede@6c48a",
+      "displayName": "006",
+      "id": "6c48a",
+      "name": "texture",
+      "userData": {
+        "wrapModeS": "clamp-to-edge",
+        "wrapModeT": "clamp-to-edge",
+        "imageUuidOrDatabaseUri": "08ff727a-219b-4de9-b172-a51bc8ebdede",
+        "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": "08ff727a-219b-4de9-b172-a51bc8ebdede@f9941",
+      "displayName": "006",
+      "id": "f9941",
+      "name": "spriteFrame",
+      "userData": {
+        "trimThreshold": 1,
+        "rotated": false,
+        "offsetX": 0,
+        "offsetY": 0,
+        "trimX": 0,
+        "trimY": 0,
+        "width": 92,
+        "height": 51,
+        "rawWidth": 92,
+        "rawHeight": 51,
+        "borderTop": 0,
+        "borderBottom": 0,
+        "borderLeft": 0,
+        "borderRight": 0,
+        "packable": true,
+        "pixelsToUnit": 100,
+        "pivotX": 0.5,
+        "pivotY": 0.5,
+        "meshType": 0,
+        "vertices": {
+          "rawPosition": [
+            -46,
+            -25.5,
+            0,
+            46,
+            -25.5,
+            0,
+            -46,
+            25.5,
+            0,
+            46,
+            25.5,
+            0
+          ],
+          "indexes": [
+            0,
+            1,
+            2,
+            2,
+            1,
+            3
+          ],
+          "uv": [
+            0,
+            51,
+            92,
+            51,
+            0,
+            0,
+            92,
+            0
+          ],
+          "nuv": [
+            0,
+            0,
+            1,
+            0,
+            0,
+            1,
+            1,
+            1
+          ],
+          "minPos": [
+            -46,
+            -25.5,
+            0
+          ],
+          "maxPos": [
+            46,
+            25.5,
+            0
+          ]
+        },
+        "isUuid": true,
+        "imageUuidOrDatabaseUri": "08ff727a-219b-4de9-b172-a51bc8ebdede@6c48a",
+        "atlasUuid": "",
+        "trimType": "auto"
+      },
+      "ver": "1.0.12",
+      "imported": true,
+      "files": [
+        ".json"
+      ],
+      "subMetas": {}
+    }
+  },
+  "userData": {
+    "type": "sprite-frame",
+    "hasAlpha": true,
+    "fixAlphaTransparencyArtifacts": false,
+    "redirect": "08ff727a-219b-4de9-b172-a51bc8ebdede@6c48a"
+  }
+}

BIN
assets/resources/images/Menu/sz.png


+ 134 - 0
assets/resources/images/Menu/sz.png.meta

@@ -0,0 +1,134 @@
+{
+  "ver": "1.0.27",
+  "importer": "image",
+  "imported": true,
+  "uuid": "0fe71cda-3654-4efb-a244-cbad8c513891",
+  "files": [
+    ".json",
+    ".png"
+  ],
+  "subMetas": {
+    "6c48a": {
+      "importer": "texture",
+      "uuid": "0fe71cda-3654-4efb-a244-cbad8c513891@6c48a",
+      "displayName": "sz",
+      "id": "6c48a",
+      "name": "texture",
+      "userData": {
+        "wrapModeS": "clamp-to-edge",
+        "wrapModeT": "clamp-to-edge",
+        "imageUuidOrDatabaseUri": "0fe71cda-3654-4efb-a244-cbad8c513891",
+        "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": "0fe71cda-3654-4efb-a244-cbad8c513891@f9941",
+      "displayName": "sz",
+      "id": "f9941",
+      "name": "spriteFrame",
+      "userData": {
+        "trimThreshold": 1,
+        "rotated": false,
+        "offsetX": 0,
+        "offsetY": 0,
+        "trimX": 0,
+        "trimY": 0,
+        "width": 41,
+        "height": 41,
+        "rawWidth": 41,
+        "rawHeight": 41,
+        "borderTop": 0,
+        "borderBottom": 0,
+        "borderLeft": 0,
+        "borderRight": 0,
+        "packable": true,
+        "pixelsToUnit": 100,
+        "pivotX": 0.5,
+        "pivotY": 0.5,
+        "meshType": 0,
+        "vertices": {
+          "rawPosition": [
+            -20.5,
+            -20.5,
+            0,
+            20.5,
+            -20.5,
+            0,
+            -20.5,
+            20.5,
+            0,
+            20.5,
+            20.5,
+            0
+          ],
+          "indexes": [
+            0,
+            1,
+            2,
+            2,
+            1,
+            3
+          ],
+          "uv": [
+            0,
+            41,
+            41,
+            41,
+            0,
+            0,
+            41,
+            0
+          ],
+          "nuv": [
+            0,
+            0,
+            1,
+            0,
+            0,
+            1,
+            1,
+            1
+          ],
+          "minPos": [
+            -20.5,
+            -20.5,
+            0
+          ],
+          "maxPos": [
+            20.5,
+            20.5,
+            0
+          ]
+        },
+        "isUuid": true,
+        "imageUuidOrDatabaseUri": "0fe71cda-3654-4efb-a244-cbad8c513891@6c48a",
+        "atlasUuid": "",
+        "trimType": "auto"
+      },
+      "ver": "1.0.12",
+      "imported": true,
+      "files": [
+        ".json"
+      ],
+      "subMetas": {}
+    }
+  },
+  "userData": {
+    "type": "sprite-frame",
+    "hasAlpha": true,
+    "fixAlphaTransparencyArtifacts": false,
+    "redirect": "0fe71cda-3654-4efb-a244-cbad8c513891@6c48a"
+  }
+}

BIN
assets/resources/images/Menu/ttt.png


+ 134 - 0
assets/resources/images/Menu/ttt.png.meta

@@ -0,0 +1,134 @@
+{
+  "ver": "1.0.27",
+  "importer": "image",
+  "imported": true,
+  "uuid": "e102b9dc-9a11-44e5-94ff-cf33cab6f221",
+  "files": [
+    ".json",
+    ".png"
+  ],
+  "subMetas": {
+    "6c48a": {
+      "importer": "texture",
+      "uuid": "e102b9dc-9a11-44e5-94ff-cf33cab6f221@6c48a",
+      "displayName": "ttt",
+      "id": "6c48a",
+      "name": "texture",
+      "userData": {
+        "wrapModeS": "clamp-to-edge",
+        "wrapModeT": "clamp-to-edge",
+        "imageUuidOrDatabaseUri": "e102b9dc-9a11-44e5-94ff-cf33cab6f221",
+        "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": "e102b9dc-9a11-44e5-94ff-cf33cab6f221@f9941",
+      "displayName": "ttt",
+      "id": "f9941",
+      "name": "spriteFrame",
+      "userData": {
+        "trimThreshold": 1,
+        "rotated": false,
+        "offsetX": 0,
+        "offsetY": 0,
+        "trimX": 0,
+        "trimY": 0,
+        "width": 46,
+        "height": 46,
+        "rawWidth": 46,
+        "rawHeight": 46,
+        "borderTop": 0,
+        "borderBottom": 0,
+        "borderLeft": 0,
+        "borderRight": 0,
+        "packable": true,
+        "pixelsToUnit": 100,
+        "pivotX": 0.5,
+        "pivotY": 0.5,
+        "meshType": 0,
+        "vertices": {
+          "rawPosition": [
+            -23,
+            -23,
+            0,
+            23,
+            -23,
+            0,
+            -23,
+            23,
+            0,
+            23,
+            23,
+            0
+          ],
+          "indexes": [
+            0,
+            1,
+            2,
+            2,
+            1,
+            3
+          ],
+          "uv": [
+            0,
+            46,
+            46,
+            46,
+            0,
+            0,
+            46,
+            0
+          ],
+          "nuv": [
+            0,
+            0,
+            1,
+            0,
+            0,
+            1,
+            1,
+            1
+          ],
+          "minPos": [
+            -23,
+            -23,
+            0
+          ],
+          "maxPos": [
+            23,
+            23,
+            0
+          ]
+        },
+        "isUuid": true,
+        "imageUuidOrDatabaseUri": "e102b9dc-9a11-44e5-94ff-cf33cab6f221@6c48a",
+        "atlasUuid": "",
+        "trimType": "auto"
+      },
+      "ver": "1.0.12",
+      "imported": true,
+      "files": [
+        ".json"
+      ],
+      "subMetas": {}
+    }
+  },
+  "userData": {
+    "type": "sprite-frame",
+    "hasAlpha": true,
+    "fixAlphaTransparencyArtifacts": false,
+    "redirect": "e102b9dc-9a11-44e5-94ff-cf33cab6f221@6c48a"
+  }
+}

BIN
assets/resources/images/Menu/xgt.jpg


+ 134 - 0
assets/resources/images/Menu/xgt.jpg.meta

@@ -0,0 +1,134 @@
+{
+  "ver": "1.0.27",
+  "importer": "image",
+  "imported": true,
+  "uuid": "b68cbe59-5f6b-46a8-aa86-0896b2628209",
+  "files": [
+    ".jpg",
+    ".json"
+  ],
+  "subMetas": {
+    "6c48a": {
+      "importer": "texture",
+      "uuid": "b68cbe59-5f6b-46a8-aa86-0896b2628209@6c48a",
+      "displayName": "xgt",
+      "id": "6c48a",
+      "name": "texture",
+      "userData": {
+        "wrapModeS": "clamp-to-edge",
+        "wrapModeT": "clamp-to-edge",
+        "imageUuidOrDatabaseUri": "b68cbe59-5f6b-46a8-aa86-0896b2628209",
+        "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": "b68cbe59-5f6b-46a8-aa86-0896b2628209@f9941",
+      "displayName": "xgt",
+      "id": "f9941",
+      "name": "spriteFrame",
+      "userData": {
+        "trimThreshold": 1,
+        "rotated": false,
+        "offsetX": 0,
+        "offsetY": 0,
+        "trimX": 0,
+        "trimY": 0,
+        "width": 720,
+        "height": 1334,
+        "rawWidth": 720,
+        "rawHeight": 1334,
+        "borderTop": 0,
+        "borderBottom": 0,
+        "borderLeft": 0,
+        "borderRight": 0,
+        "packable": true,
+        "pixelsToUnit": 100,
+        "pivotX": 0.5,
+        "pivotY": 0.5,
+        "meshType": 0,
+        "vertices": {
+          "rawPosition": [
+            -360,
+            -667,
+            0,
+            360,
+            -667,
+            0,
+            -360,
+            667,
+            0,
+            360,
+            667,
+            0
+          ],
+          "indexes": [
+            0,
+            1,
+            2,
+            2,
+            1,
+            3
+          ],
+          "uv": [
+            0,
+            1334,
+            720,
+            1334,
+            0,
+            0,
+            720,
+            0
+          ],
+          "nuv": [
+            0,
+            0,
+            1,
+            0,
+            0,
+            1,
+            1,
+            1
+          ],
+          "minPos": [
+            -360,
+            -667,
+            0
+          ],
+          "maxPos": [
+            360,
+            667,
+            0
+          ]
+        },
+        "isUuid": true,
+        "imageUuidOrDatabaseUri": "b68cbe59-5f6b-46a8-aa86-0896b2628209@6c48a",
+        "atlasUuid": "",
+        "trimType": "auto"
+      },
+      "ver": "1.0.12",
+      "imported": true,
+      "files": [
+        ".json"
+      ],
+      "subMetas": {}
+    }
+  },
+  "userData": {
+    "type": "sprite-frame",
+    "hasAlpha": false,
+    "fixAlphaTransparencyArtifacts": false,
+    "redirect": "b68cbe59-5f6b-46a8-aa86-0896b2628209@6c48a"
+  }
+}

BIN
assets/resources/images/Menu/zt-xgt.jpg


+ 134 - 0
assets/resources/images/Menu/zt-xgt.jpg.meta

@@ -0,0 +1,134 @@
+{
+  "ver": "1.0.27",
+  "importer": "image",
+  "imported": true,
+  "uuid": "48b99626-3e49-4445-b167-6dba97d36b5e",
+  "files": [
+    ".jpg",
+    ".json"
+  ],
+  "subMetas": {
+    "6c48a": {
+      "importer": "texture",
+      "uuid": "48b99626-3e49-4445-b167-6dba97d36b5e@6c48a",
+      "displayName": "zt-xgt",
+      "id": "6c48a",
+      "name": "texture",
+      "userData": {
+        "wrapModeS": "clamp-to-edge",
+        "wrapModeT": "clamp-to-edge",
+        "imageUuidOrDatabaseUri": "48b99626-3e49-4445-b167-6dba97d36b5e",
+        "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": "48b99626-3e49-4445-b167-6dba97d36b5e@f9941",
+      "displayName": "zt-xgt",
+      "id": "f9941",
+      "name": "spriteFrame",
+      "userData": {
+        "trimThreshold": 1,
+        "rotated": false,
+        "offsetX": 0,
+        "offsetY": 0,
+        "trimX": 0,
+        "trimY": 0,
+        "width": 720,
+        "height": 1334,
+        "rawWidth": 720,
+        "rawHeight": 1334,
+        "borderTop": 0,
+        "borderBottom": 0,
+        "borderLeft": 0,
+        "borderRight": 0,
+        "packable": true,
+        "pixelsToUnit": 100,
+        "pivotX": 0.5,
+        "pivotY": 0.5,
+        "meshType": 0,
+        "vertices": {
+          "rawPosition": [
+            -360,
+            -667,
+            0,
+            360,
+            -667,
+            0,
+            -360,
+            667,
+            0,
+            360,
+            667,
+            0
+          ],
+          "indexes": [
+            0,
+            1,
+            2,
+            2,
+            1,
+            3
+          ],
+          "uv": [
+            0,
+            1334,
+            720,
+            1334,
+            0,
+            0,
+            720,
+            0
+          ],
+          "nuv": [
+            0,
+            0,
+            1,
+            0,
+            0,
+            1,
+            1,
+            1
+          ],
+          "minPos": [
+            -360,
+            -667,
+            0
+          ],
+          "maxPos": [
+            360,
+            667,
+            0
+          ]
+        },
+        "isUuid": true,
+        "imageUuidOrDatabaseUri": "48b99626-3e49-4445-b167-6dba97d36b5e@6c48a",
+        "atlasUuid": "",
+        "trimType": "auto"
+      },
+      "ver": "1.0.12",
+      "imported": true,
+      "files": [
+        ".json"
+      ],
+      "subMetas": {}
+    }
+  },
+  "userData": {
+    "type": "sprite-frame",
+    "hasAlpha": false,
+    "fixAlphaTransparencyArtifacts": false,
+    "redirect": "48b99626-3e49-4445-b167-6dba97d36b5e@6c48a"
+  }
+}

BIN
assets/resources/images/Menu/底版.png


+ 134 - 0
assets/resources/images/Menu/底版.png.meta

@@ -0,0 +1,134 @@
+{
+  "ver": "1.0.27",
+  "importer": "image",
+  "imported": true,
+  "uuid": "0ad68874-5af0-4ca5-a433-8b638970d708",
+  "files": [
+    ".json",
+    ".png"
+  ],
+  "subMetas": {
+    "6c48a": {
+      "importer": "texture",
+      "uuid": "0ad68874-5af0-4ca5-a433-8b638970d708@6c48a",
+      "displayName": "底版",
+      "id": "6c48a",
+      "name": "texture",
+      "userData": {
+        "wrapModeS": "clamp-to-edge",
+        "wrapModeT": "clamp-to-edge",
+        "imageUuidOrDatabaseUri": "0ad68874-5af0-4ca5-a433-8b638970d708",
+        "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": "0ad68874-5af0-4ca5-a433-8b638970d708@f9941",
+      "displayName": "底版",
+      "id": "f9941",
+      "name": "spriteFrame",
+      "userData": {
+        "trimThreshold": 1,
+        "rotated": false,
+        "offsetX": 0,
+        "offsetY": 0,
+        "trimX": 0,
+        "trimY": 0,
+        "width": 608,
+        "height": 697,
+        "rawWidth": 608,
+        "rawHeight": 697,
+        "borderTop": 0,
+        "borderBottom": 0,
+        "borderLeft": 0,
+        "borderRight": 0,
+        "packable": true,
+        "pixelsToUnit": 100,
+        "pivotX": 0.5,
+        "pivotY": 0.5,
+        "meshType": 0,
+        "vertices": {
+          "rawPosition": [
+            -304,
+            -348.5,
+            0,
+            304,
+            -348.5,
+            0,
+            -304,
+            348.5,
+            0,
+            304,
+            348.5,
+            0
+          ],
+          "indexes": [
+            0,
+            1,
+            2,
+            2,
+            1,
+            3
+          ],
+          "uv": [
+            0,
+            697,
+            608,
+            697,
+            0,
+            0,
+            608,
+            0
+          ],
+          "nuv": [
+            0,
+            0,
+            1,
+            0,
+            0,
+            1,
+            1,
+            1
+          ],
+          "minPos": [
+            -304,
+            -348.5,
+            0
+          ],
+          "maxPos": [
+            304,
+            348.5,
+            0
+          ]
+        },
+        "isUuid": true,
+        "imageUuidOrDatabaseUri": "0ad68874-5af0-4ca5-a433-8b638970d708@6c48a",
+        "atlasUuid": "",
+        "trimType": "auto"
+      },
+      "ver": "1.0.12",
+      "imported": true,
+      "files": [
+        ".json"
+      ],
+      "subMetas": {}
+    }
+  },
+  "userData": {
+    "type": "sprite-frame",
+    "hasAlpha": true,
+    "fixAlphaTransparencyArtifacts": false,
+    "redirect": "0ad68874-5af0-4ca5-a433-8b638970d708@6c48a"
+  }
+}

BIN
assets/resources/images/Menu/底版111.png


+ 134 - 0
assets/resources/images/Menu/底版111.png.meta

@@ -0,0 +1,134 @@
+{
+  "ver": "1.0.27",
+  "importer": "image",
+  "imported": true,
+  "uuid": "f8002d53-0822-47b1-add6-059a20f3e898",
+  "files": [
+    ".json",
+    ".png"
+  ],
+  "subMetas": {
+    "6c48a": {
+      "importer": "texture",
+      "uuid": "f8002d53-0822-47b1-add6-059a20f3e898@6c48a",
+      "displayName": "底版111",
+      "id": "6c48a",
+      "name": "texture",
+      "userData": {
+        "wrapModeS": "clamp-to-edge",
+        "wrapModeT": "clamp-to-edge",
+        "imageUuidOrDatabaseUri": "f8002d53-0822-47b1-add6-059a20f3e898",
+        "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": "f8002d53-0822-47b1-add6-059a20f3e898@f9941",
+      "displayName": "底版111",
+      "id": "f9941",
+      "name": "spriteFrame",
+      "userData": {
+        "trimThreshold": 1,
+        "rotated": false,
+        "offsetX": 0,
+        "offsetY": 0,
+        "trimX": 0,
+        "trimY": 0,
+        "width": 609,
+        "height": 428,
+        "rawWidth": 609,
+        "rawHeight": 428,
+        "borderTop": 0,
+        "borderBottom": 0,
+        "borderLeft": 0,
+        "borderRight": 0,
+        "packable": true,
+        "pixelsToUnit": 100,
+        "pivotX": 0.5,
+        "pivotY": 0.5,
+        "meshType": 0,
+        "vertices": {
+          "rawPosition": [
+            -304.5,
+            -214,
+            0,
+            304.5,
+            -214,
+            0,
+            -304.5,
+            214,
+            0,
+            304.5,
+            214,
+            0
+          ],
+          "indexes": [
+            0,
+            1,
+            2,
+            2,
+            1,
+            3
+          ],
+          "uv": [
+            0,
+            428,
+            609,
+            428,
+            0,
+            0,
+            609,
+            0
+          ],
+          "nuv": [
+            0,
+            0,
+            1,
+            0,
+            0,
+            1,
+            1,
+            1
+          ],
+          "minPos": [
+            -304.5,
+            -214,
+            0
+          ],
+          "maxPos": [
+            304.5,
+            214,
+            0
+          ]
+        },
+        "isUuid": true,
+        "imageUuidOrDatabaseUri": "f8002d53-0822-47b1-add6-059a20f3e898@6c48a",
+        "atlasUuid": "",
+        "trimType": "auto"
+      },
+      "ver": "1.0.12",
+      "imported": true,
+      "files": [
+        ".json"
+      ],
+      "subMetas": {}
+    }
+  },
+  "userData": {
+    "type": "sprite-frame",
+    "hasAlpha": true,
+    "fixAlphaTransparencyArtifacts": false,
+    "redirect": "f8002d53-0822-47b1-add6-059a20f3e898@6c48a"
+  }
+}

+ 1 - 1
assets/resources/images/PlantsSprite/001-1.png.meta

@@ -53,7 +53,7 @@
         "borderBottom": 0,
         "borderLeft": 0,
         "borderRight": 0,
-        "packable": true,
+        "packable": false,
         "pixelsToUnit": 100,
         "pivotX": 0.5,
         "pivotY": 0.5,

+ 1 - 1
assets/resources/images/PlantsSprite/002.png.meta

@@ -53,7 +53,7 @@
         "borderBottom": 0,
         "borderLeft": 0,
         "borderRight": 0,
-        "packable": true,
+        "packable": false,
         "pixelsToUnit": 100,
         "pivotX": 0.5,
         "pivotY": 0.5,

+ 1 - 1
assets/resources/images/PlantsSprite/003.png.meta

@@ -53,7 +53,7 @@
         "borderBottom": 0,
         "borderLeft": 0,
         "borderRight": 0,
-        "packable": true,
+        "packable": false,
         "pixelsToUnit": 100,
         "pivotX": 0.5,
         "pivotY": 0.5,

+ 1 - 1
assets/resources/images/PlantsSprite/004.png.meta

@@ -53,7 +53,7 @@
         "borderBottom": 0,
         "borderLeft": 0,
         "borderRight": 0,
-        "packable": true,
+        "packable": false,
         "pixelsToUnit": 100,
         "pivotX": 0.5,
         "pivotY": 0.5,

+ 1 - 1
assets/resources/images/PlantsSprite/005.png.meta

@@ -53,7 +53,7 @@
         "borderBottom": 0,
         "borderLeft": 0,
         "borderRight": 0,
-        "packable": true,
+        "packable": false,
         "pixelsToUnit": 100,
         "pivotX": 0.5,
         "pivotY": 0.5,

+ 1 - 1
assets/resources/images/PlantsSprite/006.png.meta

@@ -53,7 +53,7 @@
         "borderBottom": 0,
         "borderLeft": 0,
         "borderRight": 0,
-        "packable": true,
+        "packable": false,
         "pixelsToUnit": 100,
         "pivotX": 0.5,
         "pivotY": 0.5,

+ 1 - 1
assets/resources/images/PlantsSprite/007.png.meta

@@ -53,7 +53,7 @@
         "borderBottom": 0,
         "borderLeft": 0,
         "borderRight": 0,
-        "packable": true,
+        "packable": false,
         "pixelsToUnit": 100,
         "pivotX": 0.5,
         "pivotY": 0.5,

+ 1 - 1
assets/resources/images/PlantsSprite/008.png.meta

@@ -53,7 +53,7 @@
         "borderBottom": 0,
         "borderLeft": 0,
         "borderRight": 0,
-        "packable": true,
+        "packable": false,
         "pixelsToUnit": 100,
         "pivotX": 0.5,
         "pivotY": 0.5,

+ 1 - 1
assets/resources/images/PlantsSprite/009.png.meta

@@ -53,7 +53,7 @@
         "borderBottom": 0,
         "borderLeft": 0,
         "borderRight": 0,
-        "packable": true,
+        "packable": false,
         "pixelsToUnit": 100,
         "pivotX": 0.5,
         "pivotY": 0.5,

+ 1 - 1
assets/resources/images/UI/主界面/xtb-2001.png.meta

@@ -53,7 +53,7 @@
         "borderBottom": 0,
         "borderLeft": 0,
         "borderRight": 0,
-        "packable": true,
+        "packable": false,
         "pixelsToUnit": 100,
         "pivotX": 0.5,
         "pivotY": 0.5,

+ 31 - 0
assets/resources/shaders/scan-effect.mtl

@@ -0,0 +1,31 @@
+{
+  "__type__": "cc.Material",
+  "_name": "",
+  "_objFlags": 0,
+  "__editorExtras__": {},
+  "_native": "",
+  "_effectAsset": {
+    "__uuid__": "79c4e894-ff40-492e-8e15-af06cfe21b69",
+    "__expectedType__": "cc.EffectAsset"
+  },
+  "_techIdx": 0,
+  "_defines": [
+    {
+      "USE_TEXTURE": true
+    }
+  ],
+  "_states": [
+    {
+      "rasterizerState": {},
+      "depthStencilState": {},
+      "blendState": {
+        "targets": [
+          {}
+        ]
+      }
+    }
+  ],
+  "_props": [
+    {}
+  ]
+}

+ 11 - 0
assets/resources/shaders/scan-effect.mtl.meta

@@ -0,0 +1,11 @@
+{
+  "ver": "1.0.21",
+  "importer": "material",
+  "imported": true,
+  "uuid": "963222d5-e711-47cc-a4dc-3898c755dc8d",
+  "files": [
+    ".json"
+  ],
+  "subMetas": {},
+  "userData": {}
+}

+ 126 - 0
assets/resources/shaders/scan-effects.effect

@@ -0,0 +1,126 @@
+// Copyright (c) 2017-2020 Xiamen Yaji Software Co., Ltd.
+CCEffect %{
+  techniques:
+  - passes:
+    - vert: sprite-vs:vert
+      frag: sprite-fs:frag
+      depthStencilState:
+        depthTest: false
+        depthWrite: false
+      blendState:
+        targets:
+        - blend: true
+          blendSrc: src_alpha
+          blendDst: one_minus_src_alpha
+          blendDstAlpha: one_minus_src_alpha
+      rasterizerState:
+        cullMode: none
+      properties:
+        scanColor: { value: [0.5, 0.5, 0.5, 0.2] }
+        scanWidth: { value: 0.05 }
+        scanSpeed: { value: 1.0 }
+        scanIntensity: { value: 2.0 }
+        alphaThreshold: { value: 0.5 }
+}%
+
+CCProgram sprite-vs %{
+  precision highp float;
+  #include <builtin/uniforms/cc-global>
+  #if USE_LOCAL
+    #include <builtin/uniforms/cc-local>
+  #endif
+  #if SAMPLE_FROM_RT
+    #include <common/common-define>
+  #endif
+  in vec3 a_position;
+  in vec2 a_texCoord;
+  in vec4 a_color;
+
+  out vec4 color;
+  out vec2 uv0;
+
+  vec4 vert () {
+    vec4 pos = vec4(a_position, 1);
+
+    #if USE_LOCAL
+      pos = cc_matWorld * pos;
+    #endif
+
+    #if USE_PIXEL_ALIGNMENT
+      pos = cc_matView * pos;
+      pos.xyz = floor(pos.xyz);
+      pos = cc_matProj * pos;
+    #else
+      pos = cc_matViewProj * pos;
+    #endif
+
+    uv0 = a_texCoord;
+    #if SAMPLE_FROM_RT
+      CC_HANDLE_RT_SAMPLE_FLIP(uv0);
+    #endif
+    color = a_color;
+
+    return pos;
+  }
+}%
+
+CCProgram sprite-fs %{
+  precision highp float;
+  #include <builtin/internal/embedded-alpha>
+  #include <builtin/internal/alpha-test>
+  #include <builtin/uniforms/cc-global>
+  
+  in vec4 color;
+
+  #if USE_TEXTURE
+    in vec2 uv0;
+    #pragma builtin(local)
+    layout(set = 2, binding = 12) uniform sampler2D cc_spriteTexture;
+  #endif
+
+  uniform ScanUniforms {
+    vec4 scanColor;
+    float scanWidth;
+    float scanSpeed;
+    float scanIntensity;
+  };
+
+  vec4 frag () {
+    vec4 o = vec4(1, 1, 1, 1);
+
+    #if USE_TEXTURE
+      o *= CCSampleWithAlphaSeparated(cc_spriteTexture, uv0);
+      
+      // 计算扫描线位置 (从右下到左上)
+      float scanPosition = mod(cc_time.x * scanSpeed, 2.0 + scanWidth);
+      
+      // 计算当前像素到扫描线的距离 (对角线扫描)
+      float diagonalPos = (1.0-(uv0.x + uv0.y) * 0.5); // 从右下到左上的对角线位置
+      float distanceToScan = abs(diagonalPos - scanPosition);
+      
+      // 创建扫描线效果
+      float scanEffect = 0.0;
+      if (distanceToScan < scanWidth) {
+        // 在扫描线范围内,计算强度
+        float normalizedDistance = distanceToScan / scanWidth;
+        scanEffect = (1.0 - normalizedDistance) * scanIntensity;
+        
+        // 使用平滑过渡
+        scanEffect = smoothstep(0.0, 1.0, scanEffect);
+      }
+      
+      // 应用扫描效果
+      if (scanEffect > 0.0) {
+        // 混合原始颜色和扫描颜色
+        o.rgb = mix(o.rgb, scanColor.rgb, scanEffect * scanColor.a);
+        // 增加亮度
+        o.rgb += scanColor.rgb * scanEffect * 0.5;
+      }
+      
+    #endif
+
+    o *= color;
+    ALPHA_TEST(o);
+    return o;
+  }
+}%

+ 11 - 0
assets/resources/shaders/scan-effects.effect.meta

@@ -0,0 +1,11 @@
+{
+  "ver": "1.7.1",
+  "importer": "effect",
+  "imported": true,
+  "uuid": "79c4e894-ff40-492e-8e15-af06cfe21b69",
+  "files": [
+    ".json"
+  ],
+  "subMetas": {},
+  "userData": {}
+}

+ 66 - 23
assets/scripts/Animations/GameStartMove.ts

@@ -1,4 +1,5 @@
 import { _decorator, Component, Node, Vec3, tween, Tween, UITransform, Camera } from 'cc';
+import EventBus, { GameEvents } from '../Core/EventBus';
 const { ccclass, property } = _decorator;
 
 /**
@@ -10,7 +11,6 @@ const { ccclass, property } = _decorator;
  *  2. exitBlockSelectionMode()  – complete animation for exiting block selection (camera up + UI slide down)
  * 
  * Also provides low-level methods for specific use cases:
- *  - moveDownInstant() – instantly move the camera down by a fixed offset
  *  - moveUpSmooth()   – move the camera back up with a smooth tween animation
  *  - slideUpFromBottom() – slide UI up from bottom
  *  - slideDibanDownAndHide() – slide UI down and hide
@@ -68,28 +68,7 @@ export class GameStartMove extends Component {
         }
     }
 
-    /**
-     * Instantly move the camera down to show the reference line at bottom.
-     * Requires referenceLineNode to be set.
-     */
-    public moveDownInstant () {
-        if (!this.cameraNode) return;
-        
-        const pos = this.cameraNode.position.clone();
-        
-        // Calculate movement based on reference line position
-        const moveDistance = this.calculateMoveDistanceToShowLine();
-        if (moveDistance > 0) {
-            pos.y -= moveDistance;
-            this.cameraNode.setPosition(pos);
-            console.log('GameStartMove.moveDownInstant 镜头下移,当前位置:', pos);
-        }
-        
-        // 镜头下移时显示Gray遮罩
-        if (this.grayNode) {
-            this.grayNode.active = true;
-        }
-    }
+
 
     /**
      * Smoothly move the camera back up using a tween to original position.
@@ -113,6 +92,64 @@ export class GameStartMove extends Component {
             .start();
     }
 
+    /**
+     * 重置镜头到原始位置(平滑动画)
+     * 用于返回主界面时确保镜头位置正常
+     * @param duration 动画时长,默认使用moveDuration
+     */
+    public resetCameraToOriginalPosition(duration?: number) {
+        if (!this.cameraNode) return;
+        
+        const animationDuration = duration !== undefined ? duration : this.moveDuration;
+        
+        console.log('GameStartMove.resetCameraToOriginalPosition 重置镜头到原始位置:', {
+            currentPos: this.cameraNode.position,
+            originalPos: this._originalPos,
+            duration: animationDuration
+        });
+        
+        // 停止任何正在运行的镜头动画
+        Tween.stopAllByTarget(this.cameraNode);
+        
+        // 平滑移动到原始位置
+        tween(this.cameraNode)
+            .to(animationDuration, { position: this._originalPos.clone() }, { easing: 'quadOut' })
+            .call(() => {
+                console.log('GameStartMove.resetCameraToOriginalPosition 镜头重置完成');
+                // 隐藏Gray遮罩
+                if (this.grayNode) {
+                    this.grayNode.active = false;
+                }
+            })
+            .start();
+    }
+
+    /**
+     * 立即重置镜头到原始位置(无动画)
+     * 用于需要立即重置镜头位置的场景
+     */
+    public resetCameraToOriginalPositionImmediate() {
+        if (!this.cameraNode) return;
+        
+        console.log('GameStartMove.resetCameraToOriginalPositionImmediate 立即重置镜头到原始位置:', {
+            currentPos: this.cameraNode.position,
+            originalPos: this._originalPos
+        });
+        
+        // 停止任何正在运行的镜头动画
+        Tween.stopAllByTarget(this.cameraNode);
+        
+        // 立即设置到原始位置
+        this.cameraNode.setPosition(this._originalPos.clone());
+        
+        // 隐藏Gray遮罩
+        if (this.grayNode) {
+            this.grayNode.active = false;
+        }
+        
+        console.log('GameStartMove.resetCameraToOriginalPositionImmediate 镜头立即重置完成');
+    }
+
     /**
      * Smoothly move the camera down to show the reference line at bottom.
      * Requires referenceLineNode to be set.
@@ -339,6 +376,9 @@ export class GameStartMove extends Component {
             .to(duration, { position: targetPos }, { easing: 'quadInOut' })
             .call(() => {
                 const animationEndTime = Date.now();
+                // 动画完成后发送进入备战状态事件,触发游戏暂停
+                console.log('[GameStartMove] diban上滑完成,发送游戏暂停事件');
+                EventBus.getInstance().emit(GameEvents.GAME_PAUSE);
             })
             .start();
             
@@ -394,6 +434,9 @@ export class GameStartMove extends Component {
                 }
                 // 动画完成后隐藏diban节点
                 this.dibanNode.active = false;
+                // 动画完成后发送进入战斗状态事件,恢复游戏
+                console.log('[GameStartMove] diban下滑完成,发送游戏恢复事件');
+                EventBus.getInstance().emit(GameEvents.GAME_RESUME);
             })
             .start();
             

+ 210 - 0
assets/scripts/Animations/PopUPAni.ts

@@ -0,0 +1,210 @@
+import { _decorator, Component, Node, Tween, tween, Vec3 } from 'cc';
+
+const { ccclass, property } = _decorator;
+
+/**
+ * 通用弹出动画控制器
+ * 负责管理UI面板的显示和隐藏动画
+ */
+@ccclass('PopUPAni')
+export class PopUPAni extends Component {
+    
+    // Gray节点引用
+    @property(Node) grayNode: Node = null;
+    
+    /**
+     * 显示面板动画
+     * 面板从小到大的缩放动画,从屏幕中央弹出
+     */
+    public showPanel(): Promise<void> {
+        return new Promise((resolve) => {
+            // 设置Gray节点为true
+            if (this.grayNode) {
+                this.grayNode.active = true;
+            }
+            
+            // 设置初始状态:位置居中,缩小到0
+            this.node.setPosition(0, 0, 0); // 先移动到屏幕正中间
+            this.node.setScale(Vec3.ZERO); // 然后设置缩放为0
+            
+            // 缩放动画:从0放大到1
+            tween(this.node)
+                .to(0.3, { scale: new Vec3(1, 1, 1) }, {
+                    easing: 'backOut' // 使用回弹缓动效果
+                })
+                .call(() => {
+                    resolve();
+                })
+                .start();
+        });
+    }
+    
+    /**
+     * 隐藏面板动画
+     * 面板从大到小的缩放动画,然后回到原来的画面外右侧位置并恢复原来大小
+     * 注意:不操作面板的active状态,面板始终保持active=true
+     */
+    public hidePanel(): Promise<void> {
+        return new Promise((resolve) => {
+            // 缩放动画:从1缩小到0
+            tween(this.node)
+                .to(0.2, { scale: Vec3.ZERO }, {
+                    easing: 'backIn' // 使用回弹缓动效果
+                })
+                .call(() => {
+                    // 设置Gray节点为false
+                    if (this.grayNode) {
+                        this.grayNode.active = false;
+                    }
+                    
+                    // 面板缩放到0后,回到原来的画面外右侧位置并恢复原来大小
+                    // 假设原来位置在画面外右侧,这里设置一个合理的右侧位置
+                    this.node.setPosition(1000, 0, 0); // 移动到画面外右侧
+                    this.node.setScale(Vec3.ONE); // 恢复原来大小
+                    // 不操作 this.node.active,保持面板原有的active状态
+                    resolve();
+                })
+                .start();
+        });
+    }
+    
+    /**
+     * 立即隐藏面板(无动画)
+     * 注意:不操作面板的active状态,面板始终保持active=true
+     */
+    public hidePanelImmediate(): void {
+        // 设置Gray节点为false
+        if (this.grayNode) {
+            this.grayNode.active = false;
+        }
+        
+        this.node.setScale(Vec3.ZERO);
+        // 立即移动到画面外右侧位置并恢复原来大小
+        this.node.setPosition(1000, 0, 0); // 移动到画面外右侧
+        this.node.setScale(Vec3.ONE); // 恢复原来大小
+        // 不操作 this.node.active,保持面板原有的active状态
+    }
+    
+    /**
+     * 立即显示面板(无动画)
+     * 注意:不操作面板的active状态,面板始终保持active=true
+     */
+    public showPanelImmediate(): void {
+        // 设置Gray节点为true
+        if (this.grayNode) {
+            this.grayNode.active = true;
+        }
+        
+        this.node.setPosition(0, 0, 0); // 先移动到屏幕正中间
+        this.node.setScale(Vec3.ONE); // 然后设置缩放为1
+        // 不操作 this.node.active,保持面板原有的active状态
+    }
+    
+    /**
+     * 图标升级成功动画
+     * 播放图标的放大缩小动画
+     * @param iconNode 图标节点
+     */
+    public playIconUpgradeAnimation(iconNode: Node): Promise<void> {
+        return new Promise((resolve) => {
+            if (!iconNode) {
+                resolve();
+                return;
+            }
+            
+            // 停止之前的动画,防止快速点击时动画冲突
+            Tween.stopAllByTarget(iconNode);
+            
+            // 重置到原始缩放状态,确保动画从正确的状态开始
+            iconNode.setScale(Vec3.ONE);
+            
+            // 保存原始缩放值
+            const originalScale = Vec3.ONE.clone();
+            
+            // 创建缩放动画:放大到1.5倍再立即缩小回原始大小
+            const scaleAnimation = tween(iconNode)
+                .to(0.25, { scale: new Vec3(originalScale.x * 1.5, originalScale.y * 1.5, originalScale.z) }, {
+                    easing: 'sineOut'
+                })
+                .to(0.25, { scale: originalScale }, {
+                    easing: 'sineIn'
+                });
+            
+            // 只播放缩放动画
+            scaleAnimation.call(() => resolve()).start();
+        });
+    }
+    
+    /**
+     * 提示图标动画
+     * 播放缩小到0.7倍再放大到原来大小的重复动画,直到条件不满足为止
+     * @param iconNode 图标节点
+     * @param checkCondition 检查条件的回调函数,返回false时停止动画
+     */
+    public playIconTipAnimation(iconNode: Node, checkCondition?: () => boolean): void {
+        if (!iconNode) return;
+        
+        // 停止之前的动画
+        Tween.stopAllByTarget(iconNode);
+        
+        // 保存原始缩放值
+        const originalScale = iconNode.scale.clone();
+        
+        // 如果没有提供检查条件,使用默认的无限重复动画
+        if (!checkCondition) {
+            const scaleAnimation = tween(iconNode)
+                .to(0.5, { scale: new Vec3(originalScale.x * 0.7, originalScale.y * 0.7, originalScale.z) }, {
+                    easing: 'sineInOut'
+                })
+                .to(0.5, { scale: originalScale }, {
+                    easing: 'sineInOut'
+                })
+                .repeatForever(); // 无限重复
+            
+            scaleAnimation.start();
+            return;
+        }
+        
+        // 带条件检查的动画循环
+        const playAnimationCycle = () => {
+            // 在每次动画循环开始前检查条件
+            if (!checkCondition()) {
+                // 条件不满足,停止动画并恢复原始缩放
+                this.stopIconTipAnimation(iconNode);
+                return;
+            }
+            
+            // 创建一次完整的缩放动画循环
+            const scaleAnimation = tween(iconNode)
+                .to(0.5, { scale: new Vec3(originalScale.x * 0.7, originalScale.y * 0.7, originalScale.z) }, {
+                    easing: 'sineInOut'
+                })
+                .to(0.5, { scale: originalScale }, {
+                    easing: 'sineInOut'
+                })
+                .call(() => {
+                    // 一次循环完成后,递归调用下一次循环
+                    playAnimationCycle();
+                });
+            
+            scaleAnimation.start();
+        };
+        
+        // 开始第一次动画循环
+        playAnimationCycle();
+    }
+    
+    /**
+     * 停止提示图标动画
+     * @param iconNode 图标节点
+     */
+    public stopIconTipAnimation(iconNode: Node): void {
+        if (!iconNode) return;
+        
+        // 停止所有动画
+        Tween.stopAllByTarget(iconNode);
+        
+        // 恢复原始缩放
+        iconNode.setScale(Vec3.ONE);
+    }
+}

+ 9 - 0
assets/scripts/Animations/PopUPAni.ts.meta

@@ -0,0 +1,9 @@
+{
+  "ver": "4.0.24",
+  "importer": "typescript",
+  "imported": true,
+  "uuid": "c2a13cac-2ec0-4a44-8d62-66bf0a97153d",
+  "files": [],
+  "subMetas": {},
+  "userData": {}
+}

+ 4 - 0
assets/scripts/CombatSystem/GamePause.ts

@@ -52,6 +52,9 @@ export class GamePause extends Component {
         
         // 监听游戏结束状态检查事件
         eventBus.on(GameEvents.GAME_CHECK_OVER, this.onGameCheckOverEvent, this);
+        
+        // 监听游戏结束事件来重置游戏模式状态
+        eventBus.on('GAME_END', this.onGameEndEvent, this);
     }
     
     /**
@@ -301,6 +304,7 @@ export class GamePause extends Component {
         eventBus.off(GameEvents.GAME_PAUSE, this.onGamePauseEvent, this);
         eventBus.off(GameEvents.BALL_FIRE_BULLET, this.onBallFireBulletEvent, this);
         eventBus.off(GameEvents.GAME_CHECK_OVER, this.onGameCheckOverEvent, this);
+        eventBus.off('GAME_END', this.onGameEndEvent, this);
         
         GamePause._instance = null;
     }

+ 9 - 0
assets/scripts/CombatSystem/MenuSystem.meta

@@ -0,0 +1,9 @@
+{
+  "ver": "1.2.0",
+  "importer": "directory",
+  "imported": true,
+  "uuid": "64ad7d29-b6a2-4f28-a83a-1d63701f71ab",
+  "files": [],
+  "subMetas": {},
+  "userData": {}
+}

+ 237 - 0
assets/scripts/CombatSystem/MenuSystem/MenuAni.ts

@@ -0,0 +1,237 @@
+import { _decorator, Component, Node, tween, Tween, Vec3, UIOpacity } from 'cc';
+const { ccclass, property } = _decorator;
+
+/**
+ * MenuAni
+ * 菜单动画控制器
+ * 提供菜单相关的动画效果
+ */
+@ccclass('MenuAni')
+export class MenuAni extends Component {
+    /** 动画持续时间 */
+    @property({ tooltip: '动画持续时间(秒)' })
+    public animationDuration: number = 0.3;
+    
+    /** 淡入淡出动画的目标节点 */
+    @property({ type: Node, tooltip: '执行淡入淡出动画的目标节点' })
+    public fadeTarget: Node = null;
+    
+    /** 缩放动画的目标节点 */
+    @property({ type: Node, tooltip: '执行缩放动画的目标节点' })
+    public scaleTarget: Node = null;
+    
+    private _originalScale: Vec3 = new Vec3();
+    
+    onLoad() {
+        // 保存原始缩放值
+        if (this.scaleTarget) {
+            this._originalScale.set(this.scaleTarget.scale);
+        }
+    }
+    
+    /**
+     * 淡入动画
+     * @param target 目标节点,如果不指定则使用fadeTarget
+     * @param duration 动画时长,如果不指定则使用animationDuration
+     */
+    public fadeIn(target?: Node, duration?: number): Promise<void> {
+        const animTarget = target || this.fadeTarget;
+        const animDuration = duration !== undefined ? duration : this.animationDuration;
+        
+        if (!animTarget) {
+            console.warn('[MenuAni] fadeIn: 未指定目标节点');
+            return Promise.resolve();
+        }
+        
+        return new Promise<void>((resolve) => {
+            // 获取UIOpacity组件
+            let uiOpacity = animTarget.getComponent(UIOpacity);
+            if (!uiOpacity) {
+                console.warn(`[MenuAni] fadeIn: 节点 ${animTarget.name} 缺少UIOpacity组件,请手动添加`);
+                return;
+            }
+            
+            // 停止现有动画
+            Tween.stopAllByTarget(uiOpacity);
+            
+            // 设置初始透明度
+            uiOpacity.opacity = 0;
+            
+            // 执行淡入动画
+            tween(uiOpacity)
+                .to(animDuration, { opacity: 255 }, { easing: 'quadOut' })
+                .call(() => {
+                    resolve();
+                })
+                .start();
+        });
+    }
+    
+    /**
+     * 淡出动画
+     * @param target 目标节点,如果不指定则使用fadeTarget
+     * @param duration 动画时长,如果不指定则使用animationDuration
+     */
+    public fadeOut(target?: Node, duration?: number): Promise<void> {
+        const animTarget = target || this.fadeTarget;
+        const animDuration = duration !== undefined ? duration : this.animationDuration;
+        
+        if (!animTarget) {
+            console.warn('[MenuAni] fadeOut: 未指定目标节点');
+            return Promise.resolve();
+        }
+        
+        return new Promise<void>((resolve) => {
+            // 获取UIOpacity组件
+            let uiOpacity = animTarget.getComponent(UIOpacity);
+            if (!uiOpacity) {
+                console.warn(`[MenuAni] fadeOut: 节点 ${animTarget.name} 缺少UIOpacity组件,请手动添加`);
+                return;
+            }
+            
+            // 停止现有动画
+            Tween.stopAllByTarget(uiOpacity);
+            
+            // 执行淡出动画
+            tween(uiOpacity)
+                .to(animDuration, { opacity: 0 }, { easing: 'quadIn' })
+                .call(() => {
+                    resolve();
+                })
+                .start();
+        });
+    }
+    
+    /**
+     * 缩放弹出动画
+     * @param target 目标节点,如果不指定则使用scaleTarget
+     * @param duration 动画时长,如果不指定则使用animationDuration
+     */
+    public scalePopIn(target?: Node, duration?: number): Promise<void> {
+        const animTarget = target || this.scaleTarget;
+        const animDuration = duration !== undefined ? duration : this.animationDuration;
+        
+        if (!animTarget) {
+            console.warn('[MenuAni] scalePopIn: 未指定目标节点');
+            return Promise.resolve();
+        }
+        
+        return new Promise<void>((resolve) => {
+            // 停止现有动画
+            Tween.stopAllByTarget(animTarget);
+            
+            // 设置初始缩放
+            animTarget.setScale(0, 0, 1);
+            
+            // 执行弹出动画
+            tween(animTarget)
+                .to(animDuration, { scale: this._originalScale }, { easing: 'backOut' })
+                .call(() => {
+                    resolve();
+                })
+                .start();
+        });
+    }
+    
+    /**
+     * 缩放收缩动画
+     * @param target 目标节点,如果不指定则使用scaleTarget
+     * @param duration 动画时长,如果不指定则使用animationDuration
+     */
+    public scalePopOut(target?: Node, duration?: number): Promise<void> {
+        const animTarget = target || this.scaleTarget;
+        const animDuration = duration !== undefined ? duration : this.animationDuration;
+        
+        if (!animTarget) {
+            console.warn('[MenuAni] scalePopOut: 未指定目标节点');
+            return Promise.resolve();
+        }
+        
+        return new Promise<void>((resolve) => {
+            // 停止现有动画
+            Tween.stopAllByTarget(animTarget);
+            
+            // 执行收缩动画
+            tween(animTarget)
+                .to(animDuration, { scale: new Vec3(0, 0, 1) }, { easing: 'backIn' })
+                .call(() => {
+                    resolve();
+                })
+                .start();
+        });
+    }
+    
+    /**
+     * 组合动画:淡入 + 缩放弹出
+     * @param fadeTarget 淡入动画目标节点
+     * @param scaleTarget 缩放动画目标节点
+     * @param duration 动画时长
+     */
+    public async fadeInWithScale(fadeTarget?: Node, scaleTarget?: Node, duration?: number): Promise<void> {
+        const promises: Promise<void>[] = [];
+        
+        if (fadeTarget || this.fadeTarget) {
+            promises.push(this.fadeIn(fadeTarget, duration));
+        }
+        
+        if (scaleTarget || this.scaleTarget) {
+            promises.push(this.scalePopIn(scaleTarget, duration));
+        }
+        
+        await Promise.all(promises);
+    }
+    
+    /**
+     * 组合动画:淡出 + 缩放收缩
+     * @param fadeTarget 淡出动画目标节点
+     * @param scaleTarget 缩放动画目标节点
+     * @param duration 动画时长
+     */
+    public async fadeOutWithScale(fadeTarget?: Node, scaleTarget?: Node, duration?: number): Promise<void> {
+        const promises: Promise<void>[] = [];
+        
+        if (fadeTarget || this.fadeTarget) {
+            promises.push(this.fadeOut(fadeTarget, duration));
+        }
+        
+        if (scaleTarget || this.scaleTarget) {
+            promises.push(this.scalePopOut(scaleTarget, duration));
+        }
+        
+        await Promise.all(promises);
+    }
+    
+    /**
+     * 停止所有动画
+     */
+    public stopAllAnimations() {
+        if (this.fadeTarget) {
+            const uiOpacity = this.fadeTarget.getComponent(UIOpacity);
+            if (uiOpacity) {
+                Tween.stopAllByTarget(uiOpacity);
+            }
+        }
+        
+        if (this.scaleTarget) {
+            Tween.stopAllByTarget(this.scaleTarget);
+        }
+    }
+    
+    /**
+     * 重置所有目标节点到初始状态
+     */
+    public resetToInitialState() {
+        this.stopAllAnimations();
+        
+        if (this.fadeTarget) {
+            let uiOpacity = this.fadeTarget.getComponent(UIOpacity);
+            if (uiOpacity) {
+                uiOpacity.opacity = 255;
+            }
+        }
+        
+        if (this.scaleTarget) {
+            this.scaleTarget.setScale(this._originalScale);
+        }
+    }
+}

+ 9 - 0
assets/scripts/CombatSystem/MenuSystem/MenuAni.ts.meta

@@ -0,0 +1,9 @@
+{
+  "ver": "4.0.24",
+  "importer": "typescript",
+  "imported": true,
+  "uuid": "54aaa077-21b3-424d-90bf-776dbe3b5856",
+  "files": [],
+  "subMetas": {},
+  "userData": {}
+}

+ 292 - 0
assets/scripts/CombatSystem/MenuSystem/MenuController.ts

@@ -0,0 +1,292 @@
+import { _decorator, Component, Node, Button, find } from 'cc';
+import { PopUPAni } from '../../Animations/PopUPAni';
+import EventBus, { GameEvents } from '../../Core/EventBus';
+import { GameManager, AppState } from '../../LevelSystem/GameManager';
+import { GameStartMove } from '../../Animations/GameStartMove';
+import { GamePause } from '../GamePause';
+import { SoundController } from './SoundController';
+
+const { ccclass, property } = _decorator;
+
+/**
+ * 菜单系统控制器
+ * 负责管理菜单UI的显示和隐藏
+ */
+@ccclass('MenuController')
+export class MenuController extends Component {
+    // UI节点引用
+    @property(Node) menuUI: Node = null;           // Canvas/MenuUI
+    @property(Button) menuButton: Button = null;   // Canvas/MenuButton
+    @property(Button) closeButton: Button = null;  // Canvas/MenuUI中的关闭按钮
+    @property(Button) backButton: Button = null;   // Canvas/MenuUI/Buttons/BackButton 退出游戏按钮
+    @property(Button) continueButton: Button = null; // Canvas/MenuUI/Buttons/ContinueButton 继续游戏按钮
+    
+    // 动画控制器
+    @property(PopUPAni) popupAni: PopUPAni = null; // Canvas/MenuUI上的PopUPAni组件
+    
+    // 音频控制器
+    @property(SoundController) soundController: SoundController = null; // Canvas/MenuUI上的SoundController组件
+    
+    // 游戏管理器引用
+    @property(GameManager) gameManager: GameManager = null; // GameManager组件引用
+    
+    // 菜单状态
+    private isMenuOpen: boolean = false;
+    
+    // GameStartMove组件引用,用于重置镜头位置
+    private gameStartMoveComponent: GameStartMove = null;
+    
+    onLoad() {
+        this.bindEvents();
+        // 初始化时隐藏菜单 - 使用PopUPAni的场景外位置隐藏方法
+        if (this.popupAni) {
+            this.popupAni.hidePanelImmediate();
+        }
+    }
+    
+    /**
+     * 绑定事件
+     */
+    private bindEvents() {
+        // 绑定菜单按钮点击事件
+        if (this.menuButton) {
+            this.menuButton.node.on(Button.EventType.CLICK, this.onMenuButtonClick, this);
+        }
+        
+        // 绑定关闭按钮点击事件
+        if (this.closeButton) {
+            this.closeButton.node.on(Button.EventType.CLICK, this.onCloseButtonClick, this);
+        }
+        
+        // 绑定退出游戏按钮点击事件
+        if (this.backButton) {
+            this.backButton.node.on(Button.EventType.CLICK, this.onBackButtonClick, this);
+        }
+        
+        // 绑定继续游戏按钮点击事件
+        if (this.continueButton) {
+            this.continueButton.node.on(Button.EventType.CLICK, this.onContinueButtonClick, this);
+        }
+    }
+    
+    /**
+     * 菜单按钮点击事件
+     */
+    private async onMenuButtonClick() {
+        if (this.isMenuOpen) {
+            await this.closeMenu();
+        } else {
+            await this.openMenu();
+        }
+    }
+    
+    /**
+     * 关闭按钮点击事件
+     */
+    private async onCloseButtonClick() {
+        await this.closeMenu();
+    }
+    
+    /**
+     * 继续游戏按钮点击事件
+     */
+    private async onContinueButtonClick() {
+        console.log('[MenuController] 继续游戏按钮被点击');
+        
+        // 检查GameManager组件引用
+        if (!this.gameManager) {
+            console.error('[MenuController] GameManager组件未通过装饰器挂载,请在Inspector中拖拽GameManager组件');
+            await this.closeMenu();
+            return;
+        }
+        
+        const currentAppState = this.gameManager.getCurrentAppState();
+        console.log(`[MenuController] 当前应用状态: ${currentAppState}`);
+        
+        if (currentAppState === AppState.IN_GAME) {
+            // 在游戏中点击继续,关闭菜单并恢复游戏
+            console.log('[MenuController] 游戏中点击继续,关闭菜单并恢复游戏');
+            await this.closeMenu();
+        } else {
+            // 游戏外点击继续,直接关闭菜单
+            console.log('[MenuController] 游戏外点击继续,直接关闭菜单');
+            await this.closeMenu();
+        }
+    }
+    
+    /**
+     * 退出游戏按钮点击事件
+     */
+    private async onBackButtonClick() {
+        console.log('[MenuController] 退出游戏按钮被点击');
+        
+        // 检查GameManager组件引用
+        if (!this.gameManager) {
+            console.error('[MenuController] GameManager组件未通过装饰器挂载,请在Inspector中拖拽GameManager组件');
+            return;
+        }
+        
+        const currentAppState = this.gameManager.getCurrentAppState();
+        console.log(`[MenuController] 当前应用状态: ${currentAppState}`);
+        
+        if (currentAppState === AppState.IN_GAME) {
+            // 在游戏中退出,先重置镜头位置,然后视为游戏失败
+            console.log('[MenuController] 游戏中退出,先重置镜头位置');
+            this.resetCameraPosition();
+            
+            console.log('[MenuController] 触发游戏失败事件');
+            const eventBus = EventBus.getInstance();
+            eventBus.emit(GameEvents.GAME_DEFEAT);
+            
+            // 等待游戏失败处理完成后关闭菜单
+            setTimeout(async () => {
+                await this.closeMenu();
+                console.log('[MenuController] 游戏失败处理完成,菜单已关闭');
+            }, 100);
+        } else {
+            // 游戏外退出,关闭菜单并返回主界面(不需要重置镜头)
+            console.log('[MenuController] 游戏外退出,关闭菜单并返回主界面');
+            await this.closeMenu();
+            
+            // 触发返回主界面事件
+            const eventBus = EventBus.getInstance();
+            eventBus.emit(GameEvents.RETURN_TO_MAIN_MENU);
+            console.log('[MenuController] 已触发返回主界面事件');
+        }
+    }
+    
+    /**
+     * 重置镜头位置到原始位置
+     * 确保从游戏中退出时镜头位置正常
+     */
+    private resetCameraPosition() {
+        // 如果还没有获取GameStartMove组件,尝试获取
+        if (!this.gameStartMoveComponent) {
+            const cameraNode = find('Canvas/Main Camera');
+            if (cameraNode) {
+                this.gameStartMoveComponent = cameraNode.getComponent(GameStartMove);
+            }
+        }
+        
+        // 如果成功获取到组件,重置镜头位置
+        if (this.gameStartMoveComponent) {
+            console.log('[MenuController] 重置镜头位置到原始位置');
+            this.gameStartMoveComponent.resetCameraToOriginalPosition(0.3);
+        } else {
+            console.warn('[MenuController] 未找到GameStartMove组件,无法重置镜头位置');
+        }
+    }
+    
+    /**
+     * 打开菜单
+     */
+    public async openMenu(): Promise<void> {
+        if (this.isMenuOpen) return;
+        
+        this.isMenuOpen = true;
+        
+        // 检查是否在游戏中,如果是则暂停游戏
+        if (this.gameManager && this.gameManager.getCurrentAppState() === AppState.IN_GAME) {
+            console.log('[MenuController] 游戏中打开菜单,暂停游戏');
+            const gamePause = GamePause.getInstance();
+            gamePause.pauseGame();
+        }
+        
+        // 使用动画显示菜单面板
+        if (this.popupAni) {
+            await this.popupAni.showPanel();
+        }
+        // 注意:不再有fallback逻辑设置active,菜单面板始终保持active=true
+        
+        console.log('菜单已打开');
+    }
+    
+    /**
+     * 关闭菜单
+     */
+    public async closeMenu(): Promise<void> {
+        if (!this.isMenuOpen) return;
+        
+        this.isMenuOpen = false;
+        
+        // 使用动画隐藏菜单面板
+        if (this.popupAni) {
+            await this.popupAni.hidePanel();
+        }
+        // 注意:不再有fallback逻辑设置active,菜单面板始终保持active=true
+        
+        // 检查是否在游戏中,如果是则恢复游戏
+        if (this.gameManager && this.gameManager.getCurrentAppState() === AppState.IN_GAME) {
+            console.log('[MenuController] 游戏中关闭菜单,恢复游戏');
+            const gamePause = GamePause.getInstance();
+            gamePause.resumeGame();
+        }
+        
+        console.log('菜单已关闭');
+    }
+    
+    /**
+     * 切换菜单状态
+     */
+    public async toggleMenu(): Promise<void> {
+        if (this.isMenuOpen) {
+            await this.closeMenu();
+        } else {
+            await this.openMenu();
+        }
+    }
+    
+    /**
+     * 获取菜单状态
+     */
+    public getMenuState(): boolean {
+        return this.isMenuOpen;
+    }
+    
+    /**
+     * 立即关闭菜单(无动画)
+     */
+    public closeMenuImmediate(): void {
+        this.isMenuOpen = false;
+        
+        if (this.popupAni) {
+            this.popupAni.hidePanelImmediate();
+        }
+        // 注意:不再有fallback逻辑设置active,菜单面板始终保持active=true
+        
+        console.log('菜单已立即关闭');
+    }
+    
+    /**
+     * 立即打开菜单(无动画)
+     */
+    public openMenuImmediate(): void {
+        this.isMenuOpen = true;
+        
+        if (this.popupAni) {
+            this.popupAni.showPanelImmediate();
+        }
+        // 注意:不再有fallback逻辑设置active,菜单面板始终保持active=true
+        
+        console.log('菜单已立即打开');
+    }
+    
+    onDestroy() {
+        // 解绑事件
+        if (this.menuButton) {
+            this.menuButton.node.off(Button.EventType.CLICK, this.onMenuButtonClick, this);
+        }
+        
+        if (this.closeButton) {
+            this.closeButton.node.off(Button.EventType.CLICK, this.onCloseButtonClick, this);
+        }
+        
+        if (this.backButton) {
+            this.backButton.node.off(Button.EventType.CLICK, this.onBackButtonClick, this);
+        }
+        
+        if (this.continueButton) {
+            this.continueButton.node.off(Button.EventType.CLICK, this.onContinueButtonClick, this);
+        }
+    }
+}

+ 9 - 0
assets/scripts/CombatSystem/MenuSystem/MenuController.ts.meta

@@ -0,0 +1,9 @@
+{
+  "ver": "4.0.24",
+  "importer": "typescript",
+  "imported": true,
+  "uuid": "03e9cf3b-7508-46ec-a613-ce549cd12e85",
+  "files": [],
+  "subMetas": {},
+  "userData": {}
+}

+ 490 - 0
assets/scripts/CombatSystem/MenuSystem/SoundController.ts

@@ -0,0 +1,490 @@
+import { _decorator, Component, Node, Slider, Button, Sprite, SpriteFrame, ProgressBar, resources, tween, Vec3 } from 'cc';
+import { AudioManager } from '../../Core/AudioManager';
+import { SaveDataManager } from '../../LevelSystem/SaveDataManager';
+const { ccclass, property } = _decorator;
+
+/**
+ * 音频控制器
+ * 管理音效和音乐的音量控制以及开关状态
+ */
+@ccclass('SoundController')
+export class SoundController extends Component {
+    // 音效控制组件
+    @property({ type: Slider, tooltip: '音效音量滑动条' })
+    public soundEffectSlider: Slider = null;
+    
+    @property({ type: Button, tooltip: '音效开关按钮' })
+    public soundEffectCheckbox: Button = null;
+    
+    @property({ type: Node, tooltip: '音效勾选标记节点' })
+    public soundEffectCheck: Node = null;
+    
+    @property({ type: ProgressBar, tooltip: '音效进度条' })
+    public soundEffectProgressBar: ProgressBar = null;
+    
+    // 音乐控制组件
+    @property({ type: Slider, tooltip: '音乐音量滑动条' })
+    public musicSlider: Slider = null;
+    
+    @property({ type: Button, tooltip: '音乐开关按钮' })
+    public musicCheckbox: Button = null;
+    
+    @property({ type: Node, tooltip: '音乐勾选标记节点' })
+    public musicCheck: Node = null;
+    
+    @property({ type: ProgressBar, tooltip: '音乐进度条' })
+    public musicProgressBar: ProgressBar = null;
+    
+    // 震动控制组件
+    @property({ type: Button, tooltip: '震动开关按钮' })
+    public vibrationOnButton: Button = null;
+    
+    @property({ type: Button, tooltip: '震动关闭按钮' })
+    public vibrationOffButton: Button = null;
+    
+    @property({ type: Node, tooltip: '震动滑动节点' })
+    public vibrationSlideNode: Node = null;
+    
+    // 音量状态
+    private soundEffectEnabled: boolean = true;
+    private musicEnabled: boolean = true;
+    private savedSoundEffectVolume: number = 0.5;
+    private savedMusicVolume: number = 0.5;
+    private vibrationEnabled: boolean = true;
+    
+    onLoad() {
+        this.initializeSliders();
+        this.bindEvents();
+        this.loadSettings();
+    }
+    
+    /**
+     * 初始化滑动条
+     */
+    private initializeSliders() {
+        if (this.soundEffectSlider) {
+            this.soundEffectSlider.progress = this.savedSoundEffectVolume;
+            this.updateProgressBar(this.soundEffectProgressBar, this.soundEffectSlider.progress);
+        }
+        
+        if (this.musicSlider) {
+            this.musicSlider.progress = this.savedMusicVolume;
+            this.updateProgressBar(this.musicProgressBar, this.musicSlider.progress);
+        }
+    }
+    
+    /**
+     * 绑定事件
+     */
+    private bindEvents() {
+        // 音效滑动条事件
+        if (this.soundEffectSlider) {
+            this.soundEffectSlider.node.on('slide', this.onSoundEffectSliderChange, this);
+        }
+        
+        // 音乐滑动条事件
+        if (this.musicSlider) {
+            this.musicSlider.node.on('slide', this.onMusicSliderChange, this);
+        }
+        
+        // 音效开关按钮事件
+        if (this.soundEffectCheckbox) {
+            this.soundEffectCheckbox.node.on(Button.EventType.CLICK, this.onSoundEffectCheckboxClick, this);
+        }
+        
+        // 音乐开关按钮事件
+        if (this.musicCheckbox) {
+            this.musicCheckbox.node.on(Button.EventType.CLICK, this.onMusicCheckboxClick, this);
+        }
+        
+        // 震动开关按钮事件
+        if (this.vibrationOnButton) {
+            this.vibrationOnButton.node.on(Button.EventType.CLICK, this.onVibrationOnClick, this);
+        }
+        
+        if (this.vibrationOffButton) {
+            this.vibrationOffButton.node.on(Button.EventType.CLICK, this.onVibrationOffClick, this);
+        }
+    }
+    
+    /**
+     * 音效滑动条变化事件
+     */
+    private onSoundEffectSliderChange(slider: Slider) {
+        if (this.soundEffectEnabled) {
+            this.savedSoundEffectVolume = slider.progress;
+            this.updateProgressBar(this.soundEffectProgressBar, slider.progress);
+            // TODO: 应用音效音量到音频系统
+            this.applySoundEffectVolume(slider.progress);
+        }
+    }
+    
+    /**
+     * 音乐滑动条变化事件
+     */
+    private onMusicSliderChange(slider: Slider) {
+        if (this.musicEnabled) {
+            this.savedMusicVolume = slider.progress;
+            this.updateProgressBar(this.musicProgressBar, slider.progress);
+            // TODO: 应用音乐音量到音频系统
+            this.applyMusicVolume(slider.progress);
+        }
+    }
+    
+    /**
+     * 音效开关按钮点击事件
+     */
+    private onSoundEffectCheckboxClick() {
+        this.soundEffectEnabled = !this.soundEffectEnabled;
+        
+        if (this.soundEffectEnabled) {
+            // 开启音效:恢复之前保存的音量
+            this.soundEffectSlider.progress = this.savedSoundEffectVolume;
+            this.updateProgressBar(this.soundEffectProgressBar, this.savedSoundEffectVolume);
+            this.applySoundEffectVolume(this.savedSoundEffectVolume);
+        } else {
+            // 关闭音效:保存当前音量并设置为0
+            this.savedSoundEffectVolume = this.soundEffectSlider.progress;
+            this.soundEffectSlider.progress = 0;
+            this.updateProgressBar(this.soundEffectProgressBar, 0);
+            this.applySoundEffectVolume(0);
+        }
+        
+        // 更新勾选标记显示
+        if (this.soundEffectCheck) {
+            this.soundEffectCheck.active = this.soundEffectEnabled;
+        }
+        
+        this.saveSettings();
+    }
+    
+    /**
+     * 音乐开关按钮点击事件
+     */
+    private onMusicCheckboxClick() {
+        this.musicEnabled = !this.musicEnabled;
+        
+        if (this.musicEnabled) {
+            // 开启音乐:恢复之前保存的音量
+            this.musicSlider.progress = this.savedMusicVolume;
+            this.updateProgressBar(this.musicProgressBar, this.savedMusicVolume);
+            this.applyMusicVolume(this.savedMusicVolume);
+        } else {
+            // 关闭音乐:保存当前音量并设置为0
+            this.savedMusicVolume = this.musicSlider.progress;
+            this.musicSlider.progress = 0;
+            this.updateProgressBar(this.musicProgressBar, 0);
+            this.applyMusicVolume(0);
+        }
+        
+        // 更新勾选标记显示
+        if (this.musicCheck) {
+            this.musicCheck.active = this.musicEnabled;
+        }
+        
+        this.saveSettings();
+    }
+    
+    /**
+     * 震动开启按钮点击事件
+     */
+    private onVibrationOnClick() {
+        this.vibrationEnabled = true;
+        this.updateVibrationSlideButton();
+        this.saveVibrationSetting();
+        
+        // 开启音效和音乐
+        this.enableSoundEffectAndMusic();
+    }
+    
+    /**
+     * 震动关闭按钮点击事件
+     */
+    private onVibrationOffClick() {
+        this.vibrationEnabled = false;
+        this.updateVibrationSlideButton();
+        this.saveVibrationSetting();
+        
+        // 关闭音效和音乐
+        this.disableSoundEffectAndMusic();
+    }
+    
+    /**
+     * 更新震动滑动按钮位置
+     */
+    private updateVibrationSlideButton() {
+        if (!this.vibrationSlideNode || !this.vibrationOnButton || !this.vibrationOffButton) {
+            return;
+        }
+        
+        // 获取目标按钮的位置
+        const targetButton = this.vibrationEnabled ? this.vibrationOnButton : this.vibrationOffButton;
+        const targetPosition = targetButton.node.position.clone();
+        
+        // 使用缓动动画移动滑动节点
+        tween(this.vibrationSlideNode)
+            .to(0.3, { position: targetPosition }, { easing: 'sineInOut' })
+            .start();
+    }
+    
+    /**
+     * 保存震动设置
+     */
+    private saveVibrationSetting() {
+        const saveDataManager = SaveDataManager.getInstance();
+        if (saveDataManager) {
+            saveDataManager.updateSetting('vibrationEnabled', this.vibrationEnabled);
+        }
+    }
+    
+    /**
+     * 立即更新震动滑动按钮位置(不使用动画)
+     */
+    private updateVibrationSlideButtonImmediate() {
+        if (!this.vibrationSlideNode || !this.vibrationOnButton || !this.vibrationOffButton) {
+            return;
+        }
+        
+        // 获取目标按钮的位置
+        const targetButton = this.vibrationEnabled ? this.vibrationOnButton : this.vibrationOffButton;
+        const targetPosition = targetButton.node.position.clone();
+        
+        // 直接设置位置,不使用动画
+        this.vibrationSlideNode.position = targetPosition;
+    }
+    
+    /**
+     * 更新进度条显示
+     */
+    private updateProgressBar(progressBar: ProgressBar, progress: number) {
+        if (!progressBar) return;
+        
+        // 直接设置ProgressBar的进度值
+        // ProgressBar会自动处理背景和前景的显示,保持黑色背景不变
+        progressBar.progress = progress;
+    }
+    
+    /**
+     * 应用音效音量到音频系统
+     */
+    private applySoundEffectVolume(volume: number) {
+        const audioManager = AudioManager.getInstance();
+        if (audioManager) {
+            audioManager.setSoundEffectVolume(volume);
+        }
+        //console.log(`[SoundController] 设置音效音量: ${volume}`);
+    }
+    
+    /**
+     * 应用音乐音量到音频系统
+     */
+    private applyMusicVolume(volume: number) {
+        const audioManager = AudioManager.getInstance();
+        if (audioManager) {
+            audioManager.setMusicVolume(volume);
+        }
+        //console.log(`[SoundController] 设置音乐音量: ${volume}`);
+    }
+    
+    /**
+     * 保存设置到本地存储
+     */
+    private saveSettings() {
+        const settings = {
+            soundEffectEnabled: this.soundEffectEnabled,
+            musicEnabled: this.musicEnabled,
+            soundEffectVolume: this.savedSoundEffectVolume,
+            musicVolume: this.savedMusicVolume
+        };
+        
+        localStorage.setItem('audioSettings', JSON.stringify(settings));
+    }
+    
+    /**
+     * 从本地存储加载设置
+     */
+    private loadSettings() {
+        const savedSettings = localStorage.getItem('audioSettings');
+        if (savedSettings) {
+            try {
+                const settings = JSON.parse(savedSettings);
+                this.soundEffectEnabled = settings.soundEffectEnabled ?? true;
+                this.musicEnabled = settings.musicEnabled ?? true;
+                this.savedSoundEffectVolume = settings.soundEffectVolume ?? 0.8;
+                this.savedMusicVolume = settings.musicVolume ?? 0.8;
+                
+                // 更新UI显示
+                this.updateUI();
+            } catch (e) {
+                console.warn('[SoundController] 加载音频设置失败:', e);
+            }
+        }
+        
+        // 从SaveDataManager加载震动设置
+        const saveDataManager = SaveDataManager.getInstance();
+        if (saveDataManager) {
+            this.vibrationEnabled = saveDataManager.getSetting('vibrationEnabled');
+        }
+    }
+    
+    /**
+     * 更新UI显示
+     */
+    private updateUI() {
+        // 更新勾选标记
+        if (this.soundEffectCheck) {
+            this.soundEffectCheck.active = this.soundEffectEnabled;
+        }
+        if (this.musicCheck) {
+            this.musicCheck.active = this.musicEnabled;
+        }
+        
+        // 更新滑动条和进度条
+        if (this.soundEffectSlider) {
+            this.soundEffectSlider.progress = this.soundEffectEnabled ? this.savedSoundEffectVolume : 0;
+            this.updateProgressBar(this.soundEffectProgressBar, this.soundEffectSlider.progress);
+        }
+        
+        if (this.musicSlider) {
+            this.musicSlider.progress = this.musicEnabled ? this.savedMusicVolume : 0;
+            this.updateProgressBar(this.musicProgressBar, this.musicSlider.progress);
+        }
+        
+        // 更新震动滑动按钮位置(不使用动画,直接设置位置)
+        this.updateVibrationSlideButtonImmediate();
+    }
+    
+    /**
+     * 获取当前音效音量
+     */
+    public getSoundEffectVolume(): number {
+        return this.soundEffectEnabled ? this.savedSoundEffectVolume : 0;
+    }
+    
+    /**
+     * 获取当前音乐音量
+     */
+    public getMusicVolume(): number {
+        return this.musicEnabled ? this.savedMusicVolume : 0;
+    }
+    
+    /**
+     * 设置音效音量
+     */
+    public setSoundEffectVolume(volume: number) {
+        this.savedSoundEffectVolume = Math.max(0, Math.min(1, volume));
+        if (this.soundEffectEnabled && this.soundEffectSlider) {
+            this.soundEffectSlider.progress = this.savedSoundEffectVolume;
+            this.updateProgressBar(this.soundEffectProgressBar, this.savedSoundEffectVolume);
+            this.applySoundEffectVolume(this.savedSoundEffectVolume);
+        }
+        this.saveSettings();
+    }
+    
+    /**
+     * 设置音乐音量
+     */
+    public setMusicVolume(volume: number) {
+        this.savedMusicVolume = Math.max(0, Math.min(1, volume));
+        if (this.musicEnabled && this.musicSlider) {
+            this.musicSlider.progress = this.savedMusicVolume;
+            this.updateProgressBar(this.musicProgressBar, this.savedMusicVolume);
+            this.applyMusicVolume(this.savedMusicVolume);
+        }
+        this.saveSettings();
+    }
+    
+    /**
+     * 获取当前震动状态
+     */
+    public getVibrationEnabled(): boolean {
+        return this.vibrationEnabled;
+    }
+    
+    /**
+     * 设置震动状态
+     */
+    public setVibrationEnabled(enabled: boolean) {
+        this.vibrationEnabled = enabled;
+        this.updateVibrationSlideButton();
+        this.saveVibrationSetting();
+    }
+    
+    /**
+     * 开启音效和音乐
+     */
+    private enableSoundEffectAndMusic() {
+        // 如果音效未开启,则开启音效
+        if (!this.soundEffectEnabled) {
+            this.soundEffectEnabled = true;
+            
+            // 恢复之前保存的音量或使用默认值0.5
+            const volumeToRestore = this.savedSoundEffectVolume > 0 ? this.savedSoundEffectVolume : 0.5;
+            this.soundEffectSlider.progress = volumeToRestore;
+            this.updateProgressBar(this.soundEffectProgressBar, volumeToRestore);
+            this.applySoundEffectVolume(volumeToRestore);
+            
+            // 更新勾选标记显示
+            if (this.soundEffectCheck) {
+                this.soundEffectCheck.active = true;
+            }
+        }
+        
+        // 如果音乐未开启,则开启音乐
+        if (!this.musicEnabled) {
+            this.musicEnabled = true;
+            
+            // 恢复之前保存的音量或使用默认值0.5
+            const volumeToRestore = this.savedMusicVolume > 0 ? this.savedMusicVolume : 0.5;
+            this.musicSlider.progress = volumeToRestore;
+            this.updateProgressBar(this.musicProgressBar, volumeToRestore);
+            this.applyMusicVolume(volumeToRestore);
+            
+            // 更新勾选标记显示
+            if (this.musicCheck) {
+                this.musicCheck.active = true;
+            }
+        }
+        
+        this.saveSettings();
+    }
+    
+    /**
+     * 关闭音效和音乐
+     */
+    private disableSoundEffectAndMusic() {
+        // 关闭音效
+        if (this.soundEffectEnabled) {
+            this.soundEffectEnabled = false;
+            
+            // 保存当前音量并设置为0
+            this.savedSoundEffectVolume = this.soundEffectSlider.progress;
+            this.soundEffectSlider.progress = 0;
+            this.updateProgressBar(this.soundEffectProgressBar, 0);
+            this.applySoundEffectVolume(0);
+            
+            // 更新勾选标记显示
+            if (this.soundEffectCheck) {
+                this.soundEffectCheck.active = false;
+            }
+        }
+        
+        // 关闭音乐
+        if (this.musicEnabled) {
+            this.musicEnabled = false;
+            
+            // 保存当前音量并设置为0
+            this.savedMusicVolume = this.musicSlider.progress;
+            this.musicSlider.progress = 0;
+            this.updateProgressBar(this.musicProgressBar, 0);
+            this.applyMusicVolume(0);
+            
+            // 更新勾选标记显示
+            if (this.musicCheck) {
+                this.musicCheck.active = false;
+            }
+        }
+        
+        this.saveSettings();
+    }
+}

+ 9 - 0
assets/scripts/CombatSystem/MenuSystem/SoundController.ts.meta

@@ -0,0 +1,9 @@
+{
+  "ver": "4.0.24",
+  "importer": "typescript",
+  "imported": true,
+  "uuid": "2f6fbfac-054e-4d01-b6b7-a018b798d729",
+  "files": [],
+  "subMetas": {},
+  "userData": {}
+}

+ 319 - 0
assets/scripts/Core/AudioManager.ts

@@ -0,0 +1,319 @@
+import { _decorator, Component, AudioClip, AudioSource, resources, Node } from 'cc';
+const { ccclass, property } = _decorator;
+
+/**
+ * 音频管理器
+ * 统一管理游戏中的音乐和音效播放
+ */
+@ccclass('AudioManager')
+export class AudioManager extends Component {
+    // 音频源组件
+    @property({ type: AudioSource, tooltip: '音乐播放器' })
+    public musicAudioSource: AudioSource = null;
+    
+    @property({ type: AudioSource, tooltip: '音效播放器' })
+    public soundEffectAudioSource: AudioSource = null;
+    
+    // 音量设置
+    private musicVolume: number = 0.8;
+    private soundEffectVolume: number = 0.8;
+    
+    // 当前播放的音乐
+    private currentMusicClip: AudioClip = null;
+    
+    // 单例实例
+    private static _instance: AudioManager = null;
+    
+    onLoad() {
+        // 设置单例
+        if (AudioManager._instance === null) {
+            AudioManager._instance = this;
+            // 防止场景切换时被销毁
+            // this.node.parent = null;
+            // director.addPersistRootNode(this.node);
+        } else {
+            this.node.destroy();
+            return;
+        }
+        
+        this.initializeAudioSources();
+    }
+    
+    /**
+     * 获取单例实例
+     */
+    public static getInstance(): AudioManager {
+        return AudioManager._instance;
+    }
+    
+    /**
+     * 初始化音频源
+     */
+    private initializeAudioSources() {
+        // 如果没有指定音频源,尝试自动创建
+        if (!this.musicAudioSource) {
+            const musicNode = new Node('MusicAudioSource');
+            musicNode.parent = this.node;
+            this.musicAudioSource = musicNode.addComponent(AudioSource);
+            this.musicAudioSource.loop = true;
+        }
+        
+        if (!this.soundEffectAudioSource) {
+            const sfxNode = new Node('SFXAudioSource');
+            sfxNode.parent = this.node;
+            this.soundEffectAudioSource = sfxNode.addComponent(AudioSource);
+            this.soundEffectAudioSource.loop = false;
+        }
+        
+        // 设置初始音量
+        this.updateMusicVolume();
+        this.updateSoundEffectVolume();
+    }
+    
+    /**
+     * 播放背景音乐
+     * @param musicPath 音乐资源路径
+     * @param loop 是否循环播放
+     */
+    public playMusic(musicPath: string, loop: boolean = true) {
+        if (!this.musicAudioSource) {
+            console.warn('[AudioManager] 音乐播放器未初始化');
+            return;
+        }
+        
+        resources.load(musicPath, AudioClip, (err, clip) => {
+            if (err) {
+                console.error(`[AudioManager] 加载音乐失败: ${musicPath}`, err);
+                return;
+            }
+            
+            this.currentMusicClip = clip;
+            this.musicAudioSource.clip = clip;
+            this.musicAudioSource.loop = loop;
+            this.musicAudioSource.play();
+            
+            console.log(`[AudioManager] 播放音乐: ${musicPath}`);
+        });
+    }
+    
+    /**
+     * 停止背景音乐
+     */
+    public stopMusic() {
+        if (this.musicAudioSource && this.musicAudioSource.playing) {
+            this.musicAudioSource.stop();
+            console.log('[AudioManager] 停止音乐播放');
+        }
+    }
+    
+    /**
+     * 暂停背景音乐
+     */
+    public pauseMusic() {
+        if (this.musicAudioSource && this.musicAudioSource.playing) {
+            this.musicAudioSource.pause();
+            console.log('[AudioManager] 暂停音乐播放');
+        }
+    }
+    
+    /**
+     * 恢复背景音乐
+     */
+    public resumeMusic() {
+        if (this.musicAudioSource && this.currentMusicClip) {
+            this.musicAudioSource.play();
+            console.log('[AudioManager] 恢复音乐播放');
+        }
+    }
+    
+    /**
+     * 播放音效
+     * @param sfxPath 音效资源路径
+     * @param volume 音量(可选,0-1)
+     */
+    public playSoundEffect(sfxPath: string, volume?: number) {
+        if (!this.soundEffectAudioSource) {
+            console.warn('[AudioManager] 音效播放器未初始化');
+            return;
+        }
+        
+        resources.load(sfxPath, AudioClip, (err, clip) => {
+            if (err) {
+                console.error(`[AudioManager] 加载音效失败: ${sfxPath}`, err);
+                return;
+            }
+            
+            // 设置临时音量(如果指定)
+            const originalVolume = this.soundEffectAudioSource.volume;
+            if (volume !== undefined) {
+                this.soundEffectAudioSource.volume = volume * this.soundEffectVolume;
+            }
+            
+            this.soundEffectAudioSource.playOneShot(clip);
+            
+            // 恢复原始音量
+            if (volume !== undefined) {
+                this.soundEffectAudioSource.volume = originalVolume;
+            }
+            
+            console.log(`[AudioManager] 播放音效: ${sfxPath}`);
+        });
+    }
+    
+    /**
+     * 设置音乐音量
+     * @param volume 音量值(0-1)
+     */
+    public setMusicVolume(volume: number) {
+        this.musicVolume = Math.max(0, Math.min(1, volume));
+        this.updateMusicVolume();
+        //console.log(`[AudioManager] 设置音乐音量: ${this.musicVolume}`);
+    }
+    
+    /**
+     * 设置音效音量
+     * @param volume 音量值(0-1)
+     */
+    public setSoundEffectVolume(volume: number) {
+        this.soundEffectVolume = Math.max(0, Math.min(1, volume));
+        this.updateSoundEffectVolume();
+        //console.log(`[AudioManager] 设置音效音量: ${this.soundEffectVolume}`);
+    }
+    
+    /**
+     * 获取音乐音量
+     */
+    public getMusicVolume(): number {
+        return this.musicVolume;
+    }
+    
+    /**
+     * 获取音效音量
+     */
+    public getSoundEffectVolume(): number {
+        return this.soundEffectVolume;
+    }
+    
+    /**
+     * 更新音乐音量
+     */
+    private updateMusicVolume() {
+        if (this.musicAudioSource) {
+            this.musicAudioSource.volume = this.musicVolume;
+        }
+    }
+    
+    /**
+     * 更新音效音量
+     */
+    private updateSoundEffectVolume() {
+        if (this.soundEffectAudioSource) {
+            this.soundEffectAudioSource.volume = this.soundEffectVolume;
+        }
+    }
+    
+    /**
+     * 静音所有音频
+     */
+    public muteAll() {
+        this.setMusicVolume(0);
+        this.setSoundEffectVolume(0);
+    }
+    
+    /**
+     * 恢复所有音频音量
+     */
+    public unmuteAll() {
+        this.setMusicVolume(0.8);
+        this.setSoundEffectVolume(0.8);
+    }
+    
+    /**
+     * 检查音乐是否正在播放
+     */
+    public isMusicPlaying(): boolean {
+        return this.musicAudioSource && this.musicAudioSource.playing;
+    }
+    
+    onDestroy() {
+        if (AudioManager._instance === this) {
+            AudioManager._instance = null;
+        }
+    }
+}
+
+/**
+ * 音频管理器的静态接口
+ * 提供便捷的全局访问方法
+ */
+export class Audio {
+    /**
+     * 播放背景音乐
+     */
+    static playMusic(musicPath: string, loop: boolean = true) {
+        const manager = AudioManager.getInstance();
+        if (manager) {
+            manager.playMusic(musicPath, loop);
+        }
+    }
+    
+    /**
+     * 播放音效
+     */
+    static playSFX(sfxPath: string, volume?: number) {
+        const manager = AudioManager.getInstance();
+        if (manager) {
+            manager.playSoundEffect(sfxPath, volume);
+        }
+    }
+    
+    /**
+     * 设置音乐音量
+     */
+    static setMusicVolume(volume: number) {
+        const manager = AudioManager.getInstance();
+        if (manager) {
+            manager.setMusicVolume(volume);
+        }
+    }
+    
+    /**
+     * 设置音效音量
+     */
+    static setSFXVolume(volume: number) {
+        const manager = AudioManager.getInstance();
+        if (manager) {
+            manager.setSoundEffectVolume(volume);
+        }
+    }
+    
+    /**
+     * 停止音乐
+     */
+    static stopMusic() {
+        const manager = AudioManager.getInstance();
+        if (manager) {
+            manager.stopMusic();
+        }
+    }
+    
+    /**
+     * 暂停音乐
+     */
+    static pauseMusic() {
+        const manager = AudioManager.getInstance();
+        if (manager) {
+            manager.pauseMusic();
+        }
+    }
+    
+    /**
+     * 恢复音乐
+     */
+    static resumeMusic() {
+        const manager = AudioManager.getInstance();
+        if (manager) {
+            manager.resumeMusic();
+        }
+    }
+}

+ 9 - 0
assets/scripts/Core/AudioManager.ts.meta

@@ -0,0 +1,9 @@
+{
+  "ver": "4.0.24",
+  "importer": "typescript",
+  "imported": true,
+  "uuid": "dd3e5d1f-933f-41b0-9580-f180b03133f1",
+  "files": [],
+  "subMetas": {},
+  "userData": {}
+}

+ 9 - 0
assets/scripts/Examples.meta

@@ -0,0 +1,9 @@
+{
+  "ver": "1.2.0",
+  "importer": "directory",
+  "imported": true,
+  "uuid": "c29e2985-e461-4089-a19f-c80b281bdebe",
+  "files": [],
+  "subMetas": {},
+  "userData": {}
+}

+ 200 - 0
assets/scripts/Examples/AudioExample.ts

@@ -0,0 +1,200 @@
+import { _decorator, Component, Node, Button } from 'cc';
+import { Audio } from '../Core/AudioManager';
+const { ccclass, property } = _decorator;
+
+/**
+ * 音频系统使用示例
+ * 展示如何在游戏中使用音频功能
+ */
+@ccclass('AudioExample')
+export class AudioExample extends Component {
+    
+    @property({ type: Button, tooltip: '播放背景音乐按钮' })
+    public playMusicButton: Button = null;
+    
+    @property({ type: Button, tooltip: '停止背景音乐按钮' })
+    public stopMusicButton: Button = null;
+    
+    @property({ type: Button, tooltip: '播放音效按钮' })
+    public playSFXButton: Button = null;
+    
+    onLoad() {
+        this.bindEvents();
+    }
+    
+    private bindEvents() {
+        if (this.playMusicButton) {
+            this.playMusicButton.node.on(Button.EventType.CLICK, this.onPlayMusicClick, this);
+        }
+        
+        if (this.stopMusicButton) {
+            this.stopMusicButton.node.on(Button.EventType.CLICK, this.onStopMusicClick, this);
+        }
+        
+        if (this.playSFXButton) {
+            this.playSFXButton.node.on(Button.EventType.CLICK, this.onPlaySFXClick, this);
+        }
+    }
+    
+    private onPlayMusicClick() {
+        // 播放背景音乐
+        Audio.playMusic('audio/music/background_music', true);
+        
+        // 播放按钮点击音效
+        Audio.playSFX('audio/sfx/button_click', 0.6);
+    }
+    
+    private onStopMusicClick() {
+        // 停止背景音乐
+        Audio.stopMusic();
+        
+        // 播放按钮点击音效
+        Audio.playSFX('audio/sfx/button_click', 0.6);
+    }
+    
+    private onPlaySFXClick() {
+        // 播放各种音效示例
+        Audio.playSFX('audio/sfx/weapon_fire', 0.8);
+        
+        // 延迟播放爆炸音效
+        this.scheduleOnce(() => {
+            Audio.playSFX('audio/sfx/explosion', 1.0);
+        }, 0.5);
+    }
+    
+    /**
+     * 游戏开始时的音频设置示例
+     */
+    public onGameStart() {
+        // 播放游戏开始音效
+        Audio.playSFX('audio/sfx/game_start');
+        
+        // 延迟播放背景音乐
+        this.scheduleOnce(() => {
+            Audio.playMusic('audio/music/game_background');
+        }, 1.0);
+    }
+    
+    /**
+     * 游戏暂停时的音频设置示例
+     */
+    public onGamePause() {
+        // 暂停背景音乐
+        Audio.pauseMusic();
+        
+        // 播放暂停音效
+        Audio.playSFX('audio/sfx/pause');
+    }
+    
+    /**
+     * 游戏恢复时的音频设置示例
+     */
+    public onGameResume() {
+        // 恢复背景音乐
+        Audio.resumeMusic();
+        
+        // 播放恢复音效
+        Audio.playSFX('audio/sfx/resume');
+    }
+    
+    /**
+     * 游戏结束时的音频设置示例
+     */
+    public onGameOver() {
+        // 停止背景音乐
+        Audio.stopMusic();
+        
+        // 播放游戏结束音效
+        Audio.playSFX('audio/sfx/game_over');
+        
+        // 延迟播放结束音乐
+        this.scheduleOnce(() => {
+            Audio.playMusic('audio/music/game_over', false);
+        }, 1.0);
+    }
+    
+    /**
+     * 武器发射音效示例
+     */
+    public onWeaponFire(weaponType: string) {
+        switch (weaponType) {
+            case 'pea_shooter':
+                Audio.playSFX('audio/pea_shot', 0.7);
+                break;
+            case 'sharp_carrot':
+                Audio.playSFX('audio/carrot_shot', 0.8);
+                break;
+            case 'saw_grass':
+                Audio.playSFX('audio/saw_shot', 0.9);
+                break;
+            case 'watermelon_bomb':
+                Audio.playSFX('audio/bomb_launch', 1.0);
+                break;
+            case 'boomerang_plant':
+                Audio.playSFX('audio/boomerang_throw', 0.8);
+                break;
+            case 'hot_pepper':
+                Audio.playSFX('audio/pepper_launch', 0.9);
+                break;
+            case 'cactus_shotgun':
+                Audio.playSFX('audio/cactus_shot', 1.0);
+                break;
+            case 'okra_missile':
+                Audio.playSFX('audio/missile_launch', 1.0);
+                break;
+            default:
+                Audio.playSFX('audio/sfx/default_shot', 0.7);
+                break;
+        }
+    }
+    
+    /**
+     * UI交互音效示例
+     */
+    public onUIInteraction(interactionType: string) {
+        switch (interactionType) {
+            case 'button_click':
+                Audio.playSFX('audio/sfx/button_click', 0.6);
+                break;
+            case 'button_hover':
+                Audio.playSFX('audio/sfx/button_hover', 0.4);
+                break;
+            case 'menu_open':
+                Audio.playSFX('audio/sfx/menu_open', 0.7);
+                break;
+            case 'menu_close':
+                Audio.playSFX('audio/sfx/menu_close', 0.7);
+                break;
+            case 'slider_change':
+                Audio.playSFX('audio/sfx/slider_change', 0.3);
+                break;
+            case 'checkbox_toggle':
+                Audio.playSFX('audio/sfx/checkbox_toggle', 0.5);
+                break;
+        }
+    }
+    
+    /**
+     * 动态调整音量示例
+     */
+    public adjustVolumeBasedOnGameState(gameState: string) {
+        switch (gameState) {
+            case 'menu':
+                Audio.setMusicVolume(0.6);
+                Audio.setSFXVolume(0.8);
+                break;
+            case 'playing':
+                Audio.setMusicVolume(0.4);
+                Audio.setSFXVolume(1.0);
+                break;
+            case 'paused':
+                Audio.setMusicVolume(0.2);
+                Audio.setSFXVolume(0.6);
+                break;
+            case 'game_over':
+                Audio.setMusicVolume(0.8);
+                Audio.setSFXVolume(0.7);
+                break;
+        }
+    }
+}

+ 9 - 0
assets/scripts/Examples/AudioExample.ts.meta

@@ -0,0 +1,9 @@
+{
+  "ver": "4.0.24",
+  "importer": "typescript",
+  "imported": true,
+  "uuid": "2ed6e93a-08e8-40a4-97e2-cf45dba6d102",
+  "files": [],
+  "subMetas": {},
+  "userData": {}
+}

+ 66 - 1
assets/scripts/FourUI/NavBarController.ts

@@ -1,5 +1,6 @@
-import { _decorator, Color, Component, Node, Sprite } from 'cc';
+import { _decorator, Color, Component, Node, Sprite, find } from 'cc';
 import EventBus, { GameEvents } from '../Core/EventBus';
+import { GameStartMove } from '../Animations/GameStartMove';
 const { ccclass, property } = _decorator;
 
 @ccclass('NavBarController')
@@ -12,12 +13,76 @@ export class NavBarController extends Component {
     
     // 按钮锁定状态数组 - 对应 Battle、Shop、Upgrade、Skill 四个按钮
     private buttonLockStates: boolean[] = [false, true, false, false];  // 默认只锁定Shop按钮
+    
+    // GameStartMove组件引用,用于重置镜头位置
+    private gameStartMoveComponent: GameStartMove = null;
 
     start () {
         // 默认打开 MainUI
         this.switchTo(0);
         // 初始化按钮状态,设置shop按钮为锁定状态
         this.updateButtonStates();
+        // 设置事件监听
+        this.setupEventListeners();
+    }
+    
+    onDestroy() {
+        // 移除事件监听
+        this.removeEventListeners();
+    }
+    
+    /**
+     * 设置事件监听器
+     */
+    private setupEventListeners() {
+        const eventBus = EventBus.getInstance();
+        
+        // 监听返回主菜单事件
+        eventBus.on(GameEvents.RETURN_TO_MAIN_MENU, this.onReturnToMainMenu, this);
+    }
+    
+    /**
+     * 移除事件监听器
+     */
+    private removeEventListeners() {
+        const eventBus = EventBus.getInstance();
+        
+        eventBus.off(GameEvents.RETURN_TO_MAIN_MENU, this.onReturnToMainMenu, this);
+    }
+    
+    /**
+     * 处理返回主菜单事件
+     */
+    private onReturnToMainMenu() {
+        console.log('[NavBarController] 收到返回主菜单事件,切换到主界面');
+        
+        // 重置镜头位置到原始位置
+        this.resetCameraPosition();
+        
+        // 切换到主界面(Battle按钮对应的Main面板,索引0)
+        this.switchTo(0);
+    }
+    
+    /**
+     * 重置镜头位置到原始位置
+     * 确保从游戏中返回主界面时镜头位置正常
+     */
+    private resetCameraPosition() {
+        // 如果还没有获取GameStartMove组件,尝试获取
+        if (!this.gameStartMoveComponent) {
+            const cameraNode = find('Canvas/Main Camera');
+            if (cameraNode) {
+                this.gameStartMoveComponent = cameraNode.getComponent(GameStartMove);
+            }
+        }
+        
+        // 如果成功获取到组件,重置镜头位置
+        if (this.gameStartMoveComponent) {
+            console.log('[NavBarController] 重置镜头位置到原始位置');
+            this.gameStartMoveComponent.resetCameraToOriginalPosition(0.3);
+        } else {
+            console.warn('[NavBarController] 未找到GameStartMove组件,无法重置镜头位置');
+        }
     }
 
     onBattleClick ()  { 

+ 61 - 134
assets/scripts/FourUI/UpgradeSystem/UpgradeAni.ts

@@ -1,76 +1,20 @@
-import { _decorator, Component, Node, Tween, tween,Vec3 } from 'cc';
+import { _decorator, Component, Node, Tween, tween, Vec3, Material, Sprite, resources } from 'cc';
 
 const { ccclass, property } = _decorator;
 
 /**
- * 升级面板动画控制器
- * 负责管理升级面板的显示和隐藏动画
+ * 武器升级动画控制器
+ * 负责管理武器升级相关的动画效果
  */
 @ccclass('UpgradeAni')
 export class UpgradeAni extends Component {
     
-    /**
-     * 显示面板动画
-     * 面板从小到大的缩放动画,从屏幕中央弹出
-     */
-    public showPanel(): Promise<void> {
-        return new Promise((resolve) => {
-            // 设置初始状态:缩小到0,位置居中
-            this.node.setScale(Vec3.ZERO);
-            this.node.setPosition(0, 0, 0); // 确保面板在屏幕正中间
-            this.node.active = true;
-            
-            // 缩放动画:从0放大到1
-            tween(this.node)
-                .to(0.3, { scale: new Vec3(1, 1, 1) }, {
-                    easing: 'backOut' // 使用回弹缓动效果
-                })
-                .call(() => {
-                    resolve();
-                })
-                .start();
-        });
-    }
-    
-    /**
-     * 隐藏面板动画
-     * 面板从大到小的缩放动画
-     */
-    public hidePanel(): Promise<void> {
-        return new Promise((resolve) => {
-            // 缩放动画:从1缩小到0
-            tween(this.node)
-                .to(0.2, { scale: Vec3.ZERO }, {
-                    easing: 'backIn' // 使用回弹缓动效果
-                })
-                .call(() => {
-                    this.node.active = false;
-                    resolve();
-                })
-                .start();
-        });
-    }
-    
-    /**
-     * 立即隐藏面板(无动画)
-     */
-    public hidePanelImmediate(): void {
-        this.node.setScale(Vec3.ZERO);
-        this.node.active = false;
-    }
-    
-    /**
-     * 立即显示面板(无动画)
-     */
-    public showPanelImmediate(): void {
-        this.node.setScale(Vec3.ONE);
-        this.node.setPosition(0, 0, 0); // 确保面板在屏幕正中间
-        this.node.active = true;
-    }
+    @property(Node)
+    weaponSpriteNode: Node = null;
     
     /**
      * 武器升级成功动画
-     * 播放武器图标的放大缩小动画
+     * 播放武器图标的放大缩小动画,并在升级期间应用扫描效果材质
      * @param weaponIconNode 武器图标节点
      */
     public playWeaponUpgradeAnimation(weaponIconNode: Node): Promise<void> {
@@ -89,6 +33,38 @@ export class UpgradeAni extends Component {
             // 保存原始缩放值
             const originalScale = Vec3.ONE.clone();
             
+            let weaponSprite: Sprite = null;
+            let originalMaterial: Material = null;
+            
+            console.log('[UpgradeAni] 开始武器升级动画,weaponSpriteNode:', this.weaponSpriteNode);
+            
+            // 使用装饰器引用的WeaponSprite节点,获取其Sprite组件并保存原始材质
+            if (this.weaponSpriteNode) {
+                console.log('[UpgradeAni] 找到weaponSpriteNode,开始获取Sprite组件');
+                weaponSprite = this.weaponSpriteNode.getComponent(Sprite);
+                if (weaponSprite) {
+                    console.log('[UpgradeAni] 找到Sprite组件,开始加载材质');
+                    // 保存原始材质,优先保存customMaterial,如果没有则保存当前material
+                    originalMaterial = weaponSprite.customMaterial || weaponSprite.material;
+                    console.log('[UpgradeAni] 原始材质:', originalMaterial);
+                    
+                    // 加载并应用扫描效果材质
+                    resources.load('shaders/scan-effect', Material, (err, scanMaterial) => {
+                        console.log('[UpgradeAni] 材质加载回调,err:', err, 'scanMaterial:', scanMaterial);
+                        if (!err && scanMaterial && weaponSprite) {
+                            weaponSprite.material = scanMaterial;
+                            console.log('[UpgradeAni] 应用扫描效果材质成功');
+                        } else {
+                            console.warn('[UpgradeAni] 加载扫描效果材质失败:', err);
+                        }
+                    });
+                } else {
+                    console.warn('[UpgradeAni] weaponSpriteNode上没有找到Sprite组件');
+                }
+            } else {
+                console.warn('[UpgradeAni] weaponSpriteNode为null,请在编辑器中设置');
+            }
+            
             // 创建缩放动画:放大到1.5倍再立即缩小回原始大小
             const scaleAnimation = tween(weaponIconNode)
                 .to(0.25, { scale: new Vec3(originalScale.x * 1.5, originalScale.y * 1.5, originalScale.z) }, {
@@ -96,83 +72,34 @@ export class UpgradeAni extends Component {
                 })
                 .to(0.25, { scale: originalScale }, {
                     easing: 'sineIn'
-                });
-            
-            // 只播放缩放动画
-            scaleAnimation.call(() => resolve()).start();
-        });
-    }
-    
-    /**
-     * UP图标提示动画
-     * 播放缩小到0.7倍再放大到原来大小的重复动画,直到条件不满足为止
-     * @param upIconNode UP图标节点
-     * @param checkCondition 检查条件的回调函数,返回false时停止动画
-     */
-    public playUpIconAnimation(upIconNode: Node, checkCondition?: () => boolean): void {
-        if (!upIconNode) return;
-        
-        // 停止之前的动画
-        Tween.stopAllByTarget(upIconNode);
-        
-        // 保存原始缩放值
-        const originalScale = upIconNode.scale.clone();
-        
-        // 如果没有提供检查条件,使用默认的无限重复动画
-        if (!checkCondition) {
-            const scaleAnimation = tween(upIconNode)
-                .to(0.5, { scale: new Vec3(originalScale.x * 0.7, originalScale.y * 0.7, originalScale.z) }, {
-                    easing: 'sineInOut'
-                })
-                .to(0.5, { scale: originalScale }, {
-                    easing: 'sineInOut'
-                })
-                .repeatForever(); // 无限重复
-            
-            scaleAnimation.start();
-            return;
-        }
-        
-        // 带条件检查的动画循环
-        const playAnimationCycle = () => {
-            // 在每次动画循环开始前检查条件
-            if (!checkCondition()) {
-                // 条件不满足,停止动画并恢复原始缩放
-                this.stopUpIconAnimation(upIconNode);
-                return;
-            }
-            
-            // 创建一次完整的缩放动画循环
-            const scaleAnimation = tween(upIconNode)
-                .to(0.5, { scale: new Vec3(originalScale.x * 0.7, originalScale.y * 0.7, originalScale.z) }, {
-                    easing: 'sineInOut'
-                })
-                .to(0.5, { scale: originalScale }, {
-                    easing: 'sineInOut'
                 })
                 .call(() => {
-                    // 一次循环完成后,递归调用下一次循环
-                    playAnimationCycle();
+                    // 动画结束后恢复原始材质
+                    if (weaponSprite && originalMaterial) {
+                        // 如果原始材质是customMaterial,则恢复customMaterial
+                        if (weaponSprite.customMaterial === originalMaterial) {
+                            weaponSprite.material = weaponSprite.customMaterial;
+                        } else {
+                            weaponSprite.material = originalMaterial;
+                        }
+                        console.log('[UpgradeAni] 恢复原始材质成功');
+                    } else if (weaponSprite) {
+                        // 如果没有保存的原始材质,尝试恢复到customMaterial
+                        if (weaponSprite.customMaterial) {
+                            weaponSprite.material = weaponSprite.customMaterial;
+                            console.log('[UpgradeAni] 恢复到customMaterial');
+                        } else {
+                            weaponSprite.material = null;
+                            console.log('[UpgradeAni] 恢复到默认材质');
+                        }
+                    }
+                    resolve();
                 });
             
+            // 播放缩放动画
             scaleAnimation.start();
-        };
-        
-        // 开始第一次动画循环
-        playAnimationCycle();
+        });
     }
     
-    /**
-     * 停止UP图标动画
-     * @param upIconNode UP图标节点
-     */
-    public stopUpIconAnimation(upIconNode: Node): void {
-        if (!upIconNode) return;
-        
-        // 停止所有动画
-        Tween.stopAllByTarget(upIconNode);
-        
-        // 恢复原始缩放
-        upIconNode.setScale(Vec3.ONE);
-    }
+
 }

+ 12 - 17
assets/scripts/FourUI/UpgradeSystem/UpgradeController.ts

@@ -1,6 +1,7 @@
 import { _decorator, Component, Node, Button, Label, Sprite, SpriteFrame, Texture2D, resources, ScrollView, Layout, Prefab, instantiate, find, UIOpacity, Color } from 'cc';
 import { SaveDataManager, WeaponData } from '../../LevelSystem/SaveDataManager';
 import EventBus, { GameEvents } from '../../Core/EventBus';
+import { PopUPAni } from '../../Animations/PopUPAni';
 import { UpgradeAni } from './UpgradeAni';
 
 const { ccclass, property } = _decorator;
@@ -66,6 +67,7 @@ export class UpgradeController extends Component {
     @property(Prefab) unlockedWeaponPrefab: Prefab = null;  // Unlock.prefab
     
     // 动画控制器
+    @property(PopUPAni) panelAni: PopUPAni = null;    // Canvas/UpgradeUI/UpgradePanel上的PopUPAni组件
     @property(UpgradeAni) upgradeAni: UpgradeAni = null;    // Canvas/UpgradeUI/UpgradePanel上的UpgradeAni组件
     
     // 数据管理
@@ -98,13 +100,6 @@ export class UpgradeController extends Component {
         // 刷新UI
         this.refreshWeaponList();
         
-        // 初始化升级面板状态
-        if (this.upgradeAni) {
-            this.upgradeAni.hidePanelImmediate();
-        } else {
-            this.upgradePanel.active = false;
-        }
-        
         console.log('[UpgradeController] 初始化完成');
     }
     
@@ -492,8 +487,8 @@ export class UpgradeController extends Component {
         this.refreshUpgradePanel();
         
         // 使用动画显示升级面板
-        if (this.upgradeAni) {
-            await this.upgradeAni.showPanel();
+        if (this.panelAni) {
+            await this.panelAni.showPanel();
         } else {
             // 如果没有动画组件,直接显示
             this.upgradePanel.active = true;
@@ -505,13 +500,13 @@ export class UpgradeController extends Component {
      */
     private async closeUpgradePanel() {
         // 停止UP图标动画
-        if (this.upIcon && this.upgradeAni) {
-            this.upgradeAni.stopUpIconAnimation(this.upIcon);
+        if (this.upIcon && this.panelAni) {
+            this.panelAni.stopIconTipAnimation(this.upIcon);
         }
         
         // 使用动画隐藏升级面板
-        if (this.upgradeAni) {
-            await this.upgradeAni.hidePanel();
+        if (this.panelAni) {
+            await this.panelAni.hidePanel();
         } else {
             // 如果没有动画组件,直接隐藏
             this.upgradePanel.active = false;
@@ -593,7 +588,7 @@ export class UpgradeController extends Component {
             // 钞票足够,显示UP图标并播放动画
             if (this.upIcon) {
                 this.upIcon.active = true;
-                if (this.upgradeAni) {
+                if (this.panelAni) {
                     // 传入检查条件:钞票是否足够且未达到最大等级
                     const checkCondition = () => {
                         const currentMoney = this.saveDataManager.getMoney();
@@ -603,15 +598,15 @@ export class UpgradeController extends Component {
                                currentWeaponData.level < maxLevel && 
                                currentMoney >= currentUpgradeCost;
                     };
-                    this.upgradeAni.playUpIconAnimation(this.upIcon, checkCondition);
+                    this.panelAni.playIconTipAnimation(this.upIcon, checkCondition);
                 }
             }
         } else {
             // 钞票不足或已满级,隐藏UP图标并停止动画
             if (this.upIcon) {
                 this.upIcon.active = false;
-                if (this.upgradeAni) {
-                    this.upgradeAni.stopUpIconAnimation(this.upIcon);
+                if (this.panelAni) {
+                    this.panelAni.stopIconTipAnimation(this.upIcon);
                 }
             }
         }

+ 10 - 2
assets/scripts/LevelSystem/IN_game.ts

@@ -952,11 +952,19 @@ export class InGameManager extends Component {
         eventBus.emit(GameEvents.RESET_WALL_HEALTH);         // 重置墙体血量
         eventBus.emit(GameEvents.RESET_UI_STATES);           // 重置UI状态
         
-        // 8. 发送游戏结束事件,确保游戏状态正确转换
+        // 8. 重置镜头位置
+        console.log('[InGameManager] 重置镜头位置');
+        if (this.gameStartMoveComponent) {
+            this.gameStartMoveComponent.resetCameraToOriginalPositionImmediate();
+        } else {
+            console.warn('[InGameManager] GameStartMove组件未找到,无法重置镜头位置');
+        }
+        
+        // 9. 发送游戏结束事件,确保游戏状态正确转换
         console.log('[InGameManager] 发送游戏结束事件');
         eventBus.emit('GAME_END');
         
-        // 9. 重置游戏状态为初始状态
+        // 10. 重置游戏状态为初始状态
         this.currentState = GameState.PLAYING;
         
         console.log('[InGameManager] 游戏数据清理完成,可以安全返回主页面');

+ 242 - 0
docs/AudioSystemGuide.md

@@ -0,0 +1,242 @@
+# 音频系统设置指南
+
+## 概述
+
+本文档介绍如何在游戏中集成音乐和音效系统,以及如何通过菜单控制音频音量。
+
+## 系统架构
+
+### 核心组件
+
+1. **AudioManager** (`assets/scripts/Core/AudioManager.ts`)
+   - 统一管理所有音频播放
+   - 提供音乐和音效的播放、停止、音量控制功能
+   - 单例模式,全局访问
+
+2. **SoundController** (`assets/scripts/CombatSystem/MenuSystem/SoundController.ts`)
+   - 管理菜单中的音频控制UI
+   - 处理滑动条和开关按钮的交互
+   - 与AudioManager集成,实现音量控制
+
+3. **Audio静态类** (`assets/scripts/Core/AudioManager.ts`)
+   - 提供便捷的静态方法访问音频功能
+   - 简化代码调用
+
+## 设置步骤
+
+### 1. 创建AudioManager节点
+
+在主场景中创建一个AudioManager节点:
+
+```
+Canvas/
+├── AudioManager (添加AudioManager组件)
+│   ├── MusicAudioSource (自动创建)
+│   └── SFXAudioSource (自动创建)
+```
+
+### 2. 配置SoundController
+
+在MenuUI节点上添加SoundController组件,并配置以下属性:
+
+- **soundEffectSlider**: 音效滑动条组件
+- **soundEffectCheckbox**: 音效开关按钮
+- **soundEffectCheck**: 音效勾选标记节点
+- **soundEffectProgressBg**: 音效进度条背景
+- **musicSlider**: 音乐滑动条组件
+- **musicCheckbox**: 音乐开关按钮
+- **musicCheck**: 音乐勾选标记节点
+- **musicProgressBg**: 音乐进度条背景
+- **greenProgressSprite**: 绿色进度条材质
+
+### 3. 准备音频资源
+
+将音频文件放置在resources目录下:
+
+```
+assets/resources/
+├── audio/
+│   ├── music/
+│   │   ├── background_music.mp3
+│   │   └── menu_music.mp3
+│   └── sfx/
+│       ├── button_click.mp3
+│       ├── weapon_fire.mp3
+│       └── explosion.mp3
+```
+
+### 4. 在代码中使用音频系统
+
+#### 播放背景音乐
+
+```typescript
+import { Audio } from '../Core/AudioManager';
+
+// 播放背景音乐
+Audio.playMusic('audio/music/background_music');
+
+// 停止音乐
+Audio.stopMusic();
+
+// 暂停/恢复音乐
+Audio.pauseMusic();
+Audio.resumeMusic();
+```
+
+#### 播放音效
+
+```typescript
+// 播放音效
+Audio.playSFX('audio/sfx/button_click');
+
+// 播放音效并指定音量
+Audio.playSFX('audio/sfx/explosion', 0.8);
+```
+
+#### 控制音量
+
+```typescript
+// 设置音乐音量 (0-1)
+Audio.setMusicVolume(0.7);
+
+// 设置音效音量 (0-1)
+Audio.setSFXVolume(0.8);
+```
+
+## 菜单音频控制功能
+
+### 滑动条功能
+
+- **音效滑动条**: 控制所有音效的音量大小
+- **音乐滑动条**: 控制背景音乐的音量大小
+- **绿色进度条**: 跟随滑动条Handle的进度实时更新
+
+### 开关按钮功能
+
+- **勾选状态**: 音频正常播放,显示勾选标记
+- **取消勾选**: 
+  - 记录当前音量设置
+  - 将滑动条移到最左侧(音量为0)
+  - 隐藏勾选标记
+- **重新勾选**: 
+  - 恢复之前记录的音量设置
+  - 滑动条回到之前的位置
+  - 显示勾选标记
+
+### 数据持久化
+
+音频设置会自动保存到本地存储,包括:
+- 音效开关状态
+- 音乐开关状态
+- 音效音量值
+- 音乐音量值
+
+## 集成示例
+
+### 在武器系统中添加音效
+
+```typescript
+// 在武器发射时播放音效
+export class WeaponController {
+    private fireWeapon() {
+        // 武器发射逻辑...
+        
+        // 播放发射音效
+        Audio.playSFX('audio/sfx/weapon_fire');
+    }
+}
+```
+
+### 在UI按钮中添加点击音效
+
+```typescript
+// 在按钮点击事件中添加音效
+export class ButtonController {
+    private onButtonClick() {
+        // 播放按钮点击音效
+        Audio.playSFX('audio/sfx/button_click', 0.6);
+        
+        // 按钮逻辑...
+    }
+}
+```
+
+### 在游戏状态切换时控制音乐
+
+```typescript
+// 在游戏管理器中控制背景音乐
+export class GameManager {
+    private startGame() {
+        // 播放游戏背景音乐
+        Audio.playMusic('audio/music/background_music');
+    }
+    
+    private pauseGame() {
+        // 暂停背景音乐
+        Audio.pauseMusic();
+    }
+    
+    private resumeGame() {
+        // 恢复背景音乐
+        Audio.resumeMusic();
+    }
+    
+    private gameOver() {
+        // 停止背景音乐
+        Audio.stopMusic();
+        
+        // 播放游戏结束音效
+        Audio.playSFX('audio/sfx/game_over');
+    }
+}
+```
+
+## 注意事项
+
+1. **音频格式**: 推荐使用MP3或OGG格式的音频文件
+2. **文件大小**: 控制音频文件大小,避免影响游戏加载速度
+3. **音量平衡**: 确保不同音效之间的音量平衡
+4. **性能优化**: 避免同时播放过多音效
+5. **资源管理**: 及时释放不再使用的音频资源
+
+## 扩展功能
+
+### 音频淡入淡出
+
+可以扩展AudioManager添加淡入淡出功能:
+
+```typescript
+// 音乐淡入
+public fadeInMusic(musicPath: string, duration: number = 2.0) {
+    // 实现音乐淡入逻辑
+}
+
+// 音乐淡出
+public fadeOutMusic(duration: number = 2.0) {
+    // 实现音乐淡出逻辑
+}
+```
+
+### 音效池管理
+
+对于频繁播放的音效,可以实现音效池:
+
+```typescript
+// 预加载常用音效
+public preloadSFX(sfxPaths: string[]) {
+    // 预加载音效到内存池
+}
+```
+
+### 3D音效
+
+对于需要空间音效的场景,可以扩展支持3D音效:
+
+```typescript
+// 播放3D音效
+public play3DSFX(sfxPath: string, position: Vec3) {
+    // 实现3D音效播放
+}
+```
+
+这个音频系统为游戏提供了完整的音频解决方案,支持菜单控制和代码调用,可以根据具体需求进行扩展和定制。

Algunos archivos no se mostraron porque demasiados archivos cambiaron en este cambio