EnemyAttackEffectManager.ts 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117
  1. import { _decorator, Component, Node, Vec3, director, sp, NodePool, find } from 'cc';
  2. const { ccclass, property } = _decorator;
  3. export enum AttackEffectType {
  4. Mage = 'mage',
  5. Archer = 'archer',
  6. }
  7. @ccclass('EnemyAttackEffectManager')
  8. export class EnemyAttackEffectManager extends Component {
  9. public static instance: EnemyAttackEffectManager = null;
  10. @property({ type: sp.SkeletonData })
  11. public mageImpactEffect: sp.SkeletonData = null;
  12. @property({ type: sp.SkeletonData })
  13. public archerImpactEffect: sp.SkeletonData = null;
  14. @property
  15. public mageImpactAnimName: string = 'hit';
  16. @property
  17. public archerImpactAnimName: string = 'hit';
  18. @property
  19. public effectScale: number = 1.0;
  20. private magePool: NodePool = new NodePool();
  21. private archerPool: NodePool = new NodePool();
  22. onLoad() {
  23. EnemyAttackEffectManager.instance = this;
  24. console.log(`[EnemyAttackEffectManager] 组件已加载,挂载节点: ${this.node?.name}`);
  25. }
  26. /**
  27. * 在指定世界坐标播放墙体命中特效
  28. */
  29. public playImpactEffect(type: AttackEffectType, worldPos: Vec3): void {
  30. const parent = this.resolveEffectParent();
  31. if (!parent) {
  32. console.warn('[EnemyAttackEffectManager] 未找到特效父节点,取消播放');
  33. return;
  34. }
  35. const node = this.getEffectNode(type);
  36. console.log(`[EnemyAttackEffectManager] 准备播放命中特效: type=${type}, 位置=(${worldPos.x.toFixed(1)}, ${worldPos.y.toFixed(1)}),父节点=${parent.name}`);
  37. parent.addChild(node);
  38. node.setWorldPosition(worldPos);
  39. const skeleton = node.getComponent(sp.Skeleton);
  40. if (!skeleton) {
  41. // 没有Spine组件,不播放(安全返回)
  42. console.warn('[EnemyAttackEffectManager] 节点无sp.Skeleton组件,跳过播放并回收');
  43. this.recycleNode(type, node);
  44. return;
  45. }
  46. if (!skeleton.skeletonData) {
  47. console.warn('[EnemyAttackEffectManager] 未绑定SkeletonData资源,无法播放特效');
  48. this.recycleNode(type, node);
  49. return;
  50. }
  51. const animName = type === AttackEffectType.Mage ? this.mageImpactAnimName : this.archerImpactAnimName;
  52. if (animName) {
  53. console.log(`[EnemyAttackEffectManager] setAnimation: ${animName}`);
  54. skeleton.setAnimation(0, animName, false);
  55. skeleton.setCompleteListener(() => {
  56. console.log('[EnemyAttackEffectManager] 特效播放完成,回收节点');
  57. this.recycleNode(type, node);
  58. });
  59. } else {
  60. // 未提供动画名时,直接回收
  61. console.warn('[EnemyAttackEffectManager] 未提供动画名,直接回收节点');
  62. this.recycleNode(type, node);
  63. }
  64. }
  65. private resolveEffectParent(): Node | null {
  66. // 优先挂在GameArea,保障层级正确
  67. const parent = find('Canvas/GameLevelUI/GameArea') || find('Canvas/GameLevelUI') || director.getScene();
  68. if (parent) {
  69. console.log(`[EnemyAttackEffectManager] 选择特效父节点: ${parent.name}`);
  70. }
  71. return parent;
  72. }
  73. private getEffectNode(type: AttackEffectType): Node {
  74. const pool = type === AttackEffectType.Mage ? this.magePool : this.archerPool;
  75. const data = type === AttackEffectType.Mage ? this.mageImpactEffect : this.archerImpactEffect;
  76. let node = pool.get();
  77. if (!node) {
  78. node = new Node(`EnemyWallImpact_${type}`);
  79. const skeleton = node.addComponent(sp.Skeleton);
  80. skeleton.skeletonData = data;
  81. console.log(`[EnemyAttackEffectManager] 新建特效节点: ${node.name},绑定数据=${data ? '有' : '无'}`);
  82. } else {
  83. const skeleton = node.getComponent(sp.Skeleton);
  84. if (skeleton) skeleton.skeletonData = data;
  85. console.log(`[EnemyAttackEffectManager] 复用池中节点: ${node.name},重新绑定数据=${data ? '有' : '无'}`);
  86. }
  87. // 统一缩放到 X/Y 轴,Z 轴保持 1
  88. node.setScale(new Vec3(this.effectScale, this.effectScale, 1));
  89. console.log(`[EnemyAttackEffectManager] 设置缩放: ${this.effectScale}`);
  90. return node;
  91. }
  92. private recycleNode(type: AttackEffectType, node: Node): void {
  93. if (!node || !node.isValid) return;
  94. node.removeFromParent();
  95. const pool = type === AttackEffectType.Mage ? this.magePool : this.archerPool;
  96. pool.put(node);
  97. console.log(`[EnemyAttackEffectManager] 节点已回收到对象池: ${node.name}`);
  98. }
  99. }