import { _decorator, Component, Node, Vec3, tween, Color,find, Sprite, resources, sp, Material } from 'cc'; import EventBus, { GameEvents } from '../Core/EventBus'; 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[] = []; // 当前播放中的方块动画列表 private activeBlockAnimations: Map = new Map(); start() { // 预加载撞击特效资源 this.preloadImpactEffect(); } /** * 播放方块被撞击动画 * @param blockNode 方块节点 */ public playBlockHitAnimation(blockNode: Node) { if (!blockNode || !blockNode.isValid) { console.warn('[BallAni] 方块节点无效'); return; } // 如果该方块已经在播放动画,跳过 if (this.activeBlockAnimations.has(blockNode)) { console.log('[BallAni] 方块动画已在播放中,跳过'); return; } // 检查是否有活跃敌人,只有有敌人时才播放缩放动画 const eventBus = EventBus.getInstance(); let hasActiveEnemies = false; // 通过事件系统检查是否有活跃敌人 eventBus.emit(GameEvents.BALL_FIRE_BULLET, { canFire: (canFire: boolean) => { hasActiveEnemies = canFire; } }); // 如果没有活跃敌人,延迟一小段时间再次检查(处理游戏启动时的时序问题) if (!hasActiveEnemies) { console.log('[BallAni] 首次检查未发现敌人,启动延迟检查机制'); // 延迟0.5秒后再次检查,给敌人生成一些时间 this.scheduleOnce(() => { console.log('[BallAni] 开始延迟检查敌人状态'); eventBus.emit(GameEvents.BALL_FIRE_BULLET, { canFire: (canFire: boolean) => { hasActiveEnemies = canFire; } }); console.log('[BallAni] 延迟检查敌人状态结果:', hasActiveEnemies); if (hasActiveEnemies) { console.log('[BallAni] 延迟检查成功找到敌人,执行完整动画'); } else { console.log('[BallAni] 延迟检查仍未找到敌人,执行简化动画'); } // 如果延迟检查后仍然没有敌人,则按无敌人处理 this.executeBlockAnimation(blockNode, hasActiveEnemies); }, 0.5); return; // 提前返回,等待延迟检查 } console.log('[BallAni] 开始播放方块撞击动画', blockNode.name, '有敌人:', hasActiveEnemies); console.log('[BallAni] hasActiveEnemies 检查结果:', hasActiveEnemies); // 立即执行动画 this.executeBlockAnimation(blockNode, hasActiveEnemies); } /** * 执行方块动画的具体逻辑 * @param blockNode 方块节点 * @param hasActiveEnemies 是否有活跃敌人 */ private executeBlockAnimation(blockNode: Node, hasActiveEnemies: boolean) { if (!blockNode || !blockNode.isValid) { console.warn('[BallAni] 方块节点无效'); return; } // 如果该方块已经在播放动画,跳过 if (this.activeBlockAnimations.has(blockNode)) { console.log('[BallAni] 方块动画已在播放中,跳过'); return; } const sprite = blockNode.getComponent(Sprite); // 保存原始材质和Sprite缩放 const originalMaterial = sprite ? sprite.material : null; const originalSpriteScale = sprite ? new Vec3(1, 1, 1) : new Vec3(1, 1, 1); console.log('[BallAni] 开始方块视觉缩放动画,有敌人:', hasActiveEnemies); if (hasActiveEnemies) { // 有敌人时播放Sprite缩放动画(不影响碰撞体积) const shrinkSpriteScale = new Vec3(0.5, 0.5, 1); // 应用材质 console.log('[BallAni] 检查材质状态:', { hasSprite: !!sprite, hasCustomMaterial: sprite ? !!sprite.customMaterial : false, currentMaterial: sprite ? sprite.material : null }); if (sprite && sprite.customMaterial) { sprite.material = sprite.customMaterial; console.log('[BallAni] 应用自定义材质成功'); } else if (sprite) { // 动态加载灰色材质 const grayMaterialPath = 'shaders/ui-sprite-white-material.mtl'; resources.load(grayMaterialPath, Material, (err, material) => { if (!err && material && sprite && sprite.isValid) { sprite.material = material; console.log('[BallAni] 动态加载并应用灰色材质成功'); } else { console.log('[BallAni] 无法加载灰色材质,不使用任何材质'); } }); } // 对Sprite节点进行缩放动画(不影响方块碰撞体积) const animationTween = tween(sprite.node) .to(0.2, { scale: shrinkSpriteScale }) .to(0.2, { scale: originalSpriteScale }) .call(() => { // 动画完成时恢复原始材质 console.log('[BallAni] 恢复材质状态:', { hasSprite: !!sprite, hasOriginalMaterial: !!originalMaterial, currentMaterial: sprite ? sprite.material : null }); if (sprite && originalMaterial) { sprite.material = originalMaterial; console.log('[BallAni] 恢复原始材质成功'); } else { console.log('[BallAni] 无法恢复原始材质 - 原始材质丢失'); } console.log('[BallAni] 方块动画完成,恢复原状'); // 动画完成,从活动列表中移除 this.activeBlockAnimations.delete(blockNode); }) .start(); // 添加到活动动画列表 this.activeBlockAnimations.set(blockNode, animationTween); } else { // 没有敌人时直接结束动画,不应用材质 const animationTween = tween({}) .delay(0.2) .call(() => { console.log('[BallAni] 无敌人,直接恢复原状'); // 动画完成,从活动列表中移除 this.activeBlockAnimations.delete(blockNode); }) .start(); // 添加到活动动画列表 this.activeBlockAnimations.set(blockNode, animationTween); } } /** * 预加载撞击特效资源 */ 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(); } } /** * 停止指定方块的动画 * @param blockNode 方块节点 */ public stopBlockAnimation(blockNode: Node) { const animation = this.activeBlockAnimations.get(blockNode); if (animation) { animation.stop(); this.activeBlockAnimations.delete(blockNode); } } /** * 清理所有活动的特效 */ public clearAllEffects() { for (const effect of this.activeEffects) { if (effect && effect.isValid) { effect.destroy(); } } this.activeEffects = []; } /** * 清除所有方块动画 */ public clearAllBlockAnimations() { // 停止所有方块动画 for (const [blockNode, animation] of this.activeBlockAnimations) { if (animation) { animation.stop(); } } this.activeBlockAnimations.clear(); } onDestroy() { // 组件销毁时清理所有特效和动画 this.clearAllEffects(); this.clearAllBlockAnimations(); } /** * 获取BallAni实例 * @returns BallAni实例 */ public static getInstance(): BallAni | null { const gameArea = find('Canvas/GameLevelUI/GameArea'); if (!gameArea) { console.warn('[BallAni] 未找到GameArea节点'); return null; } const ballAni = gameArea.getComponent(BallAni); if (!ballAni) { console.warn('[BallAni] GameArea节点上未找到BallAni组件'); return null; } return ballAni; } }