BallAni.ts 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142
  1. import { _decorator, Component, Node, Vec3, instantiate, UITransform, resources, sp, find, JsonAsset } from 'cc';
  2. const { ccclass, property } = _decorator;
  3. /**
  4. * 小球撞击动画管理器
  5. * 负责在小球撞击时播放特效动画
  6. */
  7. @ccclass('BallAni')
  8. export class BallAni extends Component {
  9. // 撞击特效预制体缓存
  10. private static impactEffectSkeleton: sp.SkeletonData = null;
  11. private static isLoading: boolean = false;
  12. // 当前播放中的特效节点列表
  13. private activeEffects: Node[] = [];
  14. start() {
  15. // 预加载撞击特效资源
  16. this.preloadImpactEffect();
  17. }
  18. /**
  19. * 预加载撞击特效资源
  20. */
  21. private preloadImpactEffect() {
  22. if (BallAni.impactEffectSkeleton || BallAni.isLoading) {
  23. return;
  24. }
  25. BallAni.isLoading = true;
  26. const path = 'Animation/WeaponTx/tx0005/tx0005';
  27. resources.load(path, sp.SkeletonData, (err, sData: sp.SkeletonData) => {
  28. BallAni.isLoading = false;
  29. if (err || !sData) {
  30. console.warn('[BallAni] 加载撞击特效失败:', err);
  31. return;
  32. }
  33. BallAni.impactEffectSkeleton = sData;
  34. console.log('[BallAni] 撞击特效资源加载成功');
  35. });
  36. }
  37. /**
  38. * 在指定位置播放撞击特效
  39. * @param worldPosition 世界坐标位置
  40. */
  41. public playImpactEffect(worldPosition: Vec3) {
  42. // 如果资源未加载,直接加载并播放
  43. if (!BallAni.impactEffectSkeleton) {
  44. const path = 'Animation/WeaponTx/tx0005/tx0005';
  45. resources.load(path, sp.SkeletonData, (err, sData: sp.SkeletonData) => {
  46. if (err || !sData) {
  47. console.warn('[BallAni] 加载撞击特效失败:', err);
  48. return;
  49. }
  50. BallAni.impactEffectSkeleton = sData;
  51. this.createAndPlayEffect(worldPosition, sData);
  52. });
  53. return;
  54. }
  55. this.createAndPlayEffect(worldPosition, BallAni.impactEffectSkeleton);
  56. }
  57. /**
  58. * 创建并播放特效
  59. */
  60. private createAndPlayEffect(worldPosition: Vec3, skeletonData: sp.SkeletonData) {
  61. const effectNode = new Node('ImpactEffect');
  62. const skeleton = effectNode.addComponent(sp.Skeleton);
  63. skeleton.skeletonData = skeletonData;
  64. skeleton.premultipliedAlpha = false;
  65. skeleton.setAnimation(0, 'animation', false);
  66. skeleton.setCompleteListener(() => {
  67. this.removeEffect(effectNode);
  68. });
  69. const canvas = find('Canvas');
  70. if (canvas) {
  71. canvas.addChild(effectNode);
  72. effectNode.setWorldPosition(worldPosition);
  73. // 设置特效缩放
  74. effectNode.setScale(0.8, 0.8, 1);
  75. // 添加到活动特效列表
  76. this.activeEffects.push(effectNode);
  77. } else {
  78. effectNode.destroy();
  79. }
  80. }
  81. /**
  82. * 移除特效节点
  83. * @param effectNode 要移除的特效节点
  84. */
  85. private removeEffect(effectNode: Node) {
  86. // 从活动特效列表中移除
  87. const index = this.activeEffects.indexOf(effectNode);
  88. if (index !== -1) {
  89. this.activeEffects.splice(index, 1);
  90. }
  91. // 销毁节点
  92. if (effectNode && effectNode.isValid) {
  93. effectNode.destroy();
  94. }
  95. }
  96. /**
  97. * 清理所有活动的特效
  98. */
  99. public clearAllEffects() {
  100. for (const effect of this.activeEffects) {
  101. if (effect && effect.isValid) {
  102. effect.destroy();
  103. }
  104. }
  105. this.activeEffects = [];
  106. }
  107. onDestroy() {
  108. // 组件销毁时清理所有特效
  109. this.clearAllEffects();
  110. }
  111. /**
  112. * 获取单例实例(如果需要全局访问)
  113. */
  114. public static getInstance(): BallAni | null {
  115. const gameArea = find('Canvas/GameLevelUI/GameArea');
  116. if (!gameArea) {
  117. return null;
  118. }
  119. let ballAni = gameArea.getComponent(BallAni);
  120. if (!ballAni) {
  121. ballAni = gameArea.addComponent(BallAni);
  122. }
  123. return ballAni;
  124. }
  125. }