181404010226 4 months ago
parent
commit
0fcd37cbf0

+ 3 - 3
assets/Scenes/GameLevel.scene

@@ -20117,7 +20117,7 @@
     "_lpos": {
       "__type__": "cc.Vec3",
       "x": 0,
-      "y": 0,
+      "y": 81.928,
       "z": 0
     },
     "_lrot": {
@@ -20221,7 +20221,7 @@
     "_top": 0,
     "_bottom": 0,
     "_horizontalCenter": 0,
-    "_verticalCenter": 0,
+    "_verticalCenter": 81.928,
     "_isAbsLeft": true,
     "_isAbsRight": true,
     "_isAbsTop": true,
@@ -22253,7 +22253,7 @@
         "__id__": 596
       }
     ],
-    "_active": true,
+    "_active": false,
     "_components": [
       {
         "__id__": 663

+ 142 - 0
assets/scripts/Animations/BallAni.ts

@@ -0,0 +1,142 @@
+import { _decorator, Component, Node, Vec3, instantiate, UITransform, resources, sp, find, JsonAsset } from 'cc';
+const { ccclass, property } = _decorator;
+
+/**
+ * 小球撞击动画管理器
+ * 负责在小球撞击时播放特效动画
+ */
+@ccclass('BallAni')
+export class BallAni extends Component {
+    
+    // 撞击特效预制体缓存
+    private static impactEffectSkeleton: sp.SkeletonData = null;
+    private static isLoading: boolean = false;
+    
+    // 当前播放中的特效节点列表
+    private activeEffects: Node[] = [];
+    
+    start() {
+        // 预加载撞击特效资源
+        this.preloadImpactEffect();
+    }
+    
+    /**
+     * 预加载撞击特效资源
+     */
+    private preloadImpactEffect() {
+        if (BallAni.impactEffectSkeleton || BallAni.isLoading) {
+            return;
+        }
+        
+        BallAni.isLoading = true;
+        const path = 'Animation/WeaponTx/tx0005/tx0005';
+        resources.load(path, sp.SkeletonData, (err, sData: sp.SkeletonData) => {
+            BallAni.isLoading = false;
+            if (err || !sData) {
+                console.warn('[BallAni] 加载撞击特效失败:', err);
+                return;
+            }
+            BallAni.impactEffectSkeleton = sData;
+            console.log('[BallAni] 撞击特效资源加载成功');
+        });
+    }
+    
+    /**
+     * 在指定位置播放撞击特效
+     * @param worldPosition 世界坐标位置
+     */
+    public playImpactEffect(worldPosition: Vec3) {
+        // 如果资源未加载,直接加载并播放
+        if (!BallAni.impactEffectSkeleton) {
+            const path = 'Animation/WeaponTx/tx0005/tx0005';
+            resources.load(path, sp.SkeletonData, (err, sData: sp.SkeletonData) => {
+                if (err || !sData) {
+                    console.warn('[BallAni] 加载撞击特效失败:', err);
+                    return;
+                }
+                BallAni.impactEffectSkeleton = sData;
+                this.createAndPlayEffect(worldPosition, sData);
+            });
+            return;
+        }
+        
+        this.createAndPlayEffect(worldPosition, BallAni.impactEffectSkeleton);
+    }
+    
+    /**
+     * 创建并播放特效
+     */
+    private createAndPlayEffect(worldPosition: Vec3, skeletonData: sp.SkeletonData) {
+        const effectNode = new Node('ImpactEffect');
+        const skeleton = effectNode.addComponent(sp.Skeleton);
+        skeleton.skeletonData = skeletonData;
+        skeleton.premultipliedAlpha = false;
+        skeleton.setAnimation(0, 'animation', false);
+        skeleton.setCompleteListener(() => {
+            this.removeEffect(effectNode);
+        });
+        
+        const canvas = find('Canvas');
+        if (canvas) {
+            canvas.addChild(effectNode);
+            effectNode.setWorldPosition(worldPosition);
+            // 设置特效缩放
+            effectNode.setScale(0.8, 0.8, 1);
+            // 添加到活动特效列表
+            this.activeEffects.push(effectNode);
+        } else {
+            effectNode.destroy();
+        }
+    }
+    
+    /**
+     * 移除特效节点
+     * @param effectNode 要移除的特效节点
+     */
+    private removeEffect(effectNode: Node) {
+        // 从活动特效列表中移除
+        const index = this.activeEffects.indexOf(effectNode);
+        if (index !== -1) {
+            this.activeEffects.splice(index, 1);
+        }
+        
+        // 销毁节点
+        if (effectNode && effectNode.isValid) {
+            effectNode.destroy();
+        }
+    }
+    
+    /**
+     * 清理所有活动的特效
+     */
+    public clearAllEffects() {
+        for (const effect of this.activeEffects) {
+            if (effect && effect.isValid) {
+                effect.destroy();
+            }
+        }
+        this.activeEffects = [];
+    }
+    
+    onDestroy() {
+        // 组件销毁时清理所有特效
+        this.clearAllEffects();
+    }
+    
+    /**
+     * 获取单例实例(如果需要全局访问)
+     */
+    public static getInstance(): BallAni | null {
+        const gameArea = find('Canvas/GameLevelUI/GameArea');
+        if (!gameArea) {
+            return null;
+        }
+        
+        let ballAni = gameArea.getComponent(BallAni);
+        if (!ballAni) {
+            ballAni = gameArea.addComponent(BallAni);
+        }
+        
+        return ballAni;
+    }
+}

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

@@ -0,0 +1,9 @@
+{
+  "ver": "4.0.24",
+  "importer": "typescript",
+  "imported": true,
+  "uuid": "b8bc0d20-fc6b-42b4-8443-ab30b1f96dc5",
+  "files": [],
+  "subMetas": {},
+  "userData": {}
+}

+ 66 - 37
assets/scripts/CombatSystem/BallController.ts

@@ -3,6 +3,7 @@ import { PhysicsManager } from '../Core/PhysicsManager';
 import { WeaponBullet, BulletInitData, WeaponConfig } from './WeaponBullet';
 import EventBus, { GameEvents } from '../Core/EventBus';
 import { PersistentSkillManager } from '../FourUI/SkillSystem/PersistentSkillManager';
+import { BallAni } from '../Animations/BallAni';
 const { ccclass, property } = _decorator;
 
 @ccclass('BallController')
@@ -607,68 +608,71 @@ export class BallController extends Component {
             return;
         }
         
-        // 通过事件检查是否可以发射子弹
-        const eventBus = EventBus.getInstance();
-        let canFire = true;
-        
-        // 发送检查事件,如果有监听器返回false则不发射
-        eventBus.emit(GameEvents.BALL_FIRE_BULLET, { canFire: (value: boolean) => { canFire = value; } });
-        
-        if (!canFire) {
-            return;
-        }
-        
-        // 判断哪个是小球,哪个是方块
+        // 判断哪个是小球,哪个是碰撞对象
         let ballNode: Node = null;
-        let blockNode: Node = null;
+        let otherNode: Node = null;
         
         // 检查self是否为小球(组1)
         if (selfCollider.group === 1) {
             ballNode = selfCollider.node;
-            blockNode = otherCollider.node;
+            otherNode = otherCollider.node;
         } 
         // 检查other是否为小球(组1)
         else if (otherCollider.group === 1) {
             ballNode = otherCollider.node;
-            blockNode = selfCollider.node;
+            otherNode = selfCollider.node;
         }
         
         // 如果没有找到小球,跳过处理
-        if (!ballNode || !blockNode) {
+        if (!ballNode || !otherNode) {
             return;
         }
         
-        // 检查碰撞对象是否为方块
-        const nodeName = blockNode.name;
-        const nodePath = this.getNodePath(blockNode);
+        // 计算碰撞世界坐标
+        let contactPos: Vec3 = null;
+        if (contact && (contact as any).getWorldManifold) {
+            const wm = (contact as any).getWorldManifold();
+            if (wm && wm.points && wm.points.length > 0) {
+                contactPos = new Vec3(wm.points[0].x, wm.points[0].y, 0);
+            }
+        }
+        if (!contactPos) {
+            contactPos = otherNode.worldPosition.clone();
+        }
+
+        // 播放撞击特效 - 对所有碰撞都播放特效
+        const ballAni = BallAni.getInstance();
+        if (ballAni) {
+            ballAni.playImpactEffect(contactPos);
+        }
+        
+        // 检查碰撞对象是否为方块,只有方块碰撞才发射子弹
+        const nodeName = otherNode.name;
+        const nodePath = this.getNodePath(otherNode);
         
         // 检查是否有Weapon子节点(判断是否为方块)
-        const hasWeaponChild = blockNode.getChildByName('Weapon') !== null;
+        const hasWeaponChild = otherNode.getChildByName('Weapon') !== null;
         const isBlock = 
             nodeName.includes('Block') || 
             nodePath.includes('Block') ||
             hasWeaponChild;
             
         if (isBlock) {
-            // trigger bullet without verbose logging
-            // 计算碰撞世界坐标,默认用接触点
-            let contactPos: Vec3 = null;
-            if (contact && (contact as any).getWorldManifold) {
-                const wm = (contact as any).getWorldManifold();
-                if (wm && wm.points && wm.points.length > 0) {
-                    contactPos = new Vec3(wm.points[0].x, wm.points[0].y, 0);
+            // 通过事件检查是否可以发射子弹
+            const eventBus = EventBus.getInstance();
+            let canFire = true;
+            
+            // 发送检查事件,如果有监听器返回false则不发射
+            eventBus.emit(GameEvents.BALL_FIRE_BULLET, { canFire: (value: boolean) => { canFire = value; } });
+            
+            if (canFire) {
+                const now = performance.now();
+                const lastTime = this.blockFireCooldown.get(otherNode.uuid) || 0;
+                if (now - lastTime > this.FIRE_COOLDOWN * 1000) {
+                    this.blockFireCooldown.set(otherNode.uuid, now);
+                    this.fireBulletAt(otherNode, contactPos);
                 }
             }
-            if (!contactPos) {
-                contactPos = blockNode.worldPosition.clone();
-            }
-
-            const now = performance.now();
-            const lastTime = this.blockFireCooldown.get(blockNode.uuid) || 0;
-            if (now - lastTime > this.FIRE_COOLDOWN * 1000) {
-                this.blockFireCooldown.set(blockNode.uuid, now);
-                this.fireBulletAt(blockNode, contactPos);
-            }
         }
     }
 
@@ -686,6 +690,31 @@ export class BallController extends Component {
             return;
         }
         
+        // 计算碰撞位置并播放撞击特效
+        let contactPos: Vec3 = null;
+        if (contact && (contact as any).getWorldManifold) {
+            const wm = (contact as any).getWorldManifold();
+            if (wm && wm.points && wm.points.length > 0) {
+                contactPos = new Vec3(wm.points[0].x, wm.points[0].y, 0);
+            }
+        }
+        if (!contactPos) {
+            // 使用两个小球位置的中点作为碰撞位置
+            const pos1 = ball1.worldPosition;
+            const pos2 = ball2.worldPosition;
+            contactPos = new Vec3(
+                (pos1.x + pos2.x) / 2,
+                (pos1.y + pos2.y) / 2,
+                0
+            );
+        }
+        
+        // 播放撞击特效
+        const ballAni = BallAni.getInstance();
+        if (ballAni) {
+            ballAni.playImpactEffect(contactPos);
+        }
+        
         // 获取碰撞前的速度
         const velocity1 = rigidBody1.linearVelocity.clone();
         const velocity2 = rigidBody2.linearVelocity.clone();

+ 0 - 9
assets/scripts/CombatSystem/SkillSelection/SkillSelectionController.ts

@@ -191,12 +191,6 @@ export class SkillSelectionController extends Component {
             }
         }
 
-        // 禁用所有按钮交互
-        this.skillButtons.forEach(btn => {
-            const b = btn.getComponent(Button);
-            if (b) b.interactable = false;
-        });
-
         const otherBtns = this.skillButtons.filter(btn => btn !== selectedBtn);
         let finishedOthers = 0;
 
@@ -238,9 +232,6 @@ export class SkillSelectionController extends Component {
 
         // 重新启用所有按钮交互
         this.skillButtons.forEach(btn => {
-            const b = btn.getComponent(Button);
-            if (b) b.interactable = true;
-
             // 重置按钮动画状态
             const anim = btn.getComponent(SkillButtonAnimator);
             anim?.resetState();

+ 18 - 1
assets/scripts/CombatSystem/Wall.ts

@@ -228,7 +228,7 @@ export class Wall extends Component {
 
         // 获取升级费用并扣除金币
         const cost = this.saveDataManager.getWallUpgradeCost();
-        if (!this.saveDataManager.spendCoins(cost)) return null;
+        if (!this.saveDataManager.spendMoney(cost)) return null;
 
         // 执行升级
         if (!this.saveDataManager.upgradeWallLevel()) return null;
@@ -302,6 +302,9 @@ export class Wall extends Component {
         
         // 监听重置墙体血量事件
         eventBus.on(GameEvents.RESET_WALL_HEALTH, this.onResetWallHealthEvent, this);
+        
+        // 监听墙体血量变化事件(用于升级后更新显示)
+        eventBus.on(GameEvents.WALL_HEALTH_CHANGED, this.onWallHealthChangedEvent, this);
     }
     
     /**
@@ -312,6 +315,19 @@ export class Wall extends Component {
         this.resetToFullHealth();
     }
     
+    /**
+     * 处理墙体血量变化事件(用于升级后更新)
+     */
+    private onWallHealthChangedEvent(eventData?: any) {
+        console.log('[Wall] 接收到墙体血量变化事件,重新加载血量数据');
+        
+        // 重新从存档加载墙体血量(升级后血量会更新)
+        this.loadWallHealthFromSave();
+        
+        // 更新显示
+        this.updateHealthDisplay();
+    }
+    
     /**
      * 设置技能监听器
      */
@@ -350,6 +366,7 @@ export class Wall extends Component {
         // 清理事件监听
         const eventBus = EventBus.getInstance();
         eventBus.off(GameEvents.RESET_WALL_HEALTH, this.onResetWallHealthEvent, this);
+        eventBus.off(GameEvents.WALL_HEALTH_CHANGED, this.onWallHealthChangedEvent, this);
         
         this.cleanupSkillListeners();
     }

+ 81 - 16
assets/scripts/FourUI/MainSystem/MainUIControlller.ts

@@ -40,51 +40,111 @@ export class MainUIController extends Component {
   private sdm: SaveDataManager = null;
 
   onLoad () {
+    console.log('[MainUIController] onLoad 开始执行');
+    
     this.sdm = SaveDataManager.getInstance();
     this.sdm.initialize();
+    console.log('[MainUIController] SaveDataManager 初始化完成');
 
     this.bindButtons();
     this.refreshAll();
 
     // TopArea 默认隐藏,在点击战斗后再显示
     if (this.topArea) this.topArea.active = false;
+    
+    console.log('[MainUIController] onLoad 执行完成');
   }
 
 
 
   /* 绑定按钮事件 */
   private bindButtons () {
-    this.upgradeBtn?.node.on(Button.EventType.CLICK, this.upgradeWallHp, this);
-
-    this.battleBtn?.on(Button.EventType.CLICK, this.onBattle, this);
+    console.log('[MainUIController] bindButtons 开始执行');
+    console.log('[MainUIController] upgradeBtn 状态:', this.upgradeBtn ? '已设置' : '未设置');
+    console.log('[MainUIController] battleBtn 状态:', this.battleBtn ? '已设置' : '未设置');
+    
+    // 升级按钮绑定 - upgradeBtn是Button类型
+    if (this.upgradeBtn) {
+      console.log('[MainUIController] upgradeBtn.node:', this.upgradeBtn.node ? '存在' : '不存在');
+      this.upgradeBtn.node.on(Button.EventType.CLICK, this.upgradeWallHp, this);
+      console.log('[MainUIController] 升级按钮事件已绑定');
+      
+    } else {
+      console.error('[MainUIController] upgradeBtn未设置,无法绑定升级事件');
+    }
 
+    // 战斗按钮绑定 - battleBtn是Node类型
+    if (this.battleBtn) {
+      this.battleBtn.on(Button.EventType.CLICK, this.onBattle, this);
+      console.log('[MainUIController] 战斗按钮事件已绑定');
+    } else {
+      console.error('[MainUIController] battleBtn未设置,无法绑定战斗事件');
+    }
+    
+    console.log('[MainUIController] bindButtons 执行完成');
   }
 
   /* ================= 业务逻辑 ================= */
   private upgradeWallHp () {
-    // 直接使用SaveDataManager进行墙体升级
-    if (!this.sdm.canUpgradeWall()) {
-      console.log('无法升级墙体:条件不满足');
+    console.log('[MainUIController] 升级墙体按钮被点击');
+    
+    // 检查SaveDataManager是否初始化
+    if (!this.sdm) {
+      console.error('[MainUIController] SaveDataManager未初始化');
+      return;
+    }
+    
+    // 打印当前状态用于调试
+    const currentMoney = this.sdm.getMoney();
+    const currentWallLevel = this.sdm.getWallLevel();
+    const upgradeCost = this.sdm.getWallUpgradeCost();
+    
+    console.log(`[MainUIController] 当前状态: 金币=${currentMoney}, 墙体等级=${currentWallLevel}, 升级费用=${upgradeCost}`);
+    
+    // 检查墙体等级是否已达到最大值
+    if (currentWallLevel >= 5) {
+      console.log('[MainUIController] 墙体已达到最大等级');
+      EventBus.getInstance().emit(GameEvents.SHOW_TOAST, {
+        message: '墙体已达到最大等级',
+        duration: 2.0
+      });
+      return;
+    }
+    
+    // 检查金币是否足够
+    if (currentMoney < upgradeCost) {
+      console.log(`[MainUIController] 金币不足,需要${upgradeCost},当前${currentMoney}`);
+      EventBus.getInstance().emit(GameEvents.SHOW_TOAST, {
+        message: `金币不足,需要${upgradeCost}金币`,
+        duration: 2.0
+      });
       return;
     }
 
-    const cost = this.sdm.getWallUpgradeCost();
-    if (!this.sdm.spendCoins(cost)) {
-      console.log('无法升级墙体:金币不足');
+    // 执行升级
+    if (!this.sdm.spendMoney(upgradeCost)) {
+      console.log('[MainUIController] 扣除金币失败');
       return;
     }
 
     if (!this.sdm.upgradeWallLevel()) {
-      console.log('墙体升级失败');
+      console.log('[MainUIController] 墙体升级失败');
       return;
     }
 
-    console.log('墙体升级成功');
+    console.log('[MainUIController] 墙体升级成功');
     
     // 刷新所有UI显示
     this.refreshAll();
     // 通过事件系统通知UI更新
     EventBus.getInstance().emit(GameEvents.CURRENCY_CHANGED);
+    
+    // 通知墙体组件更新血量显示
+    EventBus.getInstance().emit(GameEvents.WALL_HEALTH_CHANGED, {
+      previousHealth: 0,
+      currentHealth: this.sdm.getPlayerData()?.wallBaseHealth || 100,
+      maxHealth: this.sdm.getPlayerData()?.wallBaseHealth || 100
+    });
   }
 
   private onBattle () {
@@ -186,14 +246,20 @@ export class MainUIController extends Component {
 
   /** 刷新升级信息显示 */
   private refreshUpgradeInfo () {
+    console.log('[MainUIController] refreshUpgradeInfo 开始执行');
+    
     const costLbl = this.upgradeCostLabel?.getComponent(Label);
     const hpLbl = this.upgradeHpLabel?.getComponent(Label);
     
-    if (!costLbl || !hpLbl) return;
+    if (!costLbl || !hpLbl) {
+      console.error('[MainUIController] 升级信息标签未找到:', { costLbl: !!costLbl, hpLbl: !!hpLbl });
+      return;
+    }
 
     // 显示升级费用
     const cost = this.sdm.getWallUpgradeCost();
     costLbl.string = cost.toString();
+    console.log(`[MainUIController] 升级费用: ${cost}`);
 
     // 显示当前和下一级血量
     const currentLevel = this.sdm.getWallLevel();
@@ -210,11 +276,10 @@ export class MainUIController extends Component {
     const nextHp = wallHpMap[currentLevel + 1] || (100 + currentLevel * 200);
     
     hpLbl.string = `${currentHp}>>${nextHp}`;
+    console.log(`[MainUIController] 血量显示: ${currentHp}>>${nextHp}, 当前等级: ${currentLevel}`);
     
-    // 根据是否可以升级来设置按钮状态
-    if (this.upgradeBtn) {
-      this.upgradeBtn.interactable = this.sdm.canUpgradeWall();
-    }
+    // 升级按钮始终保持可点击状态,通过Toast显示各种提示
+    console.log(`[MainUIController] 升级按钮保持可交互状态`);
   }
 
   // 供外部(如 GameManager)调用的公共刷新接口

+ 145 - 13
assets/scripts/FourUI/SkillSystem/SkillNodeGenerator.ts

@@ -1,6 +1,7 @@
-import { _decorator, Component, Node, Prefab, resources, instantiate, Label, Sprite, SpriteFrame, Button, ProgressBar, ScrollView, Vec2, Vec3, UITransform } from 'cc';
+import { _decorator, Component, Node, Prefab, resources, instantiate, Label, Sprite, SpriteFrame, Button, ProgressBar, ScrollView, Vec2, Vec3, UITransform, tween, Color } from 'cc';
 import { PersistentSkillManager } from './PersistentSkillManager';
 import { SaveDataManager } from '../../LevelSystem/SaveDataManager';
+import EventBus, { GameEvents } from '../../Core/EventBus';
 const { ccclass, property } = _decorator;
 
 /**
@@ -64,14 +65,20 @@ export class SkillNodeGenerator extends Component {
     // 持久化技能管理器
     private skillManager: PersistentSkillManager | null = null;
     
-    // 存档管理器
+    // 保存数据管理器引用
     private saveDataManager: SaveDataManager | null = null;
     
+    // 闪烁动画相关
+    private blinkingNodes: Set<Node> = new Set(); // 正在闪烁的节点集合
+    
     start() {
         console.log('SkillNodeGenerator 初始化开始');
         this.skillManager = PersistentSkillManager.getInstance();
         this.saveDataManager = SaveDataManager.getInstance();
         
+        // 监听货币变化事件
+        EventBus.getInstance().on(GameEvents.CURRENCY_CHANGED, this.onCurrencyChanged, this);
+        
         // 生成所有技能节点
         this.generateSkillNodes();
         console.log(`技能节点生成完成,共 ${this.skillNodes.length} 个节点`);
@@ -87,6 +94,12 @@ export class SkillNodeGenerator extends Component {
             this.scrollToLatestUnlockedSkill();
         }, 0.1);
         
+        // 更新钻石UI显示
+        this.updateDiamondUI();
+        
+        // 初始化闪烁状态
+        this.updateBlinkingNodes();
+        
         console.log('SkillNodeGenerator 初始化完成');
     }
     
@@ -171,12 +184,6 @@ export class SkillNodeGenerator extends Component {
             }
         }
         
-        // 设置节点位置(从下到上排列)
-        // 每个节点垂直间距150,从最下方开始排列
-        const yPosition = -totalIndex * 150;
-        skillNode.setPosition(0, yPosition, 0);
-        console.log(`Created skill node at position Y: ${yPosition}, totalIndex: ${totalIndex}`);
-        
         // 添加到当前节点(content节点)
         this.node.addChild(skillNode);
         
@@ -216,8 +223,6 @@ export class SkillNodeGenerator extends Component {
         
         // 初始化为未点亮状态
         this.updateSkillNodeVisual(skillNodeData);
-        
-        console.log(`Created skill node: ${skillName}, Cost: ${cost}, Group: ${group}, Position: (0, ${yPosition}, 0)`);
     }
     
     /**
@@ -250,6 +255,11 @@ export class SkillNodeGenerator extends Component {
         const currentDiamonds = this.saveDataManager ? this.saveDataManager.getDiamonds() : 0;
         if (currentDiamonds < skillNodeData.cost) {
             console.log(`钻石不足Need: ${skillNodeData.cost}, Have: ${currentDiamonds}`);
+            // 发送Toast提示事件
+            EventBus.getInstance().emit(GameEvents.SHOW_TOAST, {
+                message: `钻石不足,需要${skillNodeData.cost}钻石`,
+                duration: 2.0
+            });
             return;
         }
         
@@ -283,6 +293,11 @@ export class SkillNodeGenerator extends Component {
         this.updateProgressBar();
         this.updateDiamondUI();
         
+        // 延迟更新闪烁状态,确保所有状态更新完成
+        this.scheduleOnce(() => {
+            this.updateBlinkingNodes();
+        }, 0.1);
+        
         // 输出技能解锁信息,包含数组索引
         console.log(`技能已解锁: ${this.getSkillTypeString(skillNodeData.skillIndex)} Group ${skillNodeData.group}, 索引[${nodeIndex}/${this.skillNodes.length-1}]`);
         
@@ -339,6 +354,9 @@ export class SkillNodeGenerator extends Component {
                 playerData.diamonds = amount;
                 this.saveDataManager.savePlayerData();
                 this.updateDiamondUI();
+                
+                // 更新闪烁状态
+                this.updateBlinkingNodes();
             }
         }
     }
@@ -359,6 +377,9 @@ export class SkillNodeGenerator extends Component {
         } else {
             console.warn('Diamond number node not assigned in inspector');
         }
+        
+        // 更新闪烁状态
+        this.updateBlinkingNodes();
     }
     
     /**
@@ -397,9 +418,7 @@ export class SkillNodeGenerator extends Component {
         
         // 更新进度条
         this.updateProgressBar();
-        
-        console.log(`已加载技能解锁状态,当前解锁索引: ${this.currentUnlockIndex}/${this.skillNodes.length-1}`);
-        
+                
         // 输出已解锁技能的数组信息
         this.logUnlockedSkills();
     }
@@ -688,5 +707,118 @@ export class SkillNodeGenerator extends Component {
         }
     }
     
+    /**
+     * 更新闪烁节点状态
+     * 检查哪些节点可以升级并开始闪烁动画
+     */
+    private updateBlinkingNodes() {
+        // 停止所有当前的闪烁动画
+        this.stopAllBlinking();
+        
+        // 获取当前钻石数量
+        const currentDiamonds = this.saveDataManager ? this.saveDataManager.getDiamonds() : 0;
+        
+        // 找到下一个可解锁的节点
+        const nextUnlockIndex = this.currentUnlockIndex + 1;
+        
+        if (nextUnlockIndex < this.skillNodes.length) {
+            const nextNode = this.skillNodes[nextUnlockIndex];
+            console.log(`[闪烁检查] 下一个节点 - 组别: ${nextNode.group}, 技能: ${this.getSkillTypeString(nextNode.skillIndex)}, 费用: ${nextNode.cost}, 已解锁: ${nextNode.isUnlocked}, 当前钻石: ${currentDiamonds}`);
+            
+            // 检查是否有足够钻石且节点未解锁
+            if (!nextNode.isUnlocked && currentDiamonds >= nextNode.cost) {
+                this.startBlinking(nextNode.node);
+            } else {
+                console.log(`[闪烁检查] 节点不满足闪烁条件`);
+            }
+        } else {
+            console.log(`[闪烁检查] 已达到最后一个节点,无需闪烁`);
+        }
+    }
+    
+    /**
+     * 开始节点闪烁动画
+     * @param node 要闪烁的节点
+     */
+    private startBlinking(node: Node) {
+        if (this.blinkingNodes.has(node)) {
+            return; // 已经在闪烁中
+        }
+        
+        this.blinkingNodes.add(node);
+        
+        // 创建闪烁动画:透明度在0.5和1.0之间循环
+        const blinkTween = tween(node)
+            .to(0.5, { scale: new Vec3(1.1, 1.1, 1.1) }, { easing: 'sineInOut' })
+            .to(0.5, { scale: new Vec3(1.0, 1.0, 1.0) }, { easing: 'sineInOut' })
+            .union()
+            .repeatForever();
+            
+        blinkTween.start();
+        
+        // 将tween存储到节点上,方便后续停止
+        node['_blinkTween'] = blinkTween;
+    }
+    
+    /**
+     * 停止节点闪烁动画
+     * @param node 要停止闪烁的节点
+     */
+    private stopBlinking(node: Node) {
+        if (!this.blinkingNodes.has(node)) {
+            return; // 没有在闪烁
+        }
+        
+        this.blinkingNodes.delete(node);
+        
+        // 停止tween动画
+        if (node['_blinkTween']) {
+            node['_blinkTween'].stop();
+            node['_blinkTween'] = null;
+        }
+        
+        // 重置节点状态
+        node.setScale(1, 1, 1);
+    }
+    
+    /**
+     * 停止所有闪烁动画
+     */
+    private stopAllBlinking() {
+        for (const node of this.blinkingNodes) {
+            if (node['_blinkTween']) {
+                node['_blinkTween'].stop();
+                node['_blinkTween'] = null;
+            }
+            node.setScale(1, 1, 1);
+        }
+        this.blinkingNodes.clear();
+    }
+    
+    /**
+     * 当钻石数量变化时调用,更新闪烁状态
+     */
+    public onDiamondsChanged() {
+        this.updateBlinkingNodes();
+    }
+    
+    /**
+     * 货币变化事件处理
+     */
+    private onCurrencyChanged() {
+        console.log('[SkillNodeGenerator] 检测到货币变化,更新钻石UI和闪烁状态');
+        this.updateDiamondUI();
+    }
+    
+    /**
+     * 组件销毁时清理资源
+     */
+    onDestroy() {
+        // 取消事件监听
+        EventBus.getInstance().off(GameEvents.CURRENCY_CHANGED, this.onCurrencyChanged, this);
+        
+        // 停止所有闪烁动画
+        this.stopAllBlinking();
+    }
 
 }

+ 24 - 0
assets/scripts/FourUI/UpgradeSystem/UpgradeAni.ts

@@ -67,4 +67,28 @@ export class UpgradeAni extends Component {
         this.node.setPosition(0, 0, 0); // 确保面板在屏幕正中间
         this.node.active = true;
     }
+    
+    /**
+     * 武器升级成功动画
+     * 播放武器图标的放大缩小动画
+     * @param weaponIconNode 武器图标节点
+     */
+    public playWeaponUpgradeAnimation(weaponIconNode: Node): Promise<void> {
+        return new Promise((resolve) => {
+            // 保存原始缩放值
+            const originalScale = weaponIconNode.scale.clone();
+            
+            // 创建缩放动画:放大到1.5倍再立即缩小回原始大小
+            const scaleAnimation = tween(weaponIconNode)
+                .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();
+        });
+    }
 }

+ 37 - 27
assets/scripts/FourUI/UpgradeSystem/UpgradeController.ts

@@ -433,12 +433,9 @@ export class UpgradeController extends Component {
                 
                 if (weaponData && weaponData.level >= maxLevel) {
                     buttonLabel.string = '已满级';
-                    upgradeButton.interactable = false;
                 } else {
                     // 只显示"升级",不显示费用,费用在升级面板中显示
                     buttonLabel.string = '升级';
-                    // 武器节点的升级按钮始终可点击(用于打开升级面板)
-                    upgradeButton.interactable = true;
                 }
             }
         }
@@ -461,11 +458,7 @@ export class UpgradeController extends Component {
         // Lock.prefab已经有合适的视觉效果,不需要额外设置透明度
         weaponNode.active = true;
         
-        // 禁用点击事件(如果有Button组件)
-        const button = weaponNode.getComponent(Button);
-        if (button) {
-            button.interactable = false;
-        }
+        // 锁定武器节点保持可点击状态以显示解锁提示
         
         // 添加点击事件监听(显示解锁提示)
         weaponNode.on(Node.EventType.TOUCH_END, () => {
@@ -518,11 +511,7 @@ export class UpgradeController extends Component {
         uiOpacity.opacity = 180;
         weaponNode.active = true;
         
-        // 禁用点击事件
-        const button = weaponNode.getComponent(Button);
-        if (button) {
-            button.interactable = false;
-        }
+        // 锁定武器节点保持可点击状态以显示解锁提示
     }
     
     /**
@@ -558,11 +547,7 @@ export class UpgradeController extends Component {
         // 设置为解锁状态
         this.setupUnlockedWeaponNode(weaponNode, weaponConfig, weaponData);
         
-        // 启用点击事件
-        const button = weaponNode.getComponent(Button);
-        if (button) {
-            button.interactable = true;
-        }
+        // 解锁武器节点保持可点击状态
     }
     
    
@@ -656,13 +641,8 @@ export class UpgradeController extends Component {
             this.panelCostLabel.string = upgradeCost.toString();
         }
         
-        // 设置升级按钮状态 - Canvas/UpgradeUI/UpgradePanel/UpgradeBtn
-        if (this.panelUpgradeBtn) {
-            // 这样可以确保用户点击时能看到"金币不足"的提示
-            this.panelUpgradeBtn.interactable = true;
-            
-            console.log(`[UpgradeController] 升级按钮设置为可交互状态`);
-        }
+        // 升级按钮始终保持可点击状态,通过Toast显示各种提示
+        console.log(`[UpgradeController] 升级按钮保持可交互状态`);
     }
     
     /**
@@ -746,6 +726,14 @@ export class UpgradeController extends Component {
             return;
         }
         
+        // 检查是否已达到最大等级
+        const maxLevel = 10; // 假设最大等级为10
+        if (weapon.level >= maxLevel) {
+            console.log(`[UpgradeController] 武器已达到最大等级: ${this.currentSelectedWeapon}`);
+            EventBus.getInstance().emit(GameEvents.SHOW_TOAST, { message: "武器已达到最大等级", duration: 2.0 });
+            return;
+        }
+        
         const cost = this.saveDataManager.getWeaponUpgradeCost(this.currentSelectedWeapon);
         const playerMoney = this.saveDataManager.getMoney();
         
@@ -772,8 +760,8 @@ export class UpgradeController extends Component {
             console.log(`[UpgradeController] 武器 ${this.currentSelectedWeapon} 升级成功`);
             console.log(`[UpgradeController] 升级后金币: ${coinsAfterUpgrade}, 消耗: ${coinsBeforeUpgrade - coinsAfterUpgrade}`);
             
-            // 显示升级成功提示
-            EventBus.getInstance().emit(GameEvents.SHOW_TOAST, { message: "升级成功!", duration: 1.5 });
+            // 播放武器升级成功动画
+            this.playWeaponUpgradeSuccessAnimation();
             
             // 刷新升级面板
             this.refreshUpgradePanel();
@@ -790,6 +778,28 @@ export class UpgradeController extends Component {
         }
     }
     
+    /**
+     * 播放武器升级成功动画
+     */
+    private async playWeaponUpgradeSuccessAnimation(): Promise<void> {
+        if (!this.upgradeAni || !this.panelWeaponSprite) {
+            console.warn('[UpgradeController] upgradeAni 或 panelWeaponSprite 未设置,跳过升级动画');
+            return;
+        }
+        
+        try {
+            // 获取武器图标节点
+            const weaponIconNode = this.panelWeaponSprite.node;
+            
+            // 播放升级动画
+            await this.upgradeAni.playWeaponUpgradeAnimation(weaponIconNode);
+            
+            console.log('[UpgradeController] 武器升级动画播放完成');
+        } catch (error) {
+            console.error('[UpgradeController] 播放武器升级动画时出错:', error);
+        }
+    }
+    
     /**
      * 解锁武器
      */

+ 1 - 6
assets/scripts/LevelSystem/SaveDataManager.ts

@@ -222,7 +222,7 @@ export class SaveDataManager {
             lastPlayTime: Date.now(),
             totalPlayTime: 0,
             
-            money: 0,        // 初始局外金币
+            money: 1000,        // 初始局外金币
             diamonds: 20,      // 初始钻石
             
             wallLevel: 1,       // 初始墙体等级
@@ -619,11 +619,6 @@ export class SaveDataManager {
         return true;
     }
     
-    // 保持向后兼容的方法
-    public spendCoins(amount: number): boolean {
-        return this.spendMoney(amount);
-    }
-    
     /**
      * 添加钻石
      */