BallAni.ts 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242
  1. import { _decorator, Component, Node, Vec3, tween, Color,find, Sprite, resources, sp } from 'cc';
  2. import EventBus, { GameEvents } from '../Core/EventBus';
  3. const { ccclass, property } = _decorator;
  4. /**
  5. * 小球撞击动画管理器
  6. * 负责在小球撞击时播放特效动画
  7. */
  8. @ccclass('BallAni')
  9. export class BallAni extends Component {
  10. // 撞击特效预制体缓存
  11. private static impactEffectSkeleton: sp.SkeletonData = null;
  12. private static isLoading: boolean = false;
  13. // 当前播放中的特效节点列表
  14. private activeEffects: Node[] = [];
  15. // 当前播放中的方块动画列表
  16. private activeBlockAnimations: Map<Node, any> = new Map();
  17. start() {
  18. // 预加载撞击特效资源
  19. this.preloadImpactEffect();
  20. }
  21. /**
  22. * 播放方块被撞击动画
  23. * @param blockNode 方块节点
  24. */
  25. public playBlockHitAnimation(blockNode: Node) {
  26. if (!blockNode || !blockNode.isValid) {
  27. console.warn('[BallAni] 方块节点无效');
  28. return;
  29. }
  30. // 如果该方块已经在播放动画,跳过
  31. if (this.activeBlockAnimations.has(blockNode)) {
  32. console.log('[BallAni] 方块动画已在播放中,跳过');
  33. return;
  34. }
  35. // 检查是否有活跃敌人,只有有敌人时才播放缩放动画
  36. const eventBus = EventBus.getInstance();
  37. let hasActiveEnemies = false;
  38. // 通过事件系统检查是否有活跃敌人
  39. eventBus.emit(GameEvents.BALL_FIRE_BULLET, {
  40. canFire: (canFire: boolean) => {
  41. hasActiveEnemies = canFire;
  42. }
  43. });
  44. console.log('[BallAni] 开始播放方块撞击动画', blockNode.name, '有敌人:', hasActiveEnemies);
  45. const sprite = blockNode.getComponent(Sprite);
  46. const originalScale = blockNode.scale.clone();
  47. console.log('[BallAni] 原始缩放:', originalScale);
  48. if (hasActiveEnemies) {
  49. // 有敌人时播放缩放动画
  50. const shrinkScale = originalScale.clone().multiplyScalar(0.5);
  51. const animationTween = tween(blockNode)
  52. .to(0.2, { scale: shrinkScale })
  53. .to(0.2, { scale: originalScale })
  54. .call(() => {
  55. console.log('[BallAni] 方块动画完成,恢复原状');
  56. // 动画完成,从活动列表中移除
  57. this.activeBlockAnimations.delete(blockNode);
  58. })
  59. .start();
  60. // 添加到活动动画列表
  61. this.activeBlockAnimations.set(blockNode, animationTween);
  62. } else {
  63. // 没有敌人时直接结束动画
  64. const animationTween = tween({})
  65. .delay(0.2)
  66. .call(() => {
  67. console.log('[BallAni] 无敌人,直接恢复原状');
  68. // 动画完成,从活动列表中移除
  69. this.activeBlockAnimations.delete(blockNode);
  70. })
  71. .start();
  72. // 添加到活动动画列表
  73. this.activeBlockAnimations.set(blockNode, animationTween);
  74. }
  75. }
  76. /**
  77. * 预加载撞击特效资源
  78. */
  79. private preloadImpactEffect() {
  80. if (BallAni.impactEffectSkeleton || BallAni.isLoading) {
  81. return;
  82. }
  83. BallAni.isLoading = true;
  84. const path = 'Animation/WeaponTx/tx0005/tx0005';
  85. resources.load(path, sp.SkeletonData, (err, sData: sp.SkeletonData) => {
  86. BallAni.isLoading = false;
  87. if (err || !sData) {
  88. console.warn('[BallAni] 加载撞击特效失败:', err);
  89. return;
  90. }
  91. BallAni.impactEffectSkeleton = sData;
  92. console.log('[BallAni] 撞击特效资源加载成功');
  93. });
  94. }
  95. /**
  96. * 在指定位置播放撞击特效
  97. * @param worldPosition 世界坐标位置
  98. */
  99. public playImpactEffect(worldPosition: Vec3) {
  100. // 如果资源未加载,直接加载并播放
  101. if (!BallAni.impactEffectSkeleton) {
  102. const path = 'Animation/WeaponTx/tx0005/tx0005';
  103. resources.load(path, sp.SkeletonData, (err, sData: sp.SkeletonData) => {
  104. if (err || !sData) {
  105. console.warn('[BallAni] 加载撞击特效失败:', err);
  106. return;
  107. }
  108. BallAni.impactEffectSkeleton = sData;
  109. this.createAndPlayEffect(worldPosition, sData);
  110. });
  111. return;
  112. }
  113. this.createAndPlayEffect(worldPosition, BallAni.impactEffectSkeleton);
  114. }
  115. /**
  116. * 创建并播放特效
  117. */
  118. private createAndPlayEffect(worldPosition: Vec3, skeletonData: sp.SkeletonData) {
  119. const effectNode = new Node('ImpactEffect');
  120. const skeleton = effectNode.addComponent(sp.Skeleton);
  121. skeleton.skeletonData = skeletonData;
  122. skeleton.premultipliedAlpha = false;
  123. skeleton.setAnimation(0, 'animation', false);
  124. skeleton.setCompleteListener(() => {
  125. this.removeEffect(effectNode);
  126. });
  127. const canvas = find('Canvas');
  128. if (canvas) {
  129. canvas.addChild(effectNode);
  130. effectNode.setWorldPosition(worldPosition);
  131. // 设置特效缩放
  132. effectNode.setScale(0.8, 0.8, 1);
  133. // 添加到活动特效列表
  134. this.activeEffects.push(effectNode);
  135. } else {
  136. effectNode.destroy();
  137. }
  138. }
  139. /**
  140. * 移除特效节点
  141. * @param effectNode 要移除的特效节点
  142. */
  143. private removeEffect(effectNode: Node) {
  144. // 从活动特效列表中移除
  145. const index = this.activeEffects.indexOf(effectNode);
  146. if (index !== -1) {
  147. this.activeEffects.splice(index, 1);
  148. }
  149. // 销毁节点
  150. if (effectNode && effectNode.isValid) {
  151. effectNode.destroy();
  152. }
  153. }
  154. /**
  155. * 停止指定方块的动画
  156. * @param blockNode 方块节点
  157. */
  158. public stopBlockAnimation(blockNode: Node) {
  159. const animation = this.activeBlockAnimations.get(blockNode);
  160. if (animation) {
  161. animation.stop();
  162. this.activeBlockAnimations.delete(blockNode);
  163. }
  164. }
  165. /**
  166. * 清理所有活动的特效
  167. */
  168. public clearAllEffects() {
  169. for (const effect of this.activeEffects) {
  170. if (effect && effect.isValid) {
  171. effect.destroy();
  172. }
  173. }
  174. this.activeEffects = [];
  175. }
  176. /**
  177. * 清除所有方块动画
  178. */
  179. public clearAllBlockAnimations() {
  180. // 停止所有方块动画
  181. for (const [blockNode, animation] of this.activeBlockAnimations) {
  182. if (animation) {
  183. animation.stop();
  184. }
  185. }
  186. this.activeBlockAnimations.clear();
  187. }
  188. onDestroy() {
  189. // 组件销毁时清理所有特效和动画
  190. this.clearAllEffects();
  191. this.clearAllBlockAnimations();
  192. }
  193. /**
  194. * 获取BallAni实例
  195. * @returns BallAni实例
  196. */
  197. public static getInstance(): BallAni | null {
  198. const gameArea = find('Canvas/GameLevelUI/GameArea');
  199. if (!gameArea) {
  200. console.warn('[BallAni] 未找到GameArea节点');
  201. return null;
  202. }
  203. const ballAni = gameArea.getComponent(BallAni);
  204. if (!ballAni) {
  205. console.warn('[BallAni] GameArea节点上未找到BallAni组件');
  206. return null;
  207. }
  208. return ballAni;
  209. }
  210. }