WeaponEffectManager.ts 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243
  1. import { _decorator, Component, Node, Vec3, find, sp, Color } from 'cc';
  2. import { BundleLoader } from '../../Core/BundleLoader';
  3. import { Audio } from '../../AudioManager/AudioManager';
  4. const { ccclass, property } = _decorator;
  5. /**
  6. * 武器特效管理器
  7. * 负责处理敌人武器的击中特效、轨迹特效、音效等
  8. * 挂载到Canvas/GameLevelUI/EnemyController上使用
  9. */
  10. @ccclass('WeaponEffectManager')
  11. export class WeaponEffectManager extends Component {
  12. // 当前播放中的特效节点列表
  13. private activeEffects: Node[] = [];
  14. // 特效缓存
  15. private effectCache: Map<string, sp.SkeletonData> = new Map();
  16. onLoad() {
  17. console.log('[WeaponEffectManager] 武器特效管理器已加载');
  18. }
  19. onDestroy() {
  20. this.clearAllEffects();
  21. }
  22. /**
  23. * 播放武器特效
  24. * @param weaponEffects 武器特效配置
  25. * @param position 特效播放位置
  26. * @param attackType 攻击类型
  27. */
  28. public async playWeaponEffects(weaponEffects: any, position: Vec3, attackType: string) {
  29. if (!weaponEffects) {
  30. return;
  31. }
  32. // 播放击中特效
  33. if (weaponEffects.hitEffect) {
  34. await this.playHitEffect(weaponEffects.hitEffect, position, weaponEffects.visualScale || 1.0);
  35. }
  36. // 播放轨迹特效(主要用于远程攻击)
  37. if (weaponEffects.trailEffect && (attackType === 'magic_projectile' || attackType === 'arrow_projectile')) {
  38. await this.playTrailEffect(weaponEffects.trailEffect, position, weaponEffects.visualScale || 1.0);
  39. }
  40. // 播放音效
  41. if (weaponEffects.impactSound) {
  42. this.playImpactSound(weaponEffects.impactSound);
  43. }
  44. }
  45. /**
  46. * 播放击中特效
  47. * @param effectName 特效名称
  48. * @param position 播放位置
  49. * @param scale 缩放比例
  50. */
  51. private async playHitEffect(effectName: string, position: Vec3, scale: number = 1.0) {
  52. const effectPath = this.getEffectPath(effectName);
  53. if (!effectPath) {
  54. console.warn(`[WeaponEffectManager] 未知的击中特效: ${effectName}`);
  55. return;
  56. }
  57. await this.createAndPlayEffect(effectPath, position, scale, false);
  58. }
  59. /**
  60. * 播放轨迹特效
  61. * @param effectName 特效名称
  62. * @param position 播放位置
  63. * @param scale 缩放比例
  64. */
  65. private async playTrailEffect(effectName: string, position: Vec3, scale: number = 1.0) {
  66. const effectPath = this.getEffectPath(effectName);
  67. if (!effectPath) {
  68. console.warn(`[WeaponEffectManager] 未知的轨迹特效: ${effectName}`);
  69. return;
  70. }
  71. await this.createAndPlayEffect(effectPath, position, scale, false);
  72. }
  73. /**
  74. * 播放撞击音效
  75. * @param soundName 音效名称
  76. */
  77. private playImpactSound(soundName: string) {
  78. const soundPath = this.getSoundPath(soundName);
  79. if (soundPath) {
  80. Audio.playWeaponSound(soundPath);
  81. } else {
  82. console.warn(`[WeaponEffectManager] 未知的撞击音效: ${soundName}`);
  83. }
  84. }
  85. /**
  86. * 获取特效路径
  87. * @param effectName 特效名称
  88. */
  89. private getEffectPath(effectName: string): string | null {
  90. const effectPaths: { [key: string]: string } = {
  91. 'blood_splash': 'WeaponTx/tx0001/tx0001',
  92. 'magic_burst': 'WeaponTx/tx0002/tx0002',
  93. 'arrow_impact': 'WeaponTx/tx0003/tx0003',
  94. 'weapon_clash': 'WeaponTx/tx0004/tx0004',
  95. 'fire_explosion': 'WeaponTx/tx0005/tx0005',
  96. 'ice_shatter': 'WeaponTx/tx0006/tx0006',
  97. 'lightning_strike': 'WeaponTx/tx0007/tx0007',
  98. 'dark_energy': 'WeaponTx/tx0008/tx0008'
  99. };
  100. return effectPaths[effectName] || null;
  101. }
  102. /**
  103. * 获取音效路径
  104. * @param soundName 音效名称
  105. */
  106. private getSoundPath(soundName: string): string | null {
  107. const soundPaths: { [key: string]: string } = {
  108. 'melee_hit': 'data/弹球音效/melee_hit',
  109. 'magic_impact': 'data/弹球音效/magic_impact',
  110. 'arrow_hit': 'data/弹球音效/arrow_hit',
  111. 'weapon_clash': 'data/弹球音效/weapon_clash',
  112. 'explosion': 'data/弹球音效/explosion',
  113. 'ice_break': 'data/弹球音效/ice_break',
  114. 'lightning': 'data/弹球音效/lightning',
  115. 'dark_magic': 'data/弹球音效/dark_magic'
  116. };
  117. return soundPaths[soundName] || null;
  118. }
  119. /**
  120. * 创建并播放特效
  121. * @param effectPath 特效路径
  122. * @param position 播放位置
  123. * @param scale 缩放比例
  124. * @param loop 是否循环播放
  125. */
  126. private async createAndPlayEffect(effectPath: string, position: Vec3, scale: number = 1.0, loop: boolean = false) {
  127. try {
  128. // 尝试从缓存获取特效数据
  129. let skeletonData = this.effectCache.get(effectPath);
  130. if (!skeletonData) {
  131. // 从Bundle加载特效数据
  132. skeletonData = await BundleLoader.loadSkeletonData(effectPath);
  133. if (!skeletonData) {
  134. console.warn(`[WeaponEffectManager] 加载特效失败: ${effectPath}`);
  135. return;
  136. }
  137. // 缓存特效数据
  138. this.effectCache.set(effectPath, skeletonData);
  139. }
  140. // 创建特效节点
  141. const effectNode = new Node('WeaponEffect');
  142. const skeleton = effectNode.addComponent(sp.Skeleton);
  143. skeleton.skeletonData = skeletonData;
  144. skeleton.premultipliedAlpha = false;
  145. // 设置动画
  146. skeleton.setAnimation(0, 'animation', loop);
  147. // 设置完成监听器(非循环动画)
  148. if (!loop) {
  149. skeleton.setCompleteListener(() => {
  150. this.removeEffect(effectNode);
  151. });
  152. }
  153. // 添加到Canvas并设置位置和缩放
  154. const canvas = find('Canvas');
  155. if (canvas) {
  156. canvas.addChild(effectNode);
  157. effectNode.setWorldPosition(position);
  158. effectNode.setScale(scale, scale, 1);
  159. // 设置特效颜色(增加亮度)
  160. const color = new Color(255, 255, 255, 255);
  161. color.r = Math.min(255, color.r * 1.2);
  162. color.g = Math.min(255, color.g * 1.2);
  163. color.b = Math.min(255, color.b * 1.2);
  164. skeleton.color = color;
  165. // 添加到活动特效列表
  166. this.activeEffects.push(effectNode);
  167. console.log(`[WeaponEffectManager] 播放武器特效: ${effectPath}`);
  168. } else {
  169. effectNode.destroy();
  170. console.warn('[WeaponEffectManager] 未找到Canvas节点,无法播放特效');
  171. }
  172. } catch (error) {
  173. console.error(`[WeaponEffectManager] 播放特效失败: ${effectPath}`, error);
  174. }
  175. }
  176. /**
  177. * 移除特效节点
  178. * @param effectNode 要移除的特效节点
  179. */
  180. private removeEffect(effectNode: Node) {
  181. // 从活动特效列表中移除
  182. const index = this.activeEffects.indexOf(effectNode);
  183. if (index !== -1) {
  184. this.activeEffects.splice(index, 1);
  185. }
  186. // 销毁节点
  187. if (effectNode && effectNode.isValid) {
  188. effectNode.destroy();
  189. }
  190. }
  191. /**
  192. * 清理所有活动的特效
  193. */
  194. public clearAllEffects() {
  195. for (const effect of this.activeEffects) {
  196. if (effect && effect.isValid) {
  197. effect.destroy();
  198. }
  199. }
  200. this.activeEffects = [];
  201. console.log('[WeaponEffectManager] 清理所有武器特效');
  202. }
  203. /**
  204. * 清理特效缓存
  205. */
  206. public clearEffectCache() {
  207. this.effectCache.clear();
  208. console.log('[WeaponEffectManager] 清理特效缓存');
  209. }
  210. }