Procházet zdrojové kódy

第四步超时保护

181404010226 před 2 měsíci
rodič
revize
0a636c4b39

+ 6 - 27
assets/NewbieGuidePlugin-v1.0.0/NewbieGuidePlugin-v1.0.0/scripts/GuideManager.ts

@@ -85,17 +85,9 @@ export class GuideManager {
             this._currentStep.forceComplete();
         }
         this._isGuideActive = false;
+        // 仅停止插件自身的UI状态,不主动隐藏全局引导层
         this._uiController.hideAllGuideUI();
-        // 同步隐藏根遮罩节点
-        const guideMaskRoot = find('Canvas/GuideMaskAreas');
-        if (guideMaskRoot) {
-            guideMaskRoot.active = false;
-        }
-        // 同步隐藏手指动画层(GuideLayer)
-        const guideLayer = find('Canvas/GuideLayer');
-        if (guideLayer) {
-            guideLayer.active = false;
-        }
+        // 保留 Canvas/GuideMaskAreas 与 Canvas/GuideLayer 的显隐状态给外层控制器
     }
     
     /**
@@ -169,24 +161,11 @@ export class GuideManager {
      * 引导完成时调用
      */
     private onGuideComplete(): void {
+        // 仅结束插件自身流程,不再干预全局引导UI或完成标记
         this._isGuideActive = false;
-        this._uiController.hideAllGuideUI();
-        
-        // 隐藏整套遮罩根节点,避免场景残留
-        const guideMaskRoot = find('Canvas/GuideMaskAreas');
-        if (guideMaskRoot) {
-            guideMaskRoot.active = false;
-        }
-        // 隐藏手指动画层(GuideLayer)避免残留
-        const guideLayer = find('Canvas/GuideLayer');
-        if (guideLayer) {
-            guideLayer.active = false;
-        }
-
-        // 可以在这里添加引导完成的回调
-        console.log("新手引导完成!");
-        // 标记玩家已完成新手引导并持久化
-        NewbieGuideManager.getInstance().markNewbieGuideCompleted();
+        console.log("新手引导插件阶段完成,继续由 GuideUIController 执行后续步骤");
+        // 不再隐藏 Canvas/GuideMaskAreas 或 Canvas/GuideLayer,避免中断剩余引导
+        // 不再调用 NewbieGuideManager.markNewbieGuideCompleted(),交由完整流程结束时统一标记
     }
     
     /**

+ 174 - 46
assets/NewbieGuidePlugin-v1.0.0/NewbieGuidePlugin-v1.0.0/scripts/GuideUIController.ts

@@ -6,6 +6,7 @@
 import { sp, Node, director, Vec3, find, UITransform, tween, Tween, _decorator, Component } from "cc";
 import EventBus, { GameEvents } from "../../../scripts/Core/EventBus";
 import BlinkScaleAnimator from "../../../scripts/Animations/BlinkScaleAnimator";
+import { NewbieGuideManager } from '../../../scripts/Core/NewbieGuideManager';
 const { ccclass, property } = _decorator;
 
 @ccclass('GuideUIController')
@@ -244,6 +245,38 @@ export class GuideUIController extends Component {
             this.guideStepsFrameDecorators[6] = this.guideStepsFrameDecorators[2];
         }
 
+        // Step 7: 第八步,主界面升级按钮(点击类型)
+        if (!this.guideStepsTargets[7]) {
+            let upgradeBtn: Node | null = null;
+            // 优先通过 NavBarController 的 buttons[2] 获取可点击范围
+            const navBar = find('Canvas/NavBar');
+            const navCtrl = navBar ? (navBar.getComponent('NavBarController' as any) as any) : null;
+            if (navCtrl && navCtrl.buttons && navCtrl.buttons.length > 2) {
+                upgradeBtn = navCtrl.buttons[2];
+            }
+            // 兜底:直接查找路径 Canvas/NavBar/Layout/UPGRADE
+            if (!upgradeBtn) {
+                upgradeBtn = find('Canvas/NavBar/Layout/UPGRADE') ?? null;
+            }
+            if (!upgradeBtn) {
+                console.warn('[GuideUIController] UPGRADE button node not found for step 8');
+            }
+            this.guideStepsTargets[7] = this.guideStepsTargets[7] ?? upgradeBtn ?? null;
+        }
+        this.guideStepsActions[7] = this.guideStepsActions[7] ?? 'tap';
+        // 遮罩兜底为 step_2
+        if (!this.guideStepsMaskAreas[7]) {
+            const step2Mask = find('Canvas/GuideMaskAreas/step_2');
+            if (!step2Mask) {
+                console.warn('[GuideUIController] Mask fallback step_2 not found for step 8');
+            }
+            this.guideStepsMaskAreas[7] = this.guideStepsMaskAreas[7] ?? step2Mask ?? null;
+        }
+        // 可选:Frame装饰器复用第三步(如有)
+        if (!this.guideStepsFrameDecorators[7] && this.guideStepsFrameDecorators[2]) {
+            this.guideStepsFrameDecorators[7] = this.guideStepsFrameDecorators[2];
+        }
+
         console.log('[GuideUIController] Tutorial steps ensured (editor config first, find as fallback)');
     }
 
@@ -495,6 +528,53 @@ export class GuideUIController extends Component {
 
     }
 
+    /**
+     * 点击类型步骤的统一手指展示:将手指放在按钮右下角并闪烁
+     */
+    private showFingerForClickStep(stepIndex: number, target: Node): void {
+        if (this._activeBlinkComp) {
+            this._activeBlinkComp.stop();
+            this._activeBlinkComp = null;
+        }
+        const finger = this.getGuideNode('guild_1');
+        if (!finger) {
+            console.warn('[GuideUIController] 未找到 guild_1 手指节点');
+            const ui = target.getComponent(UITransform);
+            const worldPos = ui ? ui.convertToWorldSpaceAR(new Vec3(0, 0, 0)) : target.worldPosition;
+            this.createPointingAnimation('guild_1', worldPos);
+            return;
+        }
+        // 停止手指的Spine动画,避免与缩放冲突
+        this.stopAnimation('guild_1');
+
+        // 计算目标按钮右下角世界坐标
+        const ui = target.getComponent(UITransform);
+        const baseWorldPos = ui
+            ? ui.convertToWorldSpaceAR(new Vec3(ui.width * 0.5, -ui.height * 0.5, 0))
+            : target.worldPosition;
+
+        // 转换为手指父节点的本地坐标并叠加指尖偏移
+        const localCorner = this.convertWorldToLocalPosition(baseWorldPos, finger.parent || finger);
+        const finalPos = new Vec3(
+            localCorner.x + this.fingerTipOffset.x,
+            localCorner.y + this.fingerTipOffset.y,
+            localCorner.z + this.fingerTipOffset.z
+        );
+        finger.setPosition(finalPos);
+
+        // 显示手指UI并播放闪烁缩放动画
+        this.showGuideUI('guild_1');
+        Tween.stopAllByTarget(finger);
+        this._activeBlinkComp = BlinkScaleAnimator.ensure(finger, {
+            scaleFactor: 1.3,
+            upDuration: 0.15,
+            downDuration: 0.15,
+            easingUp: 'sineOut',
+            easingDown: 'sineIn'
+        });
+        console.log(`[GuideUIController] 点击步骤 ${stepIndex}:手指位于按钮右下角并进行缩放闪烁`);
+    }
+
     /**
      * 获取节点的世界坐标位置
      */
@@ -700,6 +780,8 @@ export class GuideUIController extends Component {
             if (guideLayer) {
                 guideLayer.active = false;
             }
+            // 在完整序列结束时标记新手引导完成
+            NewbieGuideManager.getInstance().markNewbieGuideCompleted();
             return;
         }
     
@@ -741,8 +823,13 @@ export class GuideUIController extends Component {
     
         console.log(`[GuideUIController] 运行步骤 idx=${idx}, action=${action}, hasFingerConfig=${!!hasFingerConfig}, target=${target?.name || 'null'}`);
     
-        // 特殊处理第一步:显示从Block1到Grid_3_1的拖拽动画
-        if (idx === 0 && action === 'wait_event') {
+        // 点击类步骤统一处理:在按钮右下角显示手指并闪烁
+        if (action === 'tap') {
+            this.showFingerForClickStep(idx, target);
+        } else if ((idx === 2 || idx === 6) && action === 'wait_event') {
+            // 第三/七步:沿用点击类的视觉表现,但等待事件完成
+            this.showFingerForClickStep(idx, target);
+        } else if (idx === 0 && action === 'wait_event') {
             if (hasFingerConfig) {
                 // 使用配置的节点位置创建拖拽动画
                 this.createDragAnimation('guild_1', undefined, undefined, idx);
@@ -765,50 +852,6 @@ export class GuideUIController extends Component {
                     console.warn('[GuideUIController] 未找到Grid_3_1,使用普通指向动画');
                 }
             }
-        } else if ((idx === 2 || idx === 6) && action === 'wait_event') {
-            // 第三步或第七步:将手指(guild_1)放到确认按钮右下角,并让手指原地缩放
-            if (this._activeBlinkComp) {
-                this._activeBlinkComp.stop();
-                this._activeBlinkComp = null;
-            }
-            
-            const finger = this.getGuideNode('guild_1');
-            if (finger) {
-                // 先停止手指的Spine动画,避免与缩放冲突
-                this.stopAnimation('guild_1');
-            
-                // 计算目标按钮右下角的世界坐标
-                const cornerWorldPos = ui
-                    ? ui.convertToWorldSpaceAR(new Vec3(ui.width * 0.5, -ui.height * 0.5, 0))
-                    : worldPos;
-            
-                // 转换为手指父节点的本地坐标并叠加指尖偏移
-                const localCorner = this.convertWorldToLocalPosition(cornerWorldPos, finger.parent || finger);
-                const finalPos = new Vec3(
-                    localCorner.x + this.fingerTipOffset.x,
-                    localCorner.y + this.fingerTipOffset.y,
-                    localCorner.z + this.fingerTipOffset.z
-                );
-                finger.setPosition(finalPos);
-                
-                // 显示手指UI(不启动移动动画)
-                this.showGuideUI('guild_1');
-                Tween.stopAllByTarget(finger);
-                
-                // 在手指上播放闪烁缩放动画
-                this._activeBlinkComp = BlinkScaleAnimator.ensure(finger, {
-                    scaleFactor: 1.3,
-                    upDuration: 0.15,
-                    downDuration: 0.15,
-                    easingUp: 'sineOut',
-                    easingDown: 'sineIn'
-                });
-                console.log('[GuideUIController] 第三/七步:手指位于按钮右下角并进行缩放闪烁');
-            } else {
-                console.warn('[GuideUIController] 未找到 guild_1 手指节点');
-                // 兜底使用普通指向动画
-                this.createPointingAnimation('guild_1', worldPos);
-            }
         } else if (hasFingerConfig && (action === 'wait_event' || action === 'tap' || action === 'auto')) {
             // 其他步骤如果配置了手指动画节点,也使用拖拽动画
             this.createDragAnimation('guild_1', undefined, undefined, idx);
@@ -821,6 +864,78 @@ export class GuideUIController extends Component {
         // 清理旧监听
         this.clearCurrentStepListeners();
 
+        // 第四步超时保护:开始后3秒未完成则自动将方块移动到目标格子
+        if (idx === 3) {
+            if (this._autoScheduleCallback) {
+                this.unschedule(this._autoScheduleCallback as any);
+                this._autoScheduleCallback = null;
+            }
+            this._autoScheduleCallback = () => {
+                if (this._currentStepIndex !== 3) {
+                    this._autoScheduleCallback = null;
+                    return;
+                }
+                console.log('[GuideUIController] 第四步超时(5秒)未操作,自动将方块移动到目标格子');
+                const scene = director.getScene();
+                const blockManager = scene ? (scene.getComponentInChildren('BlockManager' as any) as any) : null;
+                if (!blockManager) {
+                    console.warn('[GuideUIController] 第四步:未找到 BlockManager,无法自动放置');
+                    this._autoScheduleCallback = null;
+                    return;
+                }
+
+                // 选择要移动的方块:优先 fingerStartNodes[3],兜底在 PlacedBlocks 中找“I”型
+                let block: Node | null = (this.fingerStartNodes && this.fingerStartNodes.length > 3) ? this.fingerStartNodes[3] : null;
+                if (!block || !block.isValid) {
+                    const placed = blockManager.placedBlocksContainer as Node | null;
+                    if (placed && placed.isValid) {
+                        for (let i = 0; i < placed.children.length; i++) {
+                            const child = placed.children[i];
+                            try {
+                                const shapeId = blockManager.getBlockShape ? blockManager.getBlockShape(child) : null;
+                                if (shapeId === 'I') { block = child; break; }
+                            } catch (e) {}
+                        }
+                        // 如果未找到“I”型,兜底选择第一个
+                        if (!block && placed.children.length > 0) {
+                            block = placed.children[0];
+                        }
+                    }
+                }
+
+                // 选择目标格子:优先 Grid_3_9,其次 Grid_4_9
+                const findGrid = (name: string) => {
+                    try {
+                        if (blockManager.findGridNodeByName) { return blockManager.findGridNodeByName(name); }
+                    } catch (e) {}
+                    const gridContainer = find('Canvas/GameLevelUI/GameArea/GridContainer');
+                    return gridContainer ? gridContainer.getChildByName(name) : null;
+                };
+                const grid3_9 = findGrid('Grid_3_9');
+                const grid4_9 = findGrid('Grid_4_9');
+
+                let placedOk = false;
+                if (block && block.isValid) {
+                    if (grid3_9) {
+                        try { placedOk = !!blockManager.tryPlaceBlockToSpecificGrid && blockManager.tryPlaceBlockToSpecificGrid(block, grid3_9); } catch (e) {}
+                    }
+                    if (!placedOk && grid4_9) {
+                        try { placedOk = !!blockManager.tryPlaceBlockToSpecificGrid && blockManager.tryPlaceBlockToSpecificGrid(block, grid4_9); } catch (e) {}
+                    }
+                }
+
+                if (placedOk) {
+                    console.log('[GuideUIController] 自动完成第四步:已将方块移动到目标位置');
+                    // 不手动调用 _onStepComplete;等待 BlockManager 派发 TUTORIAL_BLOCK_3_PLACED
+                } else {
+                    console.warn('[GuideUIController] 自动完成第四步失败:继续等待玩家操作');
+                }
+
+                this._autoScheduleCallback = null;
+            };
+            this.scheduleOnce(this._autoScheduleCallback as any, 5.0);
+        }
+
         switch (action) {
             case 'tap':
                 this._listeningTarget = target;
@@ -872,6 +987,11 @@ export class GuideUIController extends Component {
     
         // 清理事件监听/计时器
         this.clearCurrentStepListeners();
+        // 若存在任何未清理的定时器,统一取消
+        if (this._autoScheduleCallback) {
+            this.unschedule(this._autoScheduleCallback as any);
+            this._autoScheduleCallback = null;
+        }
     
         // 进入下一步(第三步结束后延迟5秒再触发第四步;第四步结束等待第二次方块放置再触发第五步)
         const prevIndex = this._currentStepIndex;
@@ -899,6 +1019,14 @@ export class GuideUIController extends Component {
                 this.runCurrentStep();
             };
             bus.on(gateEvent, onGate, this);
+        } else if (prevIndex === 6 && nextIndex === 7) {
+            // 第七步完成后不要立刻开始第八步,等待返回主界面事件
+            const bus = EventBus.getInstance();
+            const onReturn = () => {
+                bus.off(GameEvents.RETURN_TO_MAIN_MENU, onReturn, this);
+                this.runCurrentStep();
+            };
+            bus.on(GameEvents.RETURN_TO_MAIN_MENU, onReturn, this);
         } else {
             this.runCurrentStep();
         }

+ 16 - 1
assets/Scenes/GameLevel.scene

@@ -47951,7 +47951,7 @@
   },
   {
     "__type__": "cc.Node",
-    "_name": "tbdib",
+    "_name": "Weapon",
     "_objFlags": 0,
     "__editorExtras__": {},
     "_parent": {
@@ -49231,6 +49231,12 @@
     "nameLabel": {
       "__id__": 1408
     },
+    "bt1": {
+      "__id__": 1413
+    },
+    "weaponNode": {
+      "__id__": 1400
+    },
     "confirmBtn": {
       "__id__": 1430
     },
@@ -49350,6 +49356,9 @@
       },
       {
         "__id__": 1135
+      },
+      {
+        "__id__": 705
       }
     ],
     "guideStepsActions": [
@@ -49388,6 +49397,12 @@
       {
         "__id__": 1381
       },
+      {
+        "__id__": 1358
+      },
+      {
+        "__id__": 1358
+      },
       {
         "__id__": 1358
       }

binární
assets/data/excel/__pycache__/weapon_config_manager.cpython-313.pyc


+ 9 - 3
assets/data/excel/weapon_config_manager.py

@@ -824,9 +824,15 @@ class WeaponConfigManager:
             bullet_speed = item.get('bulletSpeed', item.get('子弹速度', 100))
             weapon_type = item.get('type', item.get('类型', ''))
             weight = item.get('weight', item.get('权重', 1))
-            unlock_level = item.get('unlockLevel', item.get('解锁条件关卡', 1))  # 新增解锁条件关卡字段
-            
-            # 获取稀有度伤害倍率
+            unlock_level = item.get('unlockLevel', item.get('解锁条件关卡', item.get('解锁条件关卡(通关)', item.get('解锁条件关卡(通关)', 1))))  # 兼容带括号的列名
+            # 强制转换为整数,避免字符串进入JSON
+            try:
+                if unlock_level is None or str(unlock_level).strip() == '' or str(unlock_level).strip().lower() == 'nan':
+                    unlock_level = 1
+                else:
+                    unlock_level = int(float(str(unlock_level).strip()))
+            except Exception:
+                unlock_level = 1
             rarity_damage_multipliers = item.get('rarityDamageMultipliers', item.get('稀有度伤害倍率', [1.0, 1.5, 2.25, 8.0]))
             # 确保是数组格式
             if not isinstance(rarity_damage_multipliers, list):

binární
assets/data/excel/关卡配置/关卡配置表.xlsx


+ 3 - 3
assets/data/levels/Level1.json

@@ -34,7 +34,7 @@
       "enemies": [
         {
           "enemyType": "normal_zombie",
-          "count": 5,
+          "count": 1,
           "spawnInterval": 1.0,
           "spawnDelay": 0.0,
           "characteristics": "中速移动, 无技能",
@@ -56,7 +56,7 @@
       "enemies": [
         {
           "enemyType": "normal_zombie",
-          "count": 6,
+          "count": 1,
           "spawnInterval": 1.0,
           "spawnDelay": 0.0,
           "characteristics": "中速移动, 无技能",
@@ -64,7 +64,7 @@
         },
         {
           "enemyType": "roadblock_zombie",
-          "count": 2,
+          "count": 1,
           "spawnInterval": 2.0,
           "spawnDelay": 0.0,
           "characteristics": "高生命, 慢速移动",

+ 0 - 8
assets/data/levels/Level10.json

@@ -333,14 +333,6 @@
           "spawnDelay": 12.0,
           "characteristics": "超高生命, 慢速移动",
           "healthMultiplier": 3.7
-        },
-        {
-          "enemyType": "终极BOSS",
-          "count": 1,
-          "spawnInterval": 0.0,
-          "spawnDelay": 30.0,
-          "characteristics": "极高生命, 全屏攻击, 无敌阶段, 召唤军团",
-          "healthMultiplier": 3.7
         }
       ]
     }

+ 0 - 8
assets/data/levels/Level15.json

@@ -319,14 +319,6 @@
           "spawnDelay": 12.0,
           "characteristics": "超高生命, 慢速移动",
           "healthMultiplier": 3.7
-        },
-        {
-          "enemyType": "终极BOSS",
-          "count": 1,
-          "spawnInterval": 60.0,
-          "spawnDelay": 30.0,
-          "characteristics": "超高生命, 范围攻击, 召唤增援",
-          "healthMultiplier": 3.7
         }
       ]
     }

+ 0 - 8
assets/data/levels/Level5.json

@@ -313,14 +313,6 @@
           "spawnDelay": 10.0,
           "characteristics": "远程弓箭攻击",
           "healthMultiplier": 3.7
-        },
-        {
-          "enemyType": "终极BOSS",
-          "count": 1,
-          "spawnInterval": 60.0,
-          "spawnDelay": 30.0,
-          "characteristics": "超高生命, 多种攻击方式, 召唤小怪",
-          "healthMultiplier": 3.7
         }
       ]
     }

+ 8 - 8
assets/data/weapons.json

@@ -5,7 +5,7 @@
       "name": "毛豆射手",
       "type": "single_shot",
       "weight": 30,
-      "unlockLevel": 1,
+      "unlockLevel": 0,
       "rarityDamageMultipliers": [
         1.0,
         2.0,
@@ -240,7 +240,7 @@
       "name": "锯齿草",
       "type": "ricochet_piercing",
       "weight": 20,
-      "unlockLevel": 1,
+      "unlockLevel": 3,
       "rarityDamageMultipliers": [
         1.0,
         2.0,
@@ -364,7 +364,7 @@
       "name": "西瓜炸弹",
       "type": "explosive",
       "weight": 15,
-      "unlockLevel": 1,
+      "unlockLevel": 5,
       "rarityDamageMultipliers": [
         1.0,
         2.0,
@@ -483,7 +483,7 @@
       "name": "回旋镖盆栽",
       "type": "boomerang",
       "weight": 18,
-      "unlockLevel": 1,
+      "unlockLevel": 7,
       "rarityDamageMultipliers": [
         1.0,
         2.0,
@@ -601,7 +601,7 @@
       "name": "炙热辣椒",
       "type": "area_burn",
       "weight": 12,
-      "unlockLevel": 1,
+      "unlockLevel": 9,
       "rarityDamageMultipliers": [
         1.0,
         2.0,
@@ -726,7 +726,7 @@
       "name": "仙人散弹",
       "type": "shotgun",
       "weight": 22,
-      "unlockLevel": 1,
+      "unlockLevel": 11,
       "rarityDamageMultipliers": [
         1.0,
         2.0,
@@ -843,7 +843,7 @@
       "name": "秋葵导弹",
       "type": "homing_missile",
       "weight": 8,
-      "unlockLevel": 1,
+      "unlockLevel": 13,
       "rarityDamageMultipliers": [
         1.0,
         2.0,
@@ -963,7 +963,7 @@
       "name": "狼牙棒",
       "type": "melee",
       "weight": 20,
-      "unlockLevel": 1,
+      "unlockLevel": 15,
       "rarityDamageMultipliers": [
         1.0,
         2.0,

+ 0 - 1617
assets/data/weapons_backup_20251023_143857.json

@@ -1,1617 +0,0 @@
-{
-  "weapons": [
-    {
-      "id": "pea_shooter",
-      "name": "毛豆射手",
-      "type": "single_shot",
-      "weight": 30,
-      "unlockLevel": 1,
-      "rarityDamageMultipliers": [
-        1.0,
-        2.0,
-        3.0,
-        4.0,
-        5.0
-      ],
-      "stats": {
-        "damage": 10,
-        "fireRate": 4.0,
-        "range": 500,
-        "bulletSpeed": 40
-      },
-      "bulletConfig": {
-        "count": {
-          "type": "single",
-          "amount": 1,
-          "spreadAngle": 0,
-          "burstCount": 1,
-          "burstDelay": 2
-        },
-        "trajectory": {
-          "type": "straight",
-          "speed": 200,
-          "gravity": 0,
-          "arcHeight": 0,
-          "homingStrength": 0,
-          "homingDelay": 0
-        },
-        "hitEffects": [
-          {
-            "type": "normal_damage",
-            "priority": 1,
-            "damage": 10
-          }
-        ],
-        "lifecycle": {
-          "type": "hit_destroy",
-          "maxLifetime": 5.0,
-          "penetration": 1,
-          "ricochetCount": 0,
-          "returnToOrigin": false
-        },
-        "visual": {
-          "bulletImages": "images/PlantsSprite/010",
-          "hitEffect": "Animation/WeaponTx/tx0002/tx0002",
-          "trailEffect": true
-        }
-      },
-      "visualConfig": {
-        "weaponSprites": "images/PlantsSprite/001-1",
-        "attackSound": "data/弹球音效/bean atk"
-      },
-      "upgradeConfig": {
-        "maxLevel": 10,
-        "levels": {
-          "1": {
-            "cost": 25,
-            "damage": 10
-          },
-          "2": {
-            "cost": 50,
-            "damage": 11
-          },
-          "3": {
-            "cost": 75,
-            "damage": 12
-          },
-          "4": {
-            "cost": 100,
-            "damage": 13
-          },
-          "5": {
-            "cost": 125,
-            "damage": 14
-          },
-          "6": {
-            "cost": 150,
-            "damage": 15
-          },
-          "7": {
-            "cost": 175,
-            "damage": 16
-          },
-          "8": {
-            "cost": 200,
-            "damage": 17
-          },
-          "9": {
-            "cost": 225,
-            "damage": 18
-          },
-          "10": {
-            "cost": 250,
-            "damage": 19
-          }
-        }
-      },
-      "inGameCostConfig": {
-        "baseCost": 10,
-        "shapeCosts": {
-          "I": 10,
-          "H-I": 10,
-          "L": 15,
-          "S": 20,
-          "D-T": 20,
-          "T": 15
-        }
-      }
-    },
-    {
-      "id": "sharp_carrot",
-      "name": "尖胡萝卜",
-      "type": "piercing",
-      "weight": 25,
-      "unlockLevel": 2,
-      "rarityDamageMultipliers": [
-        1.0,
-        2.0,
-        3.0,
-        4.0,
-        5.0
-      ],
-      "stats": {
-        "damage": 7,
-        "fireRate": 4.0,
-        "range": 500,
-        "bulletSpeed": 20
-      },
-      "bulletConfig": {
-        "count": {
-          "type": "single",
-          "amount": 1,
-          "spreadAngle": 0,
-          "burstCount": 1,
-          "burstDelay": 2
-        },
-        "trajectory": {
-          "type": "straight",
-          "speed": 200,
-          "gravity": 0,
-          "arcHeight": 0,
-          "homingStrength": 0,
-          "homingDelay": 0
-        },
-        "hitEffects": [
-          {
-            "type": "pierce_damage",
-            "priority": 1,
-            "damage": 8,
-            "pierceCount": 999
-          }
-        ],
-        "lifecycle": {
-          "type": "range_limit",
-          "maxLifetime": 5.0,
-          "penetration": 999,
-          "ricochetCount": 0,
-          "returnToOrigin": false,
-          "maxRange": 800
-        },
-        "visual": {
-          "bulletImages": "images/PlantsSprite/002",
-          "hitEffect": "Animation/WeaponTx/tx0002/tx0002",
-          "trailEffect": "Animation/WeaponTx/tx0001/tx0001"
-        },
-        "shouldRotate": false
-      },
-      "visualConfig": {
-        "weaponSprites": "images/PlantsSprite/002",
-        "attackSound": "data/弹球音效/cawl"
-      },
-      "upgradeConfig": {
-        "maxLevel": 10,
-        "levels": {
-          "1": {
-            "cost": 25,
-            "damage": 7
-          },
-          "2": {
-            "cost": 50,
-            "damage": 8
-          },
-          "3": {
-            "cost": 75,
-            "damage": 9
-          },
-          "4": {
-            "cost": 100,
-            "damage": 10
-          },
-          "5": {
-            "cost": 125,
-            "damage": 11
-          },
-          "6": {
-            "cost": 150,
-            "damage": 12
-          },
-          "7": {
-            "cost": 175,
-            "damage": 13
-          },
-          "8": {
-            "cost": 200,
-            "damage": 14
-          },
-          "9": {
-            "cost": 225,
-            "damage": 15
-          },
-          "10": {
-            "cost": 250,
-            "damage": 16
-          }
-        }
-      },
-      "inGameCostConfig": {
-        "baseCost": 15,
-        "shapeCosts": {
-          "I": 15,
-          "H-I": 15,
-          "L": 20,
-          "S": 25,
-          "D-T": 25,
-          "T": 20
-        }
-      }
-    },
-    {
-      "id": "saw_grass",
-      "name": "锯齿草",
-      "type": "ricochet_piercing",
-      "weight": 20,
-      "unlockLevel": 3,
-      "rarityDamageMultipliers": [
-        1.0,
-        2.0,
-        3.0,
-        4.0,
-        5.0
-      ],
-      "stats": {
-        "damage": 5,
-        "fireRate": 4.0,
-        "range": 500,
-        "bulletSpeed": 30
-      },
-      "bulletConfig": {
-        "count": {
-          "type": "single",
-          "amount": 1,
-          "spreadAngle": 0,
-          "burstCount": 1,
-          "burstDelay": 2
-        },
-        "trajectory": {
-          "type": "straight",
-          "speed": 200,
-          "gravity": 0,
-          "arcHeight": 0,
-          "homingStrength": 0,
-          "homingDelay": 0
-        },
-        "hitEffects": [
-          {
-            "type": "ricochet_damage",
-            "priority": 1,
-            "damage": 8,
-            "ricochetCount": 2,
-            "ricochetAngle": 45
-          },
-          {
-            "type": "pierce_damage",
-            "priority": 2,
-            "damage": 8,
-            "pierceCount": 3
-          }
-        ],
-        "lifecycle": {
-          "type": "ricochet_counter",
-          "maxLifetime": 8.0,
-          "penetration": 3,
-          "ricochetCount": 3,
-          "returnToOrigin": false
-        },
-        "visual": {
-          "bulletImages": "images/PlantsSprite/003",
-          "hitEffect": "Animation/WeaponTx/tx0002/tx0002",
-          "trailEffect": true
-        }
-      },
-      "visualConfig": {
-        "weaponSprites": "images/PlantsSprite/003",
-        "attackSound": "data/弹球音效/juchi atk"
-      },
-      "upgradeConfig": {
-        "maxLevel": 10,
-        "levels": {
-          "1": {
-            "cost": 25,
-            "damage": 5
-          },
-          "2": {
-            "cost": 50,
-            "damage": 6
-          },
-          "3": {
-            "cost": 75,
-            "damage": 7
-          },
-          "4": {
-            "cost": 100,
-            "damage": 8
-          },
-          "5": {
-            "cost": 125,
-            "damage": 9
-          },
-          "6": {
-            "cost": 150,
-            "damage": 10
-          },
-          "7": {
-            "cost": 175,
-            "damage": 11
-          },
-          "8": {
-            "cost": 200,
-            "damage": 12
-          },
-          "9": {
-            "cost": 225,
-            "damage": 13
-          },
-          "10": {
-            "cost": 250,
-            "damage": 14
-          }
-        }
-      },
-      "inGameCostConfig": {
-        "baseCost": 20,
-        "shapeCosts": {
-          "I": 20,
-          "H-I": 20,
-          "L": 25,
-          "S": 30,
-          "D-T": 30,
-          "T": 25
-        }
-      }
-    },
-    {
-      "id": "watermelon_bomb",
-      "name": "西瓜炸弹",
-      "type": "explosive",
-      "weight": 15,
-      "unlockLevel": 5,
-      "rarityDamageMultipliers": [
-        1.0,
-        2.0,
-        3.0,
-        4.0,
-        5.0
-      ],
-      "stats": {
-        "damage": 5,
-        "fireRate": 4.0,
-        "range": 1000,
-        "bulletSpeed": 20
-      },
-      "bulletConfig": {
-        "count": {
-          "type": "single",
-          "amount": 1,
-          "spreadAngle": 0,
-          "burstCount": 1,
-          "burstDelay": 2
-        },
-        "trajectory": {
-          "type": "arc",
-          "speed": 200,
-          "gravity": 0,
-          "arcHeight": 0,
-          "homingStrength": 0,
-          "homingDelay": 0
-        },
-        "hitEffects": [
-          {
-            "type": "explosion",
-            "priority": 1,
-            "damage": 35,
-            "radius": 100,
-            "delay": 0.1
-          }
-        ],
-        "lifecycle": {
-          "type": "ground_impact",
-          "maxLifetime": 5.0,
-          "penetration": 1,
-          "ricochetCount": 0,
-          "returnToOrigin": false
-        },
-        "visual": {
-          "bulletImages": "images/PlantsSprite/007",
-          "hitEffect": "Animation/WeaponTx/tx0007/tx0007",
-          "trailEffect": true,
-          "explosionEffect": "Animation/WeaponTx/tx0007/tx0007"
-        }
-      },
-      "visualConfig": {
-        "weaponSprites": "images/PlantsSprite/007",
-        "attackSound": "data/弹球音效/bomb"
-      },
-      "upgradeConfig": {
-        "maxLevel": 10,
-        "levels": {
-          "1": {
-            "cost": 125,
-            "damage": 5
-          },
-          "2": {
-            "cost": 150,
-            "damage": 6
-          },
-          "3": {
-            "cost": 175,
-            "damage": 7
-          },
-          "4": {
-            "cost": 200,
-            "damage": 8
-          },
-          "5": {
-            "cost": 225,
-            "damage": 9
-          },
-          "6": {
-            "cost": 250,
-            "damage": 10
-          },
-          "7": {
-            "cost": 275,
-            "damage": 11
-          },
-          "8": {
-            "cost": 300,
-            "damage": 12
-          },
-          "9": {
-            "cost": 325,
-            "damage": 13
-          },
-          "10": {
-            "cost": 350,
-            "damage": 14
-          }
-        }
-      },
-      "inGameCostConfig": {
-        "baseCost": 15,
-        "shapeCosts": {
-          "I": 15,
-          "H-I": 15,
-          "L": 20,
-          "S": 25,
-          "D-T": 25,
-          "T": 20
-        }
-      }
-    },
-    {
-      "id": "boomerang_plant",
-      "name": "回旋镖盆栽",
-      "type": "boomerang",
-      "weight": 18,
-      "unlockLevel": 7,
-      "rarityDamageMultipliers": [
-        1.0,
-        2.0,
-        3.0,
-        4.0,
-        5.0
-      ],
-      "stats": {
-        "damage": 7,
-        "fireRate": 4.0,
-        "range": 1000,
-        "bulletSpeed": 20
-      },
-      "bulletConfig": {
-        "count": {
-          "type": "single",
-          "amount": 1,
-          "spreadAngle": 0,
-          "burstCount": 1,
-          "burstDelay": 2
-        },
-        "trajectory": {
-          "type": "arc",
-          "speed": 15,
-          "gravity": 0,
-          "rotateSpeed": 0.6,
-          "homingStrength": 0.5,
-          "homingDelay": 0.3
-        },
-        "hitEffects": [
-          {
-            "type": "pierce_damage",
-            "priority": 1,
-            "damage": 10,
-            "pierceCount": 999
-          }
-        ],
-        "lifecycle": {
-          "type": "return_trip",
-          "maxLifetime": 10.0,
-          "penetration": 999,
-          "ricochetCount": 0,
-          "returnToOrigin": true,
-          "returnDelay": 2.0
-        },
-        "visual": {
-          "bulletImages": "images/PlantsSprite/004",
-          "hitEffect": "Animation/WeaponTx/tx0002/tx0002",
-          "trailEffect": true
-        }
-      },
-      "visualConfig": {
-        "weaponSprites": "images/PlantsSprite/004",
-        "attackSound": "data/弹球音效/huixuanbiao atk"
-      },
-      "upgradeConfig": {
-        "maxLevel": 10,
-        "levels": {
-          "1": {
-            "cost": 125,
-            "damage": 7
-          },
-          "2": {
-            "cost": 150,
-            "damage": 8
-          },
-          "3": {
-            "cost": 175,
-            "damage": 9
-          },
-          "4": {
-            "cost": 200,
-            "damage": 10
-          },
-          "5": {
-            "cost": 225,
-            "damage": 11
-          },
-          "6": {
-            "cost": 250,
-            "damage": 12
-          },
-          "7": {
-            "cost": 275,
-            "damage": 13
-          },
-          "8": {
-            "cost": 300,
-            "damage": 14
-          },
-          "9": {
-            "cost": 325,
-            "damage": 15
-          },
-          "10": {
-            "cost": 350,
-            "damage": 16
-          }
-        }
-      },
-      "inGameCostConfig": {
-        "baseCost": 15,
-        "shapeCosts": {
-          "I": 15,
-          "H-I": 15,
-          "L": 20,
-          "S": 25,
-          "D-T": 25,
-          "T": 20
-        }
-      }
-    },
-    {
-      "id": "hot_pepper",
-      "name": "炙热辣椒",
-      "type": "area_burn",
-      "weight": 12,
-      "unlockLevel": 9,
-      "rarityDamageMultipliers": [
-        1.0,
-        2.0,
-        3.0,
-        4.0,
-        5.0
-      ],
-      "stats": {
-        "damage": 2,
-        "fireRate": 4.0,
-        "range": 500,
-        "bulletSpeed": 20
-      },
-      "bulletConfig": {
-        "count": {
-          "type": "single",
-          "amount": 1,
-          "spreadAngle": 0,
-          "burstCount": 1,
-          "burstDelay": 2
-        },
-        "trajectory": {
-          "type": "straight",
-          "speed": 200,
-          "gravity": 0,
-          "arcHeight": 0,
-          "homingStrength": 0,
-          "homingDelay": 0
-        },
-        "hitEffects": [
-          {
-            "type": "normal_damage",
-            "priority": 1,
-            "damage": 15
-          },
-          {
-            "type": "ground_burn",
-            "priority": 2,
-            "damage": 5,
-            "duration": 3.0,
-            "tickInterval": 0.5
-          }
-        ],
-        "lifecycle": {
-          "type": "hit_destroy",
-          "maxLifetime": 5.0,
-          "penetration": 1,
-          "ricochetCount": 0,
-          "returnToOrigin": false
-        },
-        "visual": {
-          "bulletImages": "images/PlantsSprite/005",
-          "hitEffect": "Animation/WeaponTx/tx0002/tx0002",
-          "trailEffect": true,
-          "burnEffect": "Animation/WeaponBurnAni/燃烧/ui_zhuling"
-        },
-        "shouldRotate": false
-      },
-      "visualConfig": {
-        "weaponSprites": "images/PlantsSprite/005",
-        "attackSound": "data/弹球音效/fire"
-      },
-      "upgradeConfig": {
-        "maxLevel": 10,
-        "levels": {
-          "1": {
-            "cost": 125,
-            "damage": 2
-          },
-          "2": {
-            "cost": 150,
-            "damage": 3
-          },
-          "3": {
-            "cost": 175,
-            "damage": 4
-          },
-          "4": {
-            "cost": 200,
-            "damage": 5
-          },
-          "5": {
-            "cost": 225,
-            "damage": 6
-          },
-          "6": {
-            "cost": 250,
-            "damage": 7
-          },
-          "7": {
-            "cost": 275,
-            "damage": 8
-          },
-          "8": {
-            "cost": 300,
-            "damage": 9
-          },
-          "9": {
-            "cost": 325,
-            "damage": 10
-          },
-          "10": {
-            "cost": 350,
-            "damage": 11
-          }
-        }
-      },
-      "inGameCostConfig": {
-        "baseCost": 15,
-        "shapeCosts": {
-          "I": 15,
-          "H-I": 15,
-          "L": 20,
-          "S": 25,
-          "D-T": 25,
-          "T": 20
-        }
-      }
-    },
-    {
-      "id": "cactus_shotgun",
-      "name": "仙人散弹",
-      "type": "shotgun",
-      "weight": 22,
-      "unlockLevel": 11,
-      "rarityDamageMultipliers": [
-        1.0,
-        2.0,
-        3.0,
-        4.0,
-        5.0
-      ],
-      "stats": {
-        "damage": 8,
-        "fireRate": 4.0,
-        "range": 500,
-        "bulletSpeed": 40
-      },
-      "bulletConfig": {
-        "count": {
-          "type": "spread",
-          "amount": 5,
-          "spreadAngle": 30,
-          "burstCount": 1,
-          "burstDelay": 2
-        },
-        "trajectory": {
-          "type": "straight",
-          "speed": 200,
-          "gravity": 0,
-          "arcHeight": 0,
-          "homingStrength": 0,
-          "homingDelay": 2
-        },
-        "hitEffects": [
-          {
-            "type": "normal_damage",
-            "priority": 1,
-            "damage": 6
-          }
-        ],
-        "lifecycle": {
-          "type": "range_limit",
-          "maxLifetime": 5.0,
-          "penetration": 1,
-          "ricochetCount": 0,
-          "returnToOrigin": false,
-          "maxRange": 1000
-        },
-        "visual": {
-          "bulletImages": "images/PlantsSprite/008",
-          "hitEffect": "Animation/WeaponTx/tx0002/tx0002",
-          "trailEffect": true
-        }
-      },
-      "visualConfig": {
-        "weaponSprites": "images/PlantsSprite/008",
-        "attackSound": "data/弹球音效/xianrenzhang hit"
-      },
-      "upgradeConfig": {
-        "maxLevel": 10,
-        "levels": {
-          "1": {
-            "cost": 325,
-            "damage": 8
-          },
-          "2": {
-            "cost": 350,
-            "damage": 9
-          },
-          "3": {
-            "cost": 375,
-            "damage": 10
-          },
-          "4": {
-            "cost": 400,
-            "damage": 11
-          },
-          "5": {
-            "cost": 425,
-            "damage": 12
-          },
-          "6": {
-            "cost": 450,
-            "damage": 13
-          },
-          "7": {
-            "cost": 475,
-            "damage": 14
-          },
-          "8": {
-            "cost": 500,
-            "damage": 15
-          },
-          "9": {
-            "cost": 525,
-            "damage": 16
-          },
-          "10": {
-            "cost": 550,
-            "damage": 17
-          }
-        }
-      },
-      "inGameCostConfig": {
-        "baseCost": 20,
-        "shapeCosts": {
-          "I": 20,
-          "H-I": 20,
-          "L": 25,
-          "S": 30,
-          "D-T": 30,
-          "T": 25
-        }
-      }
-    },
-    {
-      "id": "okra_missile",
-      "name": "秋葵导弹",
-      "type": "homing_missile",
-      "weight": 8,
-      "unlockLevel": 13,
-      "rarityDamageMultipliers": [
-        1.0,
-        2.0,
-        3.0,
-        4.0,
-        5.0
-      ],
-      "stats": {
-        "damage": 15,
-        "fireRate": 4.0,
-        "range": 500,
-        "bulletSpeed": 15
-      },
-      "bulletConfig": {
-        "count": {
-          "type": "single",
-          "amount": 1,
-          "spreadAngle": 0,
-          "burstCount": 1,
-          "burstDelay": 2
-        },
-        "trajectory": {
-          "type": "arc",
-          "speed": 200,
-          "gravity": 0,
-          "arcHeight": 0,
-          "homingStrength": 0,
-          "homingDelay": 0
-        },
-        "hitEffects": [
-          {
-            "type": "explosion",
-            "priority": 1,
-            "damage": 20,
-            "radius": 150,
-            "delay": 0
-          }
-        ],
-        "lifecycle": {
-          "type": "ground_impact",
-          "maxLifetime": 5.0,
-          "penetration": 1,
-          "ricochetCount": 0,
-          "returnToOrigin": false
-        },
-        "visual": {
-          "bulletImages": "images/PlantsSprite/006",
-          "hitEffect": "Animation/WeaponTx/tx0002/tx0002",
-          "trailEffect": true,
-          "explosionEffect": "Animation/WeaponTx/tx0007/tx0007"
-        },
-        "shouldRotate": false
-      },
-      "visualConfig": {
-        "weaponSprites": "images/PlantsSprite/006",
-        "attackSound": "data/弹球音效/qiukui hit"
-      },
-      "upgradeConfig": {
-        "maxLevel": 10,
-        "levels": {
-          "1": {
-            "cost": 325,
-            "damage": 15
-          },
-          "2": {
-            "cost": 350,
-            "damage": 16
-          },
-          "3": {
-            "cost": 375,
-            "damage": 17
-          },
-          "4": {
-            "cost": 400,
-            "damage": 18
-          },
-          "5": {
-            "cost": 425,
-            "damage": 19
-          },
-          "6": {
-            "cost": 450,
-            "damage": 20
-          },
-          "7": {
-            "cost": 475,
-            "damage": 21
-          },
-          "8": {
-            "cost": 500,
-            "damage": 22
-          },
-          "9": {
-            "cost": 525,
-            "damage": 23
-          },
-          "10": {
-            "cost": 550,
-            "damage": 24
-          }
-        }
-      },
-      "inGameCostConfig": {
-        "baseCost": 20,
-        "shapeCosts": {
-          "I": 20,
-          "H-I": 20,
-          "L": 25,
-          "S": 30,
-          "D-T": 30,
-          "T": 25
-        }
-      }
-    },
-    {
-      "id": "mace_club",
-      "name": "狼牙棒",
-      "type": "melee",
-      "weight": 20,
-      "unlockLevel": 15,
-      "rarityDamageMultipliers": [
-        1.0,
-        2.0,
-        3.0,
-        4.0,
-        5.0
-      ],
-      "stats": {
-        "damage": 10,
-        "fireRate": 4.0,
-        "range": 500,
-        "bulletSpeed": 35
-      },
-      "bulletConfig": {
-        "count": {
-          "type": "single",
-          "amount": 1,
-          "spreadAngle": 0,
-          "burstCount": 1,
-          "burstDelay": 0
-        },
-        "trajectory": {
-          "type": "straight",
-          "speed": 250,
-          "gravity": 0,
-          "arcHeight": 0,
-          "homingStrength": 0,
-          "homingDelay": 0
-        },
-        "hitEffects": [
-          {
-            "type": "normal_damage",
-            "priority": 1,
-            "damage": 25
-          },
-          {
-            "type": "knockback",
-            "priority": 2,
-            "force": 150
-          }
-        ],
-        "lifecycle": {
-          "type": "range_limit",
-          "maxLifetime": 3.0,
-          "penetration": 2,
-          "ricochetCount": 0,
-          "returnToOrigin": false,
-          "maxRange": 200
-        },
-        "visual": {
-          "bulletImages": "images/PlantsSprite/009",
-          "hitEffect": "Animation/WeaponTx/tx0002/tx0002",
-          "trailEffect": true
-        },
-        "shouldRotate": false
-      },
-      "visualConfig": {
-        "weaponSprites": "images/PlantsSprite/009",
-        "attackSound": "data/弹球音效/mace_club_hit"
-      },
-      "upgradeConfig": {
-        "maxLevel": 10,
-        "levels": {
-          "1": {
-            "cost": 325,
-            "damage": 10
-          },
-          "2": {
-            "cost": 350,
-            "damage": 11
-          },
-          "3": {
-            "cost": 375,
-            "damage": 12
-          },
-          "4": {
-            "cost": 400,
-            "damage": 13
-          },
-          "5": {
-            "cost": 425,
-            "damage": 14
-          },
-          "6": {
-            "cost": 450,
-            "damage": 15
-          },
-          "7": {
-            "cost": 475,
-            "damage": 16
-          },
-          "8": {
-            "cost": 500,
-            "damage": 17
-          },
-          "9": {
-            "cost": 525,
-            "damage": 18
-          },
-          "10": {
-            "cost": 550,
-            "damage": 19
-          }
-        }
-      },
-      "inGameCostConfig": {
-        "baseCost": 15,
-        "shapeCosts": {
-          "I": 15,
-          "H-I": 15,
-          "L": 20,
-          "S": 25,
-          "D-T": 25,
-          "T": 20
-        }
-      }
-    }
-  ],
-  "blockSizes": [
-    {
-      "id": "I",
-      "name": "I形",
-      "shape": [
-        [
-          [
-            1,
-            0,
-            0,
-            0
-          ],
-          [
-            1,
-            0,
-            0,
-            0
-          ],
-          [
-            0,
-            0,
-            0,
-            0
-          ],
-          [
-            0,
-            0,
-            0,
-            0
-          ]
-        ],
-        [
-          0,
-          0,
-          0,
-          0
-        ],
-        [
-          0,
-          0,
-          0,
-          0
-        ],
-        [
-          0,
-          0,
-          0,
-          0
-        ]
-      ],
-      "gridCount": 2,
-      "costMultiplier": 2,
-      "description": "最简单的直线形状"
-    },
-    {
-      "id": "H-I",
-      "name": "横I形",
-      "shape": [
-        [
-          [
-            0,
-            0,
-            0,
-            0
-          ],
-          [
-            1,
-            1,
-            0,
-            0
-          ],
-          [
-            0,
-            0,
-            0,
-            0
-          ],
-          [
-            0,
-            0,
-            0,
-            0
-          ]
-        ],
-        [
-          0,
-          0,
-          0,
-          0
-        ],
-        [
-          0,
-          0,
-          0,
-          0
-        ],
-        [
-          0,
-          0,
-          0,
-          0
-        ]
-      ],
-      "gridCount": 2,
-      "costMultiplier": 2,
-      "description": "水平直线形状"
-    },
-    {
-      "id": "L",
-      "name": "L形",
-      "shape": [
-        [
-          [
-            1,
-            0,
-            0,
-            0
-          ],
-          [
-            1,
-            1,
-            0,
-            0
-          ],
-          [
-            0,
-            0,
-            0,
-            0
-          ],
-          [
-            0,
-            0,
-            0,
-            0
-          ]
-        ],
-        [
-          0,
-          0,
-          0,
-          0
-        ],
-        [
-          0,
-          0,
-          0,
-          0
-        ],
-        [
-          0,
-          0,
-          0,
-          0
-        ]
-      ],
-      "gridCount": 3,
-      "costMultiplier": 3,
-      "description": "L型左上转角形状"
-    },
-    {
-      "id": "S",
-      "name": "S形",
-      "shape": [
-        [
-          [
-            0,
-            1,
-            1,
-            0
-          ],
-          [
-            1,
-            1,
-            0,
-            0
-          ],
-          [
-            0,
-            0,
-            0,
-            0
-          ],
-          [
-            0,
-            0,
-            0,
-            0
-          ]
-        ],
-        [
-          0,
-          0,
-          0,
-          0
-        ],
-        [
-          0,
-          0,
-          0,
-          0
-        ],
-        [
-          0,
-          0,
-          0,
-          0
-        ]
-      ],
-      "gridCount": 4,
-      "costMultiplier": 4,
-      "description": "S型曲线形状"
-    },
-    {
-      "id": "D-T",
-      "name": "倒T形",
-      "shape": [
-        [
-          [
-            0,
-            1,
-            0,
-            0
-          ],
-          [
-            1,
-            1,
-            1,
-            0
-          ],
-          [
-            0,
-            0,
-            0,
-            0
-          ],
-          [
-            0,
-            0,
-            0,
-            0
-          ]
-        ],
-        [
-          0,
-          0,
-          0,
-          0
-        ],
-        [
-          0,
-          0,
-          0,
-          0
-        ],
-        [
-          0,
-          0,
-          0,
-          0
-        ]
-      ],
-      "gridCount": 4,
-      "costMultiplier": 4,
-      "description": "倒T型形状"
-    },
-    {
-      "id": "L2",
-      "name": "L2形",
-      "shape": [
-        [
-          [
-            1,
-            1,
-            0,
-            0
-          ],
-          [
-            0,
-            1,
-            0,
-            0
-          ],
-          [
-            0,
-            0,
-            0,
-            0
-          ],
-          [
-            0,
-            0,
-            0,
-            0
-          ]
-        ],
-        [
-          0,
-          0,
-          0,
-          0
-        ],
-        [
-          0,
-          0,
-          0,
-          0
-        ],
-        [
-          0,
-          0,
-          0,
-          0
-        ]
-      ],
-      "gridCount": 3,
-      "costMultiplier": 3,
-      "description": "L2型右上转角形状"
-    },
-    {
-      "id": "L3",
-      "name": "L3形",
-      "shape": [
-        [
-          [
-            1,
-            0,
-            0,
-            0
-          ],
-          [
-            1,
-            1,
-            0,
-            0
-          ],
-          [
-            0,
-            0,
-            0,
-            0
-          ],
-          [
-            0,
-            0,
-            0,
-            0
-          ]
-        ],
-        [
-          0,
-          0,
-          0,
-          0
-        ],
-        [
-          0,
-          0,
-          0,
-          0
-        ],
-        [
-          0,
-          0,
-          0,
-          0
-        ]
-      ],
-      "gridCount": 3,
-      "costMultiplier": 3,
-      "description": "L3型左下转角形状"
-    },
-    {
-      "id": "L4",
-      "name": "L4形",
-      "shape": [
-        [
-          [
-            0,
-            1,
-            0,
-            0
-          ],
-          [
-            1,
-            1,
-            0,
-            0
-          ],
-          [
-            0,
-            0,
-            0,
-            0
-          ],
-          [
-            0,
-            0,
-            0,
-            0
-          ]
-        ],
-        [
-          0,
-          0,
-          0,
-          0
-        ],
-        [
-          0,
-          0,
-          0,
-          0
-        ],
-        [
-          0,
-          0,
-          0,
-          0
-        ]
-      ],
-      "gridCount": 3,
-      "costMultiplier": 3,
-      "description": "L4型右下转角形状"
-    },
-    {
-      "id": "S-F",
-      "name": "S-F形",
-      "shape": [
-        [
-          [
-            1,
-            1,
-            0,
-            0
-          ],
-          [
-            0,
-            1,
-            1,
-            0
-          ],
-          [
-            0,
-            0,
-            0,
-            0
-          ],
-          [
-            0,
-            0,
-            0,
-            0
-          ]
-        ],
-        [
-          0,
-          0,
-          0,
-          0
-        ],
-        [
-          0,
-          0,
-          0,
-          0
-        ],
-        [
-          0,
-          0,
-          0,
-          0
-        ]
-      ],
-      "gridCount": 4,
-      "costMultiplier": 4,
-      "description": "S型旋转镜像形状"
-    },
-    {
-      "id": "T",
-      "name": "T形",
-      "shape": [
-        [
-          [
-            1,
-            1,
-            1,
-            0
-          ],
-          [
-            0,
-            1,
-            0,
-            0
-          ],
-          [
-            0,
-            0,
-            0,
-            0
-          ],
-          [
-            0,
-            0,
-            0,
-            0
-          ]
-        ],
-        [
-          0,
-          0,
-          0,
-          0
-        ],
-        [
-          0,
-          0,
-          0,
-          0
-        ],
-        [
-          0,
-          0,
-          0,
-          0
-        ]
-      ],
-      "gridCount": 4,
-      "costMultiplier": 4,
-      "description": "T型形状"
-    }
-  ]
-}

+ 0 - 11
assets/data/weapons_backup_20251023_143857.json.meta

@@ -1,11 +0,0 @@
-{
-  "ver": "2.0.1",
-  "importer": "json",
-  "imported": true,
-  "uuid": "bcdc90af-7e71-4215-86e6-3eabbd35a4b6",
-  "files": [
-    ".json"
-  ],
-  "subMetas": {},
-  "userData": {}
-}

+ 42 - 11
assets/scripts/Animations/MoneyAni.ts

@@ -62,6 +62,11 @@ export class MoneyAni extends Component {
     
     private saveDataManager: SaveDataManager = null;
     
+    // 新增:奖励动画延迟的状态与挂起数据
+    private gatingEnabled: boolean = false;
+    private pendingReward: { money: number; diamonds: number } | null = null;
+    private pendingOnComplete: (() => void) | null = null;
+    
     // 新增:统一派发奖励动画完成事件
     private emitRewardCompleted(money: number, diamonds: number): void {
         EventBus.getInstance().emit(GameEvents.REWARD_ANIMATION_COMPLETED, { money, diamonds });
@@ -72,6 +77,9 @@ export class MoneyAni extends Component {
         
         // 监听奖励动画事件
         EventBus.getInstance().on('PLAY_REWARD_ANIMATION', this.onPlayRewardAnimation, this);
+        // 新增:监听显示GainUI与确认事件
+        EventBus.getInstance().on(GameEvents.SHOW_GAIN_UI, this.onShowGainUI, this);
+        EventBus.getInstance().on(GameEvents.GAIN_UI_CONFIRMED, this.onGainUIConfirmed, this);
     }
     
     /**
@@ -85,6 +93,8 @@ export class MoneyAni extends Component {
     onDestroy() {
         // 移除事件监听器
         EventBus.getInstance().off('PLAY_REWARD_ANIMATION', this.onPlayRewardAnimation, this);
+        EventBus.getInstance().off(GameEvents.SHOW_GAIN_UI, this.onShowGainUI, this);
+        EventBus.getInstance().off(GameEvents.GAIN_UI_CONFIRMED, this.onGainUIConfirmed, this);
     }
     
     /**
@@ -94,6 +104,12 @@ export class MoneyAni extends Component {
      * @param onComplete 动画完成回调
      */
     public playRewardAnimation(coinAmount: number, diamondAmount: number, onComplete?: () => void) {
+        if (this.gatingEnabled) {
+            this.pendingReward = { money: coinAmount, diamonds: diamondAmount };
+            this.pendingOnComplete = onComplete || null;
+            console.log('[MoneyAni] 已延迟奖励动画,等待GainUI确认');
+            return;
+        }
         console.log(`[MoneyAni] 开始播放奖励动画 - 钞票: ${coinAmount}, 钻石: ${diamondAmount}`);
         
         // 如果奖励为0,直接返回不播放动画
@@ -182,10 +198,7 @@ export class MoneyAni extends Component {
                 this.createAndAnimateCoin(startPos, targetNode, () => {
                     coinsCompleted++;
                     if (coinsCompleted >= coinCount) {
-                        // 所有钞票动画完成,更新数据
-                        this.saveDataManager.addMoney(amount, 'level_reward');
-                        // 通过事件系统通知UI更新
-            EventBus.getInstance().emit(GameEvents.CURRENCY_CHANGED);
+                        // 所有钞票动画完成
                         if (onComplete) onComplete();
                     }
                 });
@@ -233,10 +246,7 @@ export class MoneyAni extends Component {
                 this.createAndAnimateDiamond(startPos, targetNode, () => {
                     diamondsCompleted++;
                     if (diamondsCompleted >= diamondCount) {
-                        // 所有钻石动画完成,更新数据
-                        this.saveDataManager.addDiamonds(amount, 'level_reward');
-                        // 通过事件系统通知UI更新
-                        EventBus.getInstance().emit(GameEvents.CURRENCY_CHANGED);
+                        // 所有钻石动画完成
                         if (onComplete) onComplete();
                     }
                 });
@@ -451,16 +461,37 @@ export class MoneyAni extends Component {
         try {
             const rewards = await this.saveDataManager.getLevelRewardsFromConfig(level);
             if (rewards) {
-                this.playRewardAnimation(rewards.money, rewards.diamonds, onComplete);
+                if (this.gatingEnabled) {
+                    this.pendingReward = { money: rewards.money, diamonds: rewards.diamonds };
+                    this.pendingOnComplete = onComplete || null;
+                    console.log('[MoneyAni] 已延迟关卡奖励动画,等待GainUI确认');
+                } else {
+                    this.playRewardAnimation(rewards.money, rewards.diamonds, onComplete);
+                }
             } else {
                 console.warn(`[MoneyAni] 无法获取关卡${level}的奖励配置,使用默认奖励`);
-                // 使用默认奖励
                 this.playRewardAnimation(50, 5, onComplete);
             }
         } catch (error) {
             console.error('[MoneyAni] 获取关卡奖励配置时出错:', error);
-            // 使用默认奖励
             this.playRewardAnimation(50, 5, onComplete);
         }
     }
+
+private onShowGainUI = () => {
+    this.gatingEnabled = true;
+    console.log('[MoneyAni] 检测到SHOW_GAIN_UI,启用奖励动画延迟');
+}
+
+private onGainUIConfirmed = () => {
+    console.log('[MoneyAni] 检测到GAIN_UI_CONFIRMED,准备播放延迟的奖励动画');
+    const pending = this.pendingReward;
+    const onComplete = this.pendingOnComplete || undefined;
+    this.pendingReward = null;
+    this.pendingOnComplete = null;
+    this.gatingEnabled = false;
+    if (pending) {
+        this.playRewardAnimation(pending.money, pending.diamonds, onComplete);
+    }
+}
 }

+ 66 - 35
assets/scripts/CombatSystem/GainUI.ts

@@ -2,6 +2,7 @@ import { _decorator, Component, Node, Label, Sprite, Button } from 'cc';
 import EventBus, { GameEvents } from '../Core/EventBus';
 import { JsonConfigLoader } from '../Core/JsonConfigLoader';
 import { BundleLoader } from '../Core/BundleLoader';
+import { PopUPAni } from '../Animations/PopUPAni';
 
 const { ccclass, property } = _decorator;
 
@@ -11,6 +12,8 @@ export class GainUI extends Component {
     @property(Label) titleLabel: Label = null;        // 标题,如“新武器”
     @property(Sprite) iconSprite: Sprite = null;      // 武器图标
     @property(Label) nameLabel: Label = null;         // 武器名称
+    @property(Node) bt1: Node = null;                 // Canvas/GainUI/bt1
+    @property(Node) weaponNode: Node = null;          // Canvas/GainUI/Weapon
     @property(Button) confirmBtn: Button = null;      // 确认按钮
 
     private weaponsConfig: any = null;
@@ -22,21 +25,35 @@ export class GainUI extends Component {
     private rewardAnimationCompleted = false;
     // 当前是否正在显示弹窗
     private showing = false;
+    // 当前正在展示的武器名称(用于避免重复渲染队列首项)
+    private currentWeaponName: string | null = null;
+
+    // 辅助:获取或添加 PopUPAni 组件
+    private getOrAddPopAni(node: Node): PopUPAni | null {
+        if (!node) return null;
+        let comp = node.getComponent(PopUPAni);
+        if (!comp) {
+            comp = node.addComponent(PopUPAni);
+        }
+        return comp;
+    }
 
     async onLoad() {
         this.bundleLoader = BundleLoader.getInstance();
         // 默认隐藏弹窗
         if (this.panel) this.panel.active = false;
         console.log('[GainUI] 已隐藏弹窗(默认状态)');
-        // 绑定事件
+        // 改为仅监听GameManager派发的显示事件
+        EventBus.getInstance().on(GameEvents.SHOW_GAIN_UI, this.onShowGainUI, this);
+        // 监听新武器解锁事件,接收武器名称队列
         EventBus.getInstance().on(GameEvents.NEW_WEAPONS_UNLOCKED, this.onNewWeaponsUnlocked, this);
-        EventBus.getInstance().on(GameEvents.REWARD_ANIMATION_COMPLETED, this.onRewardAnimationCompleted, this);
 
         // 绑定确认按钮
         this.confirmBtn?.node.on(Button.EventType.CLICK, this.onConfirm, this);
     }
 
     async start() {
+        console.log('[GainUI] 已初始化,等待新武器解锁事件...');
         // 预加载武器配置,便于查找图标与名称
         try {
             this.weaponsConfig = await JsonConfigLoader.getInstance().loadConfig('weapons');
@@ -46,43 +63,11 @@ export class GainUI extends Component {
     }
 
     onDestroy() {
+        EventBus.getInstance().off(GameEvents.SHOW_GAIN_UI, this.onShowGainUI, this);
         EventBus.getInstance().off(GameEvents.NEW_WEAPONS_UNLOCKED, this.onNewWeaponsUnlocked, this);
-        EventBus.getInstance().off(GameEvents.REWARD_ANIMATION_COMPLETED, this.onRewardAnimationCompleted, this);
         this.confirmBtn?.node.off(Button.EventType.CLICK, this.onConfirm, this);
     }
 
-    // 处理新武器解锁事件
-    private onNewWeaponsUnlocked = (weaponNames: string[]) => {
-        if (!weaponNames || weaponNames.length === 0) return;
-        this.pendingWeapons.push(...weaponNames);
-        this.tryShowPopup();
-    }
-
-    // 处理奖励动画完成事件
-    private onRewardAnimationCompleted = () => {
-        this.rewardAnimationCompleted = true;
-        this.tryShowPopup();
-    }
-
-    // 在满足条件时显示弹窗:需要奖励动画已完成且队列非空
-    private async tryShowPopup() {
-        if (this.showing) return;
-        if (!this.rewardAnimationCompleted) return;
-        if (this.pendingWeapons.length === 0) return;
-
-        const nextName = this.pendingWeapons.shift();
-        await this.renderWeapon(nextName);
-        // 激活并确保置顶显示,避免被MainUI/TopBar覆盖
-        if (this.panel) {
-            this.panel.active = true;
-            const parent = this.panel.parent;
-            if (parent) {
-                this.panel.setSiblingIndex(parent.children.length - 1);
-            }
-        }
-        this.showing = true;
-    }
-
     // 确认按钮逻辑:继续展示队列中的下一个或关闭弹窗
     private async onConfirm() {
         if (this.pendingWeapons.length > 0) {
@@ -91,11 +76,26 @@ export class GainUI extends Component {
         } else {
             if (this.panel) this.panel.active = false;
             this.showing = false;
+            this.currentWeaponName = null;
+            EventBus.getInstance().emit(GameEvents.GAIN_UI_CONFIRMED);
+        }
+    }
+
+    // 接收解锁事件并更新待展示队列
+    private async onNewWeaponsUnlocked(weaponNames: string[]) {
+        if (!weaponNames || weaponNames.length === 0) return;
+        this.pendingWeapons.push(...weaponNames);
+        console.log('[GainUI] 收到新武器解锁:', weaponNames);
+        // 如果弹窗已显示且当前未渲染任何武器,则渲染第一个
+        if (this.showing && !this.currentWeaponName && this.pendingWeapons.length > 0) {
+            const first = this.pendingWeapons.shift();
+            await this.renderWeapon(first);
         }
     }
 
     // 根据武器名称渲染弹窗内容(标题、图标、名称)
     private async renderWeapon(weaponName: string) {
+        this.currentWeaponName = weaponName || null;
         if (this.titleLabel) this.titleLabel.string = '新武器';
         if (this.nameLabel) this.nameLabel.string = weaponName || '';
 
@@ -131,4 +131,35 @@ export class GainUI extends Component {
             console.warn(`[GainUI] 加载武器图标失败: ${spritePath}`, err);
         }
     }
+    // 新增:接收GameManager派发的显示事件,直接显示弹窗
+    private onShowGainUI = async () => {
+        if (this.panel) {
+            this.panel.active = true;
+            const parent = this.panel.parent;
+            if (parent) {
+                this.panel.setSiblingIndex(parent.children.length - 1);
+            }
+        }
+        this.showing = true;
+
+        // 新增:显示时让 bt1 与 Weapon 节点弹出显示
+        try {
+            const promises: Promise<void>[] = [];
+            const bt1Ani = this.getOrAddPopAni(this.bt1);
+            const weaponAni = this.getOrAddPopAni(this.weaponNode);
+            if (bt1Ani) promises.push(bt1Ani.showPanel());
+            if (weaponAni) promises.push(weaponAni.showPanel());
+            if (promises.length > 0) {
+                await Promise.all(promises);
+            }
+        } catch (e) {
+            console.warn('[GainUI] 弹出动画执行失败:', e);
+        }
+
+        // 如果已有待展示武器,立即渲染第一个(并弹出队列)
+        if (this.pendingWeapons.length > 0) {
+            const first = this.pendingWeapons.shift();
+            await this.renderWeapon(first);
+        }
+    }
 }

+ 2 - 18
assets/scripts/CombatSystem/GameEnd.ts

@@ -648,15 +648,7 @@ export class GameEnd extends Component {
         // 触发货币变化事件
         EventBus.getInstance().emit(GameEvents.CURRENCY_CHANGED);
         
-        // 派发事件给MoneyAni播放奖励动画
-        const rewards = this.saveDataManager.getLastRewards();
-        console.log('[GameEnd] 派发奖励动画事件,奖励数据:', rewards);
-        EventBus.getInstance().emit('PLAY_REWARD_ANIMATION', {
-            money: rewards.money,
-            diamonds: rewards.diamonds
-        });
-        
-        // 触发返回主菜单事件
+        // 触发返回主菜单事件(奖励动画由主界面统一播放)
         EventBus.getInstance().emit('CONTINUE_CLICK');
         
         // 隐藏结算面板(通过动画)
@@ -671,15 +663,7 @@ export class GameEnd extends Component {
         Audio.playUISound('data/弹球音效/ui play');
         console.log('[GameEnd] 点击继续按钮');
         
-        // 派发事件给MoneyAni播放奖励动画
-        const rewards = this.saveDataManager.getLastRewards();
-        console.log('[GameEnd] 派发奖励动画事件,奖励数据:', rewards);
-        EventBus.getInstance().emit('PLAY_REWARD_ANIMATION', {
-            money: rewards.money,
-            diamonds: rewards.diamonds
-        });
-        
-        // 触发返回主菜单事件
+        // 触发返回主菜单事件(奖励动画由主界面统一播放)
         EventBus.getInstance().emit('CONTINUE_CLICK');
         
         // 隐藏结算面板(通过动画)

+ 2 - 0
assets/scripts/Core/EventBus.ts

@@ -42,6 +42,8 @@ export enum GameEvents {
     CURRENCY_CHANGED = 'CURRENCY_CHANGED',
     UI_PANEL_SWITCHED = 'UI_PANEL_SWITCHED',
     APP_STATE_CHANGED = 'APP_STATE_CHANGED',
+    SHOW_GAIN_UI = 'SHOW_GAIN_UI',
+    GAIN_UI_CONFIRMED = 'GAIN_UI_CONFIRMED',
     // 新增:奖励动画与武器解锁事件
     REWARD_ANIMATION_COMPLETED = 'REWARD_ANIMATION_COMPLETED',
     NEW_WEAPONS_UNLOCKED = 'NEW_WEAPONS_UNLOCKED',

+ 10 - 3
assets/scripts/LevelSystem/GameManager.ts

@@ -569,6 +569,10 @@ export class GameManager extends Component {
                 const nextLevel = currentLevel + 1;
                 this.saveDataManager.setCurrentLevel(nextLevel);
                 console.log(`[GameManager] 游戏获胜,从第${currentLevel}关进入第${nextLevel}关`);
+                // 仅当下一关触发新武器解锁时才显示 GainUI
+                if (this.saveDataManager && this.saveDataManager.hasNewWeaponUnlockAtLevel(nextLevel)) {
+                    EventBus.getInstance().emit(GameEvents.SHOW_GAIN_UI);
+                }
             } else {
                 // 失败时保持当前关卡,不重置
                 const currentLevel = this.saveDataManager.getCurrentLevel();
@@ -592,9 +596,12 @@ export class GameManager extends Component {
         const mainUIController = this.mainUI.getComponent('MainUIController' as any);
         
         if (mainUIController) {
-            // 奖励动画已迁移到GameEnd.ts中,这里只需要普通返回主界面
-            if (typeof (mainUIController as any).onReturnToMainUI === 'function') {
-                console.log('[GameManager] 调用普通返回主界面方法');
+            // 统一由主界面播放奖励动画(含返回与动画)
+            if (typeof (mainUIController as any).onReturnToMainUIWithReward === 'function') {
+                console.log('[GameManager] 调用返回主界面并播放奖励动画方法');
+                (mainUIController as any).onReturnToMainUIWithReward();
+            } else if (typeof (mainUIController as any).onReturnToMainUI === 'function') {
+                console.warn('[GameManager] 未找到带奖励方法,回退到普通返回');
                 (mainUIController as any).onReturnToMainUI();
             } else {
                 console.warn('[GameManager] 未找到返回主界面方法,使用兜底逻辑');

+ 17 - 2
assets/scripts/LevelSystem/SaveDataManager.ts

@@ -23,7 +23,6 @@ interface WeaponConfig {
  * 玩家数据结构
  */
 export interface PlayerData {
-    playerData: {};
     // 基本信息
     playerId: string;
     playerName: string;
@@ -303,7 +302,6 @@ export class SaveDataManager {
                 totalDamageDealt: 0,
                 totalTimePlayed: 0,
                 totalPlayTime: 0,
-                totalPlayTime: 0,
                 highestLevel: 1,
                 consecutiveWins: 0,
                 bestWinStreak: 0,
@@ -869,6 +867,23 @@ export class SaveDataManager {
         return maxUnlockedLevel >= requiredLevel;
     }
     
+    /**
+     * 检查指定关卡是否会触发新武器解锁(基于配置)
+     */
+    public hasNewWeaponUnlockAtLevel(level: number): boolean {
+        if (!this.weaponsConfig || !this.weaponsConfig.weapons) return false;
+        for (const weapon of this.weaponsConfig.weapons) {
+            if (weapon.unlockLevel === level) {
+                const weaponData = this.getWeapon(weapon.id);
+                const isUnlocked = weaponData && weaponData.level > 0;
+                if (!isUnlocked) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+    
     /**
      * 获取武器解锁所需的关卡等级
      */