181404010226 преди 1 месец
родител
ревизия
6a6f403afe

+ 57 - 33
assets/NewbieGuidePlugin-v1.0.0/NewbieGuidePlugin-v1.0.0/scripts/GuideUIController.ts

@@ -4,7 +4,6 @@
  */
 import { sp, Node, director, Vec3, find, UITransform, tween, Tween, _decorator, Component, Mask, instantiate, Graphics, Color, BlockInputEvents, Button, Label, Sprite } from "cc";
 import EventBus, { GameEvents } from "../../../scripts/Core/EventBus";
-import BlinkScaleAnimator from "../../../scripts/Animations/BlinkScaleAnimator";
 import { NewbieGuideManager } from '../../../scripts/Core/NewbieGuideManager';
 import { RoundRectMask } from './RoundRectMask';
 const { ccclass, property } = _decorator;
@@ -178,7 +177,9 @@ export class GuideUIController extends Component {
     private _autoScheduleCallback: Function | null = null;
     private _listeningTarget: Node | null = null;
     private _listeningEventName: string | null = null;
-    private _activeBlinkComp: BlinkScaleAnimator | null = null;
+    // 手指缩放闪烁:代码控制(不使用组件)
+    private _fingerBlinkTween: Tween<Node> | null = null;
+    private _fingerOrigScale: Vec3 | null = null;
     // 预等待:在步骤1与步骤5开始前等待diban上滑动画完成
     private _prewaitDoneForCurrentStep: boolean = false;
     private _prewaitHandler: (() => void) | null = null;
@@ -396,7 +397,7 @@ export class GuideUIController extends Component {
      * 统一清理并隐藏手指(guild_1)相关的所有可视动画/补间
      * - 停止针对手指节点的所有 Tween
      * - 清除手指的 Spine 动画轨道
-     * - 停止并清空当前激活的 BlinkScaleAnimator
+     * - 停止并清空当前激活的缩放闪烁补间
      * - 隐藏手指节点
      */
     private cleanupFingerVisuals(): void {
@@ -412,21 +413,26 @@ export class GuideUIController extends Component {
                     try { spineComp.clearTracks(); } catch (e) { /* 安全兜底 */ }
                 }
 
-                // 停止闪烁缩放效果
-                if (this._activeBlinkComp) {
-                    this._activeBlinkComp.stop();
-                    this._activeBlinkComp = null;
+                // 停止闪烁缩放效果(Tween控制)并恢复缩放
+                if (this._fingerBlinkTween) {
+                    try { this._fingerBlinkTween.stop(); } catch { /* 忽略 */ }
+                    this._fingerBlinkTween = null;
+                }
+                if (this._fingerOrigScale) {
+                    finger.setScale(this._fingerOrigScale);
+                    this._fingerOrigScale = null;
                 }
 
                 // 隐藏手指UI
                 finger.active = false;
                 console.log('[GuideUIController] 清理并隐藏 guild_1 手指动画');
             } else {
-                // 若未找到节点,至少停止可能残留的 Blink
-                if (this._activeBlinkComp) {
-                    this._activeBlinkComp.stop();
-                    this._activeBlinkComp = null;
+                // 若未找到节点,至少停止可能残留的补间
+                if (this._fingerBlinkTween) {
+                    try { this._fingerBlinkTween.stop(); } catch { /* 忽略 */ }
+                    this._fingerBlinkTween = null;
                 }
+                this._fingerOrigScale = null;
                 console.log('[GuideUIController] 未找到 guild_1 手指节点,跳过节点隐藏');
             }
         } catch (err) {
@@ -530,9 +536,10 @@ export class GuideUIController extends Component {
             return;
         }
 
-        if (this._activeBlinkComp) {
-            this._activeBlinkComp.stop();
-            this._activeBlinkComp = null;
+        // 停止可能存在的旧闪烁补间
+        if (this._fingerBlinkTween) {
+            try { this._fingerBlinkTween.stop(); } catch { /* 忽略 */ }
+            this._fingerBlinkTween = null;
         }
         const finger = this.getGuideNode('guild_1');
         if (!finger) {
@@ -560,16 +567,27 @@ export class GuideUIController extends Component {
         );
         finger.setPosition(finalPos);
 
-        // 显示手指UI并播放闪烁缩放动画
+        // 显示手指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'
-        });
+        // 若有记录的原始缩放,先复位
+        if (this._fingerOrigScale) {
+            finger.setScale(this._fingerOrigScale);
+        }
+        // 记录原始缩放并启动补间循环
+        const origScale = finger.scale.clone();
+        const targetScale = new Vec3(origScale.x * 1.3, origScale.y * 1.3, origScale.z);
+        this._fingerOrigScale = origScale.clone();
+        if (this._fingerBlinkTween) {
+            this._fingerBlinkTween.stop();
+            this._fingerBlinkTween = null;
+        }
+        this._fingerBlinkTween = tween(finger)
+            .to(0.15, { scale: targetScale }, { easing: 'sineOut' })
+            .to(0.15, { scale: origScale }, { easing: 'sineIn' })
+            .union()
+            .repeatForever()
+            .start();
         console.log(`[GuideUIController] 点击步骤 ${stepIndex}:手指位于按钮右下角并进行缩放闪烁`);
     }
 
@@ -929,11 +947,7 @@ export class GuideUIController extends Component {
             this.setMaskAndFrameVisibility(this._currentStepIndex, false);
         }
 
-        // 停止第三步的闪烁缩放效果(如果有)
-        if (this._activeBlinkComp) {
-            this._activeBlinkComp.stop();
-            this._activeBlinkComp = null;
-        }
+        // 点击完成后无需额外处理缩放闪烁,已在 cleanupFingerVisuals 中停止并复位
 
         // 清理事件监听/计时器
         this.clearCurrentStepListeners();
@@ -1033,9 +1047,14 @@ export class GuideUIController extends Component {
         this.clearCurrentStepListeners();
         this.hideAllGuideUI();
         this.hideAllMasksAndFrames();
-        if (this._activeBlinkComp) {
-            this._activeBlinkComp.stop();
-            this._activeBlinkComp = null;
+        // 停止可能存在的闪烁补间并复位缩放
+        if (this._fingerBlinkTween) {
+            try { this._fingerBlinkTween.stop(); } catch { /* 忽略 */ }
+            this._fingerBlinkTween = null;
+        }
+        if (this.guild1Node && this.guild1Node.isValid && this._fingerOrigScale) {
+            this.guild1Node.setScale(this._fingerOrigScale);
+            this._fingerOrigScale = null;
         }
         // 使用属性绑定的节点关闭遮罩与引导层,移除场景 find()
         this.ensureMaskRootAndTemplate();
@@ -1357,9 +1376,14 @@ private createDragAnimation(stepIndex: number, start: Node, targets: Node[]): vo
  
         // 停止点击类相关动画(Spine tap 与缩放闪烁),确保仅移动动画运行
         this.stopAnimation('guild_1');
-        if (this._activeBlinkComp) {
-            this._activeBlinkComp.stop();
-            this._activeBlinkComp = null;
+        // 停止点击类相关动画的缩放闪烁(Tween控制)
+        if (this._fingerBlinkTween) {
+            try { this._fingerBlinkTween.stop(); } catch { /* 忽略 */ }
+            this._fingerBlinkTween = null;
+        }
+        if (finger && finger.isValid && this._fingerOrigScale) {
+            finger.setScale(this._fingerOrigScale);
+            this._fingerOrigScale = null;
         }
         // 停止可能存在的旧 tween
         Tween.stopAllByTarget(finger);

Файловите разлики са ограничени, защото са твърде много
+ 189 - 150
assets/Scenes/GameLevel.scene


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

@@ -204,6 +204,7 @@ export class BallAni extends Component {
                 originalWeaponScale.z
             ) : null;
             
+            // 统一使用代码控制的 Tween 动画(不使用组件)
             const animationTween = tween(blockNode)
                 .to(0.1, { scale: shrinkBlockScale }, {
                     onStart: () => {
@@ -406,6 +407,7 @@ export class BallAni extends Component {
             animation.stop();
             this.activeBlockAnimations.delete(blockNode);
         }
+        // 代码控制,无需组件停止
     }
     
     /**

+ 7 - 25
assets/scripts/Animations/BlinkScaleAnimator.ts

@@ -20,10 +20,11 @@ export default class BlinkScaleAnimator extends Component {
     public easingDown: TweenEasing | ((k: number) => number) = 'sineIn';
 
     @property({ tooltip: '启用组件时自动开始闪烁' })
-    public playOnEnable: boolean = true;
+    public playOnEnable: boolean = false; // 默认不自动播放,避免误触发
 
     private _originalScale: Vec3 = Vec3.ONE.clone();
     private _blinkTween: Tween<Node> | null = null;
+    private _isPlaying: boolean = false;
 
     onLoad() {
         this._originalScale = this.node.scale.clone();
@@ -44,7 +45,11 @@ export default class BlinkScaleAnimator extends Component {
     }
 
     public play(): void {
+        if (this._isPlaying) {
+            return;
+        }
         this.stop();
+        this._isPlaying = true;
         const targetScale = new Vec3(
             this._originalScale.x * this.scaleFactor,
             this._originalScale.y * this.scaleFactor,
@@ -60,6 +65,7 @@ export default class BlinkScaleAnimator extends Component {
     }
 
     public stop(): void {
+        this._isPlaying = false;
         if (this._blinkTween) {
             this._blinkTween.stop();
             this._blinkTween = null;
@@ -69,28 +75,4 @@ export default class BlinkScaleAnimator extends Component {
         this.node.scale = this._originalScale.clone();
     }
 
-    // 自动挂载并启动闪烁
-    public static ensure(node: Node, options?: Partial<{
-        scaleFactor: number,
-        upDuration: number,
-        downDuration: number,
-        easingUp: TweenEasing | ((k: number) => number),
-        easingDown: TweenEasing | ((k: number) => number),
-        playOnEnable: boolean,
-    }>): BlinkScaleAnimator {
-        let comp = node.getComponent(BlinkScaleAnimator);
-        if (!comp) {
-            comp = node.addComponent(BlinkScaleAnimator);
-        }
-        if (options) {
-            if (options.scaleFactor !== undefined) comp.scaleFactor = options.scaleFactor;
-            if (options.upDuration !== undefined) comp.upDuration = options.upDuration;
-            if (options.downDuration !== undefined) comp.downDuration = options.downDuration;
-            if (options.easingUp !== undefined) comp.easingUp = options.easingUp;
-            if (options.easingDown !== undefined) comp.easingDown = options.easingDown;
-            if (options.playOnEnable !== undefined) comp.playOnEnable = options.playOnEnable;
-        }
-        comp.play();
-        return comp;
-    }
 }

+ 42 - 29
assets/scripts/CombatSystem/BlockManager.ts

@@ -584,31 +584,34 @@ export class BlockManager extends Component {
             return;
         }
         
-        // 尝试查找节点
+        // 尝试查找节点(优先 GameArea)
         this.placedBlocksContainer = find('Canvas/GameLevelUI/GameArea/PlacedBlocks');
         if (this.placedBlocksContainer) {
             return;
         }
         
-        // 如果找不到,创建新节点
+        // 如果找不到,创建新节点并优先挂到 GameArea
+        const gameArea = find('Canvas/GameLevelUI/GameArea');
         const gameLevelUI = find('Canvas/GameLevelUI');
-        if (!gameLevelUI) {
-            console.error('找不到GameLevelUI节点,无法创建PlacedBlocks');
-            return;
-        }
-        
         this.placedBlocksContainer = new Node('PlacedBlocks');
-        gameLevelUI.addChild(this.placedBlocksContainer);
+        if (gameArea) {
+            gameArea.addChild(this.placedBlocksContainer);
+            console.log('已在GameArea下创建PlacedBlocks节点');
+        } else if (gameLevelUI) {
+            gameLevelUI.addChild(this.placedBlocksContainer);
+            console.warn('[BlockManager] 未找到GameArea,PlacedBlocks暂挂GameLevelUI');
+        } else {
+            cc.director.getScene()?.addChild(this.placedBlocksContainer);
+            console.warn('[BlockManager] 未找到GameLevelUI和GameArea,PlacedBlocks暂挂场景根节点');
+        }
         if (!this.placedBlocksContainer.getComponent(UITransform)) {
             this.placedBlocksContainer.addComponent(UITransform);
         }
-        console.log('已在GameLevelUI下创建PlacedBlocks节点');
     }
     
     // 初始化网格信息
     initGridInfo() {
         if (!this.gridContainer || this.gridInitialized) return;
-        
         this.gridNodes = [];
         for (let row = 0; row < this.GRID_ROWS; row++) {
             this.gridNodes[row] = [];
@@ -621,7 +624,6 @@ export class BlockManager extends Component {
                 if (parts.length === 3) {
                     const row = parseInt(parts[1]);
                     const col = parseInt(parts[2]);
-                    
                     if (row >= 0 && row < this.GRID_ROWS && col >= 0 && col < this.GRID_COLS) {
                         this.gridNodes[row][col] = grid;
                     }
@@ -630,7 +632,6 @@ export class BlockManager extends Component {
         }
         // 动态计算网格间距,适应不同分辨率
         this.calculateGridSpacing();
-        
         this.gridInitialized = true;
     }
 
@@ -657,7 +658,6 @@ export class BlockManager extends Component {
                 return;
             }
         }
-        
         // 如果都失败了,保持默认值
          console.warn(`无法动态计算网格间距,使用默认值: ${this.gridSpacing}`);
      }
@@ -666,7 +666,6 @@ export class BlockManager extends Component {
      public getGridSpacing(): number {
          return this.gridSpacing;
      }
-    
     // 初始化网格占用情况
     initGridOccupationMap() {
         this.gridOccupationMap = [];
@@ -681,9 +680,7 @@ export class BlockManager extends Component {
     
     // 在kuang下生成三个方块(基于关卡配置)
     private async generateRandomBlocksInKuang() {
-        // 清除kuang区域中的旧方块,但不清除已放置在网格中的方块的标签
-        this.clearBlocks();
-        
+        this.clearBlocks();      
         // 优先检查预加载的武器配置
         if (this.isWeaponsConfigPreloaded) {
             const preloadedConfig = this.getPreloadedWeaponsConfig();
@@ -695,8 +692,7 @@ export class BlockManager extends Component {
                 console.warn('[BlockManager] 预加载配置不完整,回退到ConfigManager模式');
                 this.isWeaponsConfigPreloaded = false;
             }
-        }
-        
+        }    
         // 如果没有预加载配置,检查ConfigManager
         if (!this.isWeaponsConfigPreloaded) {
             // 检查配置管理器是否可用
@@ -707,32 +703,24 @@ export class BlockManager extends Component {
                 }, 2.0);
                 return;
             }
-            
             // 检查武器配置是否已加载(不需要等待所有配置完成)
             const weaponsConfigLoaded = !!this.configManager['weaponsConfig'];
             const blockSizesAvailable = weaponsConfigLoaded && !!this.configManager['weaponsConfig'].blockSizes;
-            
             if (!weaponsConfigLoaded) {
                 console.log('[BlockManager] 武器配置未加载完成,延迟2秒后重试生成方块');
-                console.log('[BlockManager] 整体配置状态:', this.configManager.isConfigLoaded());
                 this.scheduleOnce(() => {
                     this.generateRandomBlocksInKuang();
                 }, 2.0);
                 return;
-            }
-            
+            }     
             if (!blockSizesAvailable) {
-                console.warn('[BlockManager] 武器配置已加载但blockSizes缺失,延迟2秒后重试');
-                console.log('[BlockManager] weaponsConfig keys:', Object.keys(this.configManager['weaponsConfig']));
+                console.log('[BlockManager] 武器配置已加载但blockSizes缺失,延迟2秒后重试');
                 this.scheduleOnce(() => {
                     this.generateRandomBlocksInKuang();
                 }, 2.0);
                 return;
             }
         }
-        
-        console.log('[BlockManager] 配置已加载完成,开始生成方块');
-        
         if (this.blockPrefabs.length === 0) {
             console.error('没有可用的预制体');
             return;
@@ -2077,6 +2065,31 @@ export class BlockManager extends Component {
         block.removeFromParent();
         this.placedBlocksContainer.addChild(block);
         block.setWorldPosition(worldPosition);
+
+        // Scheme D:将方块(B1参考点)对齐到最近网格中心
+        try {
+            const b1Node = block.name === 'B1' ? block : block.getChildByName('B1');
+            if (b1Node && this.gridContainer) {
+                const b1WorldPos = b1Node.worldPosition.clone();
+                const nearestGrid = this.findNearestGridNode(b1WorldPos);
+                if (nearestGrid) {
+                    const gridCenterWorld = this.gridContainer.getComponent(UITransform).convertToWorldSpaceAR(nearestGrid.position);
+                    if (b1Node === block) {
+                        block.setWorldPosition(gridCenterWorld);
+                    } else {
+                        const b1Local = b1Node.position.clone();
+                        const rootTargetWorldPos = new Vec3(
+                            gridCenterWorld.x - b1Local.x,
+                            gridCenterWorld.y - b1Local.y,
+                            gridCenterWorld.z
+                        );
+                        block.setWorldPosition(rootTargetWorldPos);
+                    }
+                }
+            }
+        } catch (e) {
+            console.warn('[BlockManager] 对齐网格中心失败,保留原位置:', e);
+        }
         
         // 通过事件机制重新设置拖拽事件
         const eventBus = EventBus.getInstance();

+ 93 - 2
assets/scripts/CombatSystem/BlockSelection/GameBlockSelection.ts

@@ -265,6 +265,7 @@ export class GameBlockSelection extends Component {
     private currentDragBlock: Node | null = null;
     private startPos = new Vec2();
     private blockStartPos: Vec3 = new Vec3();
+    private activeTouchId: number | null = null;
     
     // 调试绘制相关属性
     @property({
@@ -382,6 +383,8 @@ export class GameBlockSelection extends Component {
         
         // 监听重置方块选择事件
         eventBus.on(GameEvents.RESET_BLOCK_SELECTION, this.onResetBlockSelectionEvent, this);
+        // 监听全局UI重置事件,确保按钮视觉状态恢复
+        eventBus.on(GameEvents.RESET_UI_STATES, this.onResetUIStatesEvent, this);
         // 监听游戏开始事件,用于标记可以开始生成方块
         eventBus.on(GameEvents.GAME_START, this.onGameStartEvent, this);
         
@@ -402,6 +405,18 @@ export class GameBlockSelection extends Component {
     // 处理重置方块选择事件
     private onResetBlockSelectionEvent() {
         this.resetSelection();
+        // 恢复按钮颜色与交互状态,并清理颜色缓存,避免跨关卡残留
+        this.applyGuideDisabledVisual(this.addCoinButton, false);
+        this.applyGuideDisabledVisual(this.refreshButton, false);
+        this.originalButtonColors.clear();
+    }
+
+    // 处理全局UI重置事件(返回主界面或关卡结束时)
+    private onResetUIStatesEvent() {
+        // 恢复按钮颜色与交互状态,并清理颜色缓存,避免跨关卡残留
+        this.applyGuideDisabledVisual(this.addCoinButton, false);
+        this.applyGuideDisabledVisual(this.refreshButton, false);
+        this.originalButtonColors.clear();
     }
     // 处理游戏开始事件
     private onGameStartEvent() {
@@ -980,6 +995,11 @@ export class GameBlockSelection extends Component {
     // 设置方块拖拽事件
     public setupBlockDragEvents(block: Node) {
         block.on(Node.EventType.TOUCH_START, (event: EventTouch) => {
+            // 仅处理首个触点,过滤其他触点事件
+            const touchId = this.getTouchId(event);
+            if (this.activeTouchId !== null && this.activeTouchId !== touchId) {
+                return;
+            }
             // 检查游戏是否已开始
             if (!this.blockManager.gameStarted) {
                 return;
@@ -999,6 +1019,8 @@ export class GameBlockSelection extends Component {
             
             // grid区域的方块目前允许自由移动
             
+            // 绑定当前拖拽手指ID
+            this.activeTouchId = touchId;
             this.currentDragBlock = block;
             this.startPos = event.getUILocation();
             this.blockStartPos.set(block.position);
@@ -1024,6 +1046,11 @@ export class GameBlockSelection extends Component {
         }, this);
         
         block.on(Node.EventType.TOUCH_MOVE, (event: EventTouch) => {
+            // 只处理与当前绑定触点一致的移动事件
+            const touchId = this.getTouchId(event);
+            if (this.activeTouchId !== touchId) {
+                return;
+            }
             // 检查游戏是否已开始
             if (!this.blockManager.gameStarted) {
                 return;
@@ -1043,14 +1070,48 @@ export class GameBlockSelection extends Component {
             const deltaX = location.x - this.startPos.x;
             const deltaY = location.y - this.startPos.y;
             
-            this.currentDragBlock.position = new Vec3(
+            // 目标位置(本地坐标)
+            const targetLocal = new Vec3(
                 this.blockStartPos.x + deltaX,
                 this.blockStartPos.y + deltaY,
                 this.blockStartPos.z
             );
+
+            const parentTransform = this.currentDragBlock.parent ? this.currentDragBlock.parent.getComponent(UITransform) : null;
+            const targetWorld = parentTransform ? parentTransform.convertToWorldSpaceAR(targetLocal) : targetLocal.clone();
+
+            // GameArea 边界夹紧(仅在目标点位于 GameArea 内部时进行夹紧)
+            const gameAreaNode = find('Canvas/GameLevelUI/GameArea');
+            const gameAreaTransform = gameAreaNode ? gameAreaNode.getComponent(UITransform) : null;
+            if (gameAreaTransform) {
+                const bounds = gameAreaTransform.getBoundingBoxToWorld();
+                const inside = bounds.contains(new Vec2(targetWorld.x, targetWorld.y));
+                if (inside && parentTransform) {
+                    const minX = bounds.x;
+                    const maxX = bounds.x + bounds.width;
+                    const minY = bounds.y;
+                    const maxY = bounds.y + bounds.height;
+                    const clampedWorld = new Vec3(
+                        Math.min(Math.max(targetWorld.x, minX), maxX),
+                        Math.min(Math.max(targetWorld.y, minY), maxY),
+                        targetWorld.z
+                    );
+                    const clampedLocal = parentTransform.convertToNodeSpaceAR(clampedWorld);
+                    this.currentDragBlock.position = clampedLocal;
+                } else {
+                    this.currentDragBlock.position = targetLocal;
+                }
+            } else {
+                this.currentDragBlock.position = targetLocal;
+            }
         }, this);
         
         block.on(Node.EventType.TOUCH_END, async (event: EventTouch) => {
+            // 只处理与当前绑定触点一致的结束事件
+            const touchId = this.getTouchId(event);
+            if (this.activeTouchId !== null && this.activeTouchId !== touchId) {
+                return;
+            }
             // 检查游戏是否已开始
             if (!this.blockManager.gameStarted) {
                 return;
@@ -1083,10 +1144,16 @@ export class GameBlockSelection extends Component {
                 
                 // 通知BallController方块拖拽结束
                 EventBus.getInstance().emit(GameEvents.BLOCK_DRAG_END, { block: block });
+                // 释放触点绑定
+                this.activeTouchId = null;
             }
         }, this);
         
-        block.on(Node.EventType.TOUCH_CANCEL, () => {
+        block.on(Node.EventType.TOUCH_CANCEL, (event: EventTouch) => {
+            const touchId = this.getTouchId(event);
+            if (this.activeTouchId !== null && this.activeTouchId !== touchId) {
+                return;
+            }
             if (this.currentDragBlock) {
                 this.returnBlockToOriginalPosition();
                 
@@ -1100,6 +1167,8 @@ export class GameBlockSelection extends Component {
                 
                 // 通知BallController方块拖拽结束
                 EventBus.getInstance().emit(GameEvents.BLOCK_DRAG_END, { block: block });
+                // 释放触点绑定
+                this.activeTouchId = null;
             }
         }, this);
     }
@@ -1164,6 +1233,8 @@ export class GameBlockSelection extends Component {
             
             // 清理拖拽状态
             this.currentDragBlock = null;
+            // 释放触点绑定
+            this.activeTouchId = null;
         }
         
         // 刷新方块占用情况
@@ -1580,6 +1651,7 @@ export class GameBlockSelection extends Component {
     private removeEventListeners() {
         const eventBus = EventBus.getInstance();
         eventBus.off(GameEvents.RESET_BLOCK_SELECTION, this.onResetBlockSelectionEvent, this);
+        eventBus.off(GameEvents.RESET_UI_STATES, this.onResetUIStatesEvent, this);
         eventBus.off(GameEvents.GAME_START, this.onGameStartEvent, this);
         eventBus.off(GameEvents.GAME_START, this.updateGuideButtonStates, this);
         eventBus.off(GameEvents.ENTER_PLAYING_STATE, this.updateGuideButtonStates, this);
@@ -1618,8 +1690,27 @@ export class GameBlockSelection extends Component {
             console.warn('[GameBlockSelection] 强制结束拖拽失败:', e);
         } finally {
             this.currentDragBlock = null;
+            this.activeTouchId = null;
         }
     }
+
+    // 统一获取触点ID,兼容不同版本API
+    private getTouchId(event: EventTouch): number {
+        try {
+            const anyEvent = event as any;
+            const touch = anyEvent.touch;
+            if (touch && typeof touch.getID === 'function') {
+                return touch.getID();
+            }
+            if (touch && typeof touch._id === 'number') {
+                return touch._id;
+            }
+            if (typeof anyEvent.id === 'number') {
+                return anyEvent.id;
+            }
+        } catch {}
+        return 0;
+    }
     
     /**
      * 获取方块的原始位置

+ 49 - 7
assets/scripts/CombatSystem/GameEnd.ts

@@ -65,6 +65,16 @@ export class GameEnd extends Component {
     private hasDoubledReward: boolean = false;
     private isGameSuccess: boolean = false;
     private hasProcessedGameEnd: boolean = false; // 添加处理状态标志
+    private isAdRequestPending: boolean = false; // 防止重复点击广告期间的状态
+
+    // === 弹窗冷却控制 ===
+    private static lastPopupTimestamp: number = 0; // 最近一次弹出时间戳
+    private static readonly popupCooldownMs: number = 2000; // 弹窗冷却时长(毫秒)
+
+    private isInCooldown(): boolean {
+        const now = Date.now();
+        return (now - GameEnd.lastPopupTimestamp) < GameEnd.popupCooldownMs;
+    }
 
     onLoad() {
         console.log('[GameEnd] onLoad方法被调用');
@@ -305,6 +315,12 @@ export class GameEnd extends Component {
     private onGameSuccess() {
         console.log('[GameEnd] 接收到GAME_SUCCESS事件');
         console.log('[GameEnd] 游戏成功事件处理,开始统一处理流程');
+
+        // 冷却期内拦截重复的胜利判定,避免二次弹窗/奖励
+        if (this.isInCooldown()) {
+            console.log('[GameEnd] 冷却期内,忽略重复的GAME_SUCCESS事件');
+            return;
+        }
         
         // 1. 清理敌人(阶段一:UI弹出阶段的职责)
         this.clearAllEnemies();
@@ -336,6 +352,12 @@ export class GameEnd extends Component {
     private onGameDefeat() {
         console.log('[GameEnd] 接收到GAME_DEFEAT事件');
         console.log('[GameEnd] 游戏失败事件处理,开始统一处理流程');
+
+        // 冷却期内拦截重复的失败判定,避免二次弹窗/奖励
+        if (this.isInCooldown()) {
+            console.log('[GameEnd] 冷却期内,忽略重复的GAME_DEFEAT事件');
+            return;
+        }
         
         // 1. 清理敌人(阶段一:UI弹出阶段的职责)
         this.clearAllEnemies();
@@ -468,6 +490,12 @@ export class GameEnd extends Component {
      */
     private showEndPanelWithAnimation() {
         console.log('[GameEnd] 开始显示结算面板动画');
+
+        // 若处于冷却期,直接跳过显示,避免重复弹窗
+        if (this.isInCooldown()) {
+            console.log('[GameEnd] 弹窗处于冷却期,跳过本次显示');
+            return;
+        }
         
         // 确保节点处于激活状态
         if (this.node && !this.node.active) {
@@ -490,6 +518,9 @@ export class GameEnd extends Component {
             }
         }
         
+        // 记录弹窗时间戳(无论是否播放动画,都计入冷却)
+        GameEnd.lastPopupTimestamp = Date.now();
+
         // 播放显示动画(如果有的话)
         if (this.animationDuration > 0) {
             console.log(`[GameEnd] 动画持续时间: ${this.animationDuration}秒,开始播放GameEnd面板弹出动画`);
@@ -593,16 +624,29 @@ export class GameEnd extends Component {
         }
         
         console.log('[GameEnd] 点击双倍奖励按钮');
+        // 若广告请求正在进行,拦截重复点击(按钮保持不置灰)
+        if (this.isAdRequestPending) {
+            console.log('[GameEnd] 广告请求进行中,忽略重复点击');
+            return;
+        }
+        // 标记广告请求进行中
+        this.isAdRequestPending = true;
         
         // 显示激励视频广告
         AdManager.getInstance().showRewardedVideoAd(
             () => {
                 // 广告观看完成,给予双倍奖励
                 this.giveDoubleReward();
+                // 广告流程完成,解除请求中状态
+                this.isAdRequestPending = false;
+                GameEnd.lastPopupTimestamp = Date.now();
             },
             (error) => {
                 console.error('[GameEnd] 广告显示失败:', error);
-                // 广告失败时不给予奖励
+                // 广告失败:不返回主界面,保留当前结算面板供用户选择继续
+                // 解除请求中状态,按钮不置灰,允许用户再次尝试
+                this.isAdRequestPending = false;
+                GameEnd.lastPopupTimestamp = Date.now();
             }
         );
     }
@@ -647,12 +691,8 @@ export class GameEnd extends Component {
         
         // 触发货币变化事件
         EventBus.getInstance().emit(GameEvents.CURRENCY_CHANGED);
-        
-        // 触发返回主菜单事件(奖励动画由主界面统一播放)
-        EventBus.getInstance().emit('CONTINUE_CLICK');
-        
-        // 隐藏结算面板(通过动画)
-        this.hideEndPanelWithAnimation();
+
+        // 不自动返回主页面,不隐藏结算面板;由用户点击“继续”按钮决定返回
     }
     
     /**
@@ -703,6 +743,8 @@ export class GameEnd extends Component {
         if (this.doubleButton) {
             this.doubleButton.interactable = true;
         }
+        // 清理广告请求状态,避免跨局残留
+        this.isAdRequestPending = false;
         
         console.log('[GameEnd] UI状态重置完成');
     }

+ 7 - 22
assets/scripts/CombatSystem/MenuSystem/MenuController.ts

@@ -18,7 +18,6 @@ export class MenuController extends Component {
     // UI节点引用
     @property(Node) menuUI: Node = null;           // Canvas/MenuUI
     @property(Button) menuButton: Button = null;   // Canvas/MenuButton (主界面使用)
-    @property(Button) menuButton1: Button = null;  // Canvas/MenueButton-001 (游戏中使用)
     @property(Button) closeButton: Button = null;  // Canvas/MenuUI中的关闭按钮
     @property(Button) backButton: Button = null;   // Canvas/MenuUI/Buttons/BackButton 退出游戏按钮
     @property(Button) continueButton: Button = null; // Canvas/MenuUI/Buttons/ContinueButton 继续游戏按钮
@@ -71,11 +70,6 @@ export class MenuController extends Component {
             this.menuButton.node.on(Button.EventType.CLICK, this.onMenuButtonClick, this);
         }
         
-        // 绑定菜单按钮1点击事件 (游戏中使用)
-        if (this.menuButton1) {
-            this.menuButton1.node.on(Button.EventType.CLICK, this.onMenuButtonClick, this);
-        }
-        
         // 绑定关闭按钮点击事件
         if (this.closeButton) {
             this.closeButton.node.on(Button.EventType.CLICK, this.onCloseButtonClick, this);
@@ -137,27 +131,22 @@ export class MenuController extends Component {
         
         // 在游戏外界面中,检查是否在需要隐藏菜单按钮的界面
         if (currentAppState !== AppState.IN_GAME && this.isInHiddenMenuUI()) {
-            // 在SkillUpUI、UpgradeUI、ShopUI界面中隐藏所有菜单按钮
+            // 在SkillUpUI、UpgradeUI、ShopUI界面中隐藏菜单按钮
             if (this.menuButton) this.menuButton.node.active = false;
-            if (this.menuButton1) this.menuButton1.node.active = false;
             console.log('[MenuController] 在游戏外界面中隐藏菜单按钮');
         } else if (currentAppState === AppState.IN_GAME) {
-            // 第一关隐藏游戏中的菜单按钮
+            // 第一关隐藏游戏中的菜单按钮;其他关卡显示
             if (isFirstLevel) {
                 if (this.menuButton) this.menuButton.node.active = false;
-                if (this.menuButton1) this.menuButton1.node.active = false;
-                console.log('[MenuController] 第一关隐藏MenueButton-001');
+                console.log('[MenuController] 第一关隐藏菜单按钮');
             } else {
-                // 非第一关:游戏中显示menuButton1,隐藏menuButton
-                if (this.menuButton) this.menuButton.node.active = false;
-                if (this.menuButton1) this.menuButton1.node.active = true;
-                console.log('[MenuController] 游戏中显示menuButton1');
+                if (this.menuButton) this.menuButton.node.active = true;
+                console.log('[MenuController] 游戏中显示菜单按钮');
             }
         } else {
-            // 主界面显示menuButton,隐藏menuButton1
+            // 主界面显示菜单按钮
             if (this.menuButton) this.menuButton.node.active = true;
-            if (this.menuButton1) this.menuButton1.node.active = false;
-            console.log('[MenuController] 主界面显示menuButton');
+            console.log('[MenuController] 主界面显示菜单按钮');
         }
     }
     
@@ -406,10 +395,6 @@ export class MenuController extends Component {
             this.menuButton.node.off(Button.EventType.CLICK, this.onMenuButtonClick, this);
         }
         
-        if (this.menuButton1) {
-            this.menuButton1.node.off(Button.EventType.CLICK, this.onMenuButtonClick, this);
-        }
-        
         if (this.closeButton) {
             this.closeButton.node.off(Button.EventType.CLICK, this.onCloseButtonClick, this);
         }

+ 24 - 19
assets/scripts/CombatSystem/SkillSelection/SkillButtonAnimator.ts

@@ -1,5 +1,4 @@
-import { _decorator, Component, Node, Vec3, tween, find, Sprite, Color } from 'cc';
-import BlinkScaleAnimator from '../../Animations/BlinkScaleAnimator';
+import { _decorator, Component, Node, Vec3, tween, find, Sprite, Color, Tween } from 'cc';
 const { ccclass, property } = _decorator;
 
 /**
@@ -15,8 +14,9 @@ export class SkillButtonAnimator extends Component {
 
     private _origScale: Vec3 = new Vec3();
     private _origPos: Vec3 = new Vec3();
-    private _blinkTween: any = null;
-    private _starBlinkComp: BlinkScaleAnimator | null = null;
+    private _blinkTween: Tween<Node> | null = null;
+    private _blinkTweenTarget: Node | null = null;
+    private _origStarScale: Vec3 | null = null;
     
     // 星星颜色配置
     private readonly STAR_ACTIVE_COLOR = new Color(255, 255, 255, 255); // 亮起的星星
@@ -70,32 +70,37 @@ export class SkillButtonAnimator extends Component {
         const sprite = nextStar.getComponent(Sprite);
         if (!sprite) return;
         
-        // 使用通用组件在该星星上播放闪烁缩放(不再使用颜色闪烁
-        if (this._starBlinkComp) {
-            this._starBlinkComp.stop();
-            this._starBlinkComp = null;
+        // 直接使用补间动画控制星星缩放闪烁(不依赖组件
+        if (this._blinkTween) {
+            this._blinkTween.stop();
+            this._blinkTween = null;
         }
-        this._starBlinkComp = BlinkScaleAnimator.ensure(nextStar, {
-            scaleFactor: 1.3,
-            upDuration: 0.15,
-            downDuration: 0.15,
-            easingUp: 'sineOut',
-            easingDown: 'sineIn'
-        });
+        const origScale = nextStar.scale.clone();
+        const targetScale = new Vec3(origScale.x * 1.3, origScale.y * 1.3, origScale.z);
+        this._blinkTweenTarget = nextStar;
+        this._origStarScale = origScale.clone();
+        this._blinkTween = tween(nextStar)
+            .to(0.15, { scale: targetScale }, { easing: 'sineOut' })
+            .to(0.15, { scale: origScale }, { easing: 'sineIn' })
+            .union()
+            .repeatForever()
+            .start();
     }
 
     /**
      * 停止星星闪烁动画
      */
     private stopBlinkAnimation() {
-        if (this._starBlinkComp) {
-            this._starBlinkComp.stop();
-            this._starBlinkComp = null;
-        }
         if (this._blinkTween) {
             this._blinkTween.stop();
             this._blinkTween = null;
         }
+        // 恢复被闪烁星星的原始缩放
+        if (this._blinkTweenTarget && this._blinkTweenTarget.isValid && this._origStarScale) {
+            this._blinkTweenTarget.setScale(this._origStarScale);
+        }
+        this._blinkTweenTarget = null;
+        this._origStarScale = null;
     }
 
     /**

+ 50 - 42
assets/scripts/FourUI/UpgradeSystem/UpgradeAni.ts

@@ -80,40 +80,43 @@ export class UpgradeAni extends Component {
                 console.warn('[UpgradeAni] weaponSpriteNode为null,请在编辑器中设置');
             }
             
-            // 创建缩放动画:放大到1.5倍再立即缩小回原始大小
-            const comp = BlinkScaleAnimator.ensure(weaponIconNode, {
-                scaleFactor: 1.5,
-                upDuration: 0.25,
-                downDuration: 0.25,
-                easingUp: 'sineOut',
-                easingDown: 'sineIn',
-                playOnEnable: false,
-            });
-            comp.play();
-            this.scheduleOnce(() => {
-                comp.stop();
-                // 动画结束后恢复原始材质
-                if (weaponSprite && originalMaterial) {
-                    if (weaponSprite.customMaterial === originalMaterial) {
-                        weaponSprite.material = weaponSprite.customMaterial;
-                    } else {
-                        weaponSprite.material = originalMaterial;
-                    }
-                    console.log('[UpgradeAni] 恢复原始材质成功');
-                } else if (weaponSprite) {
-                    if (weaponSprite.customMaterial) {
-                        weaponSprite.material = weaponSprite.customMaterial;
-                        console.log('[UpgradeAni] 恢复到customMaterial');
-                    } else {
-                        weaponSprite.material = null;
-                        console.log('[UpgradeAni] 恢复到默认材质');
-                    }
-                }
-                resolve();
-            }, 0.25 + 0.25);
+            // 使用代码驱动的缩放动画:放大到1.5倍再缩回原始大小
+            const upDuration = 0.25;
+            const downDuration = 0.25;
+            const scaleFactor = 1.5;
+            const easingUp = 'sineOut';
+            const easingDown = 'sineIn';
             
-            // 播放缩放动画
-            // 使用 BlinkScaleAnimator 播放动画,无需调用 scaleAnimation.start();
+            const targetScale = new Vec3(
+                originalScale.x * scaleFactor,
+                originalScale.y * scaleFactor,
+                originalScale.z
+            );
+            
+            tween(weaponIconNode)
+                .to(upDuration, { scale: targetScale }, { easing: easingUp })
+                .to(downDuration, { scale: originalScale }, { easing: easingDown })
+                .call(() => {
+                    // 动画结束后恢复原始材质
+                    if (weaponSprite && originalMaterial) {
+                        if (weaponSprite.customMaterial === originalMaterial) {
+                            weaponSprite.material = weaponSprite.customMaterial;
+                        } else {
+                            weaponSprite.material = originalMaterial;
+                        }
+                        console.log('[UpgradeAni] 恢复原始材质成功');
+                    } else if (weaponSprite) {
+                        if (weaponSprite.customMaterial) {
+                            weaponSprite.material = weaponSprite.customMaterial;
+                            console.log('[UpgradeAni] 恢复到customMaterial');
+                        } else {
+                            weaponSprite.material = null;
+                            console.log('[UpgradeAni] 恢复到默认材质');
+                        }
+                    }
+                    resolve();
+                })
+                .start();
         });
     }
     
@@ -207,15 +210,20 @@ export class UpgradeAni extends Component {
         // 重置到原始缩放状态
         this.upgradeBtnNode.setScale(Vec3.ONE);
         
-        // 使用通用组件进行循环闪烁(放大缩小)
-        this.blinkComp = BlinkScaleAnimator.ensure(this.upgradeBtnNode, {
-            scaleFactor: 1.5,
-            upDuration: 0.5,
-            downDuration: 0.5,
-            easingUp: 'sineInOut',
-            easingDown: 'sineInOut',
-        });
-        this.blinkComp.play();
+        // 使用预挂载通用组件进行循环闪烁(放大缩小)
+        const comp = this.upgradeBtnNode.getComponent(BlinkScaleAnimator);
+        if (!comp) {
+            console.warn('[UpgradeAni] 未挂载 BlinkScaleAnimator 组件,请在编辑器中为 upgradeBtnNode 预先添加该组件(默认禁用)。');
+        } else {
+            comp.scaleFactor = 1.5;
+            comp.upDuration = 0.5;
+            comp.downDuration = 0.5;
+            comp.easingUp = 'sineInOut';
+            comp.easingDown = 'sineInOut';
+            comp.playOnEnable = false;
+            comp.play();
+            this.blinkComp = comp;
+        }
         
         console.log('[UpgradeAni] 开始升级按钮闪烁动画');
     }

+ 18 - 5
assets/scripts/LevelSystem/GameManager.ts

@@ -15,6 +15,7 @@ import { GameStartMove } from '../Animations/GameStartMove';
 import { StartGame } from './StartGame';
 import { InGameManager, GameState } from './IN_game';
 import { GuideManager } from '../../NewbieGuidePlugin-v1.0.0/NewbieGuidePlugin-v1.0.0/scripts/GuideManager';
+import { WeaponBullet } from '../CombatSystem/WeaponBullet';
 import DragBlockToGridStep from '../Guide/DragBlockToGridStep';
 import { NewbieGuideManager } from '../Core/NewbieGuideManager';
 const { ccclass, property } = _decorator;
@@ -260,6 +261,9 @@ export class GameManager extends Component {
         
         // 监听主菜单按钮点击事件(由UIStateManager转发)
         eventBus.on('CONTINUE_CLICK', this.onMainMenuClick, this);
+
+        // 监听清理子弹事件,确保返回主菜单或清理流程中销毁所有子弹
+        eventBus.on(GameEvents.CLEAR_ALL_BULLETS, this.onClearAllBulletsEvent, this);
         
         // 敌人击杀事件监听已迁移到 InGameManager
     }
@@ -591,11 +595,7 @@ export class GameManager extends Component {
         
         if (mainUIController) {
             // 根据游戏结果决定是否播放奖励动画
-            const inGameManagerForUI = this.getInGameManager();
-            const currentStateForUI = inGameManagerForUI?.getCurrentState();
-            const isSuccessForUI = currentStateForUI === GameState.SUCCESS;
-
-            if (isSuccessForUI && typeof (mainUIController as any).onReturnToMainUIWithReward === 'function') {
+            if (isGameSuccess && typeof (mainUIController as any).onReturnToMainUIWithReward === 'function') {
                 console.log('[GameManager] 游戏胜利,调用返回主界面并播放奖励动画方法');
                 (mainUIController as any).onReturnToMainUIWithReward();
             } else if (typeof (mainUIController as any).onReturnToMainUI === 'function') {
@@ -870,6 +870,7 @@ export class GameManager extends Component {
         eventBus.off(GameEvents.GAME_RESTART, this.onGameRestartEvent, this);
         eventBus.off(GameEvents.RESET_GAME_MANAGER, this.onResetGameManagerEvent, this);
         eventBus.off('CONTINUE_CLICK', this.onMainMenuClick, this);
+        eventBus.off(GameEvents.CLEAR_ALL_BULLETS, this.onClearAllBulletsEvent, this);
         // ENEMY_KILLED事件监听已迁移到 InGameManager
 
         // 按钮事件监听已迁移到 UIStateManager
@@ -880,6 +881,18 @@ export class GameManager extends Component {
         }
     }
 
+    /**
+     * 清理所有子弹(响应 GameEvents.CLEAR_ALL_BULLETS)
+     */
+    private onClearAllBulletsEvent() {
+        try {
+            console.log('[GameManager] 接收到 CLEAR_ALL_BULLETS,开始清理所有子弹');
+            WeaponBullet.clearAllBullets();
+        } catch (e) {
+            console.warn('[GameManager] 清理子弹时发生异常:', e);
+        }
+    }
+
     // === 加载当前关卡配置 ===
     public async loadCurrentLevelConfig() {
         if (!this.saveDataManager || !this.levelConfigManager) return;

Някои файлове не бяха показани, защото твърде много файлове са промени