AudioManager.ts 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732
  1. import { _decorator, Component, AudioClip, AudioSource, resources, Node } from 'cc';
  2. import { BundleLoader } from '../Core/BundleLoader';
  3. const { ccclass, property } = _decorator;
  4. /**
  5. * 音频管理器
  6. * 统一管理游戏中的音乐和音效播放
  7. */
  8. @ccclass('AudioManager')
  9. export class AudioManager extends Component {
  10. // 音频源组件
  11. @property({ type: AudioSource, tooltip: '背景音乐播放器' })
  12. public musicAudioSource: AudioSource = null;
  13. @property({ type: AudioSource, tooltip: 'UI音效播放器' })
  14. public uiSoundAudioSource: AudioSource = null;
  15. @property({ type: AudioSource, tooltip: '敌人音效播放器' })
  16. public enemySoundAudioSource: AudioSource = null;
  17. @property({ type: AudioSource, tooltip: '环境音效播放器' })
  18. public environmentSoundAudioSource: AudioSource = null;
  19. @property({ type: AudioSource, tooltip: '武器音效播放器' })
  20. public weaponSoundAudioSource: AudioSource = null;
  21. // 音量设置
  22. private musicVolume: number = 0.8;
  23. private uiSoundVolume: number = 0.8;
  24. private enemySoundVolume: number = 0.8;
  25. private environmentSoundVolume: number = 0.8;
  26. private weaponSoundVolume: number = 0.8;
  27. // 主音效音量(向后兼容)
  28. private soundEffectVolume: number = 0.8;
  29. // 当前播放的音乐
  30. private currentMusicClip: AudioClip = null;
  31. // Bundle加载器
  32. private bundleLoader: BundleLoader = null;
  33. // 音频缓存
  34. private audioClipCache: Map<string, AudioClip> = new Map();
  35. // 单例实例
  36. private static _instance: AudioManager = null;
  37. start() {
  38. // 设置单例
  39. if (AudioManager._instance === null) {
  40. AudioManager._instance = this;
  41. // 防止场景切换时被销毁
  42. // this.node.parent = null;
  43. // director.addPersistRootNode(this.node);
  44. } else {
  45. this.node.destroy();
  46. return;
  47. }
  48. this.bundleLoader = BundleLoader.getInstance();
  49. this.initializeAudioSources();
  50. }
  51. /**
  52. * 获取单例实例
  53. */
  54. public static getInstance(): AudioManager {
  55. return AudioManager._instance;
  56. }
  57. /**
  58. * 初始化音频源
  59. */
  60. private initializeAudioSources() {
  61. // 如果没有指定音频源,尝试自动创建
  62. if (!this.musicAudioSource) {
  63. const musicNode = new Node('MusicAudioSource');
  64. musicNode.parent = this.node;
  65. this.musicAudioSource = musicNode.addComponent(AudioSource);
  66. this.musicAudioSource.loop = true;
  67. }
  68. if (!this.uiSoundAudioSource) {
  69. const uiSfxNode = new Node('UISoundAudioSource');
  70. uiSfxNode.parent = this.node;
  71. this.uiSoundAudioSource = uiSfxNode.addComponent(AudioSource);
  72. this.uiSoundAudioSource.loop = false;
  73. }
  74. if (!this.enemySoundAudioSource) {
  75. const enemySfxNode = new Node('EnemySoundAudioSource');
  76. enemySfxNode.parent = this.node;
  77. this.enemySoundAudioSource = enemySfxNode.addComponent(AudioSource);
  78. this.enemySoundAudioSource.loop = false;
  79. }
  80. if (!this.environmentSoundAudioSource) {
  81. const envSfxNode = new Node('EnvironmentSoundAudioSource');
  82. envSfxNode.parent = this.node;
  83. this.environmentSoundAudioSource = envSfxNode.addComponent(AudioSource);
  84. this.environmentSoundAudioSource.loop = false;
  85. }
  86. if (!this.weaponSoundAudioSource) {
  87. const weaponSfxNode = new Node('WeaponSoundAudioSource');
  88. weaponSfxNode.parent = this.node;
  89. this.weaponSoundAudioSource = weaponSfxNode.addComponent(AudioSource);
  90. this.weaponSoundAudioSource.loop = false;
  91. }
  92. // 设置初始音量
  93. this.updateAllVolumes();
  94. }
  95. /**
  96. * 播放背景音乐
  97. * @param musicPath 音乐资源路径
  98. * @param loop 是否循环播放
  99. */
  100. public async playMusic(musicPath: string, loop: boolean = true) {
  101. if (!this.musicAudioSource) {
  102. console.warn('[AudioManager] 音乐播放器未初始化');
  103. return;
  104. }
  105. try {
  106. // 转换路径格式:从 "data/弹球音效/fight bgm" 转换为 "弹球音效/fight bgm"
  107. const bundlePath = musicPath.replace('data/', '');
  108. const clip = await this.bundleLoader.loadAssetFromBundle<AudioClip>('data', bundlePath, AudioClip);
  109. this.currentMusicClip = clip;
  110. this.musicAudioSource.clip = clip;
  111. this.musicAudioSource.loop = loop;
  112. this.musicAudioSource.play();
  113. console.log(`[AudioManager] 播放音乐: ${musicPath}`);
  114. } catch (error) {
  115. console.error(`[AudioManager] 加载音乐失败: ${musicPath}`, error);
  116. }
  117. }
  118. /**
  119. * 停止背景音乐
  120. */
  121. public stopMusic() {
  122. if (this.musicAudioSource && this.musicAudioSource.playing) {
  123. this.musicAudioSource.stop();
  124. console.log('[AudioManager] 停止音乐播放');
  125. }
  126. }
  127. /**
  128. * 暂停背景音乐
  129. */
  130. public pauseMusic() {
  131. if (this.musicAudioSource && this.musicAudioSource.playing) {
  132. this.musicAudioSource.pause();
  133. console.log('[AudioManager] 暂停音乐播放');
  134. }
  135. }
  136. /**
  137. * 恢复背景音乐
  138. */
  139. public resumeMusic() {
  140. if (this.musicAudioSource && this.currentMusicClip) {
  141. this.musicAudioSource.play();
  142. console.log('[AudioManager] 恢复音乐播放');
  143. }
  144. }
  145. /**
  146. * 播放音效(向后兼容)
  147. * @param sfxPath 音效资源路径
  148. * @param volume 音量(可选,0-1)
  149. */
  150. public playSoundEffect(sfxPath: string, volume?: number) {
  151. this.playUISound(sfxPath, volume);
  152. }
  153. /**
  154. * 播放UI音效
  155. * @param sfxPath 音效资源路径
  156. * @param volume 音量(可选,0-1)
  157. */
  158. public playUISound(sfxPath: string, volume?: number) {
  159. this.playSound(this.uiSoundAudioSource, sfxPath, volume, this.uiSoundVolume, 'UI音效');
  160. }
  161. /**
  162. * 播放敌人音效
  163. * @param sfxPath 音效资源路径
  164. * @param volume 音量(可选,0-1)
  165. */
  166. public playEnemySound(sfxPath: string, volume?: number) {
  167. this.playSound(this.enemySoundAudioSource, sfxPath, volume, this.enemySoundVolume, '敌人音效');
  168. }
  169. /**
  170. * 播放环境音效
  171. * @param sfxPath 音效资源路径
  172. * @param volume 音量(可选,0-1)
  173. */
  174. public playEnvironmentSound(sfxPath: string, volume?: number) {
  175. this.playSound(this.environmentSoundAudioSource, sfxPath, volume, this.environmentSoundVolume, '环境音效');
  176. }
  177. /**
  178. * 播放武器音效
  179. * @param sfxPath 音效资源路径
  180. * @param volume 音量(可选,0-1)
  181. */
  182. public playWeaponSound(sfxPath: string, volume?: number) {
  183. this.playSound(this.weaponSoundAudioSource, sfxPath, volume, this.weaponSoundVolume, '武器音效');
  184. }
  185. /**
  186. * 通用音效播放方法
  187. * @param audioSource 音频源
  188. * @param sfxPath 音效资源路径
  189. * @param volume 音量(可选,0-1)
  190. * @param baseVolume 基础音量
  191. * @param soundType 音效类型(用于日志)
  192. */
  193. private async playSound(audioSource: AudioSource, sfxPath: string, volume: number | undefined, baseVolume: number, soundType: string) {
  194. if (!audioSource) {
  195. console.warn(`[AudioManager] ${soundType}播放器未初始化`);
  196. return;
  197. }
  198. try {
  199. // 去掉.mp3后缀(如果存在)
  200. const cleanPath = sfxPath.endsWith('.mp3') ? sfxPath.slice(0, -4) : sfxPath;
  201. // 检查缓存中是否已有该音频
  202. let clip = this.audioClipCache.get(cleanPath);
  203. if (!clip) {
  204. // 转换路径格式:从 "data/弹球音效/xxx" 转换为 "弹球音效/xxx"
  205. const bundlePath = cleanPath.replace('data/', '');
  206. console.log(`[AudioManager] 从Bundle加载新音效: ${bundlePath}`);
  207. clip = await this.bundleLoader.loadAssetFromBundle<AudioClip>('data', bundlePath, AudioClip);
  208. // 缓存音频资源
  209. this.audioClipCache.set(cleanPath, clip);
  210. console.log(`[AudioManager] 音效已缓存: ${cleanPath}`);
  211. } else {
  212. console.log(`[AudioManager] 使用缓存音效: ${cleanPath}`);
  213. }
  214. // 设置临时音量(如果指定)
  215. const originalVolume = audioSource.volume;
  216. if (volume !== undefined) {
  217. audioSource.volume = volume * baseVolume;
  218. }
  219. audioSource.playOneShot(clip);
  220. // 恢复原始音量
  221. if (volume !== undefined) {
  222. audioSource.volume = originalVolume;
  223. }
  224. console.log(`[AudioManager] 播放${soundType}: ${cleanPath}`);
  225. } catch (error) {
  226. console.error(`[AudioManager] 加载${soundType}失败: ${sfxPath}`, error);
  227. }
  228. }
  229. /**
  230. * 设置音乐音量
  231. * @param volume 音量值(0-1)
  232. */
  233. public setMusicVolume(volume: number) {
  234. this.musicVolume = Math.max(0, Math.min(1, volume));
  235. this.updateMusicVolume();
  236. //console.log(`[AudioManager] 设置音乐音量: ${this.musicVolume}`);
  237. }
  238. /**
  239. * 设置音效音量(向后兼容)
  240. * @param volume 音量值(0-1)
  241. */
  242. public setSoundEffectVolume(volume: number) {
  243. this.soundEffectVolume = Math.max(0, Math.min(1, volume));
  244. this.setUISoundVolume(volume);
  245. //console.log(`[AudioManager] 设置音效音量: ${this.soundEffectVolume}`);
  246. }
  247. /**
  248. * 设置UI音效音量
  249. * @param volume 音量值(0-1)
  250. */
  251. public setUISoundVolume(volume: number) {
  252. this.uiSoundVolume = Math.max(0, Math.min(1, volume));
  253. this.updateUISoundVolume();
  254. }
  255. /**
  256. * 设置敌人音效音量
  257. * @param volume 音量值(0-1)
  258. */
  259. public setEnemySoundVolume(volume: number) {
  260. this.enemySoundVolume = Math.max(0, Math.min(1, volume));
  261. this.updateEnemySoundVolume();
  262. }
  263. /**
  264. * 设置环境音效音量
  265. * @param volume 音量值(0-1)
  266. */
  267. public setEnvironmentSoundVolume(volume: number) {
  268. this.environmentSoundVolume = Math.max(0, Math.min(1, volume));
  269. this.updateEnvironmentSoundVolume();
  270. }
  271. /**
  272. * 设置武器音效音量
  273. * @param volume 音量值(0-1)
  274. */
  275. public setWeaponSoundVolume(volume: number) {
  276. this.weaponSoundVolume = Math.max(0, Math.min(1, volume));
  277. this.updateWeaponSoundVolume();
  278. }
  279. /**
  280. * 设置所有音效音量
  281. * @param volume 音量值(0-1)
  282. */
  283. public setAllSoundVolume(volume: number) {
  284. this.setUISoundVolume(volume);
  285. this.setEnemySoundVolume(volume);
  286. this.setEnvironmentSoundVolume(volume);
  287. this.setWeaponSoundVolume(volume);
  288. this.setSoundEffectVolume(volume);
  289. }
  290. /**
  291. * 获取音乐音量
  292. */
  293. public getMusicVolume(): number {
  294. return this.musicVolume;
  295. }
  296. /**
  297. * 获取音效音量(向后兼容)
  298. */
  299. public getSoundEffectVolume(): number {
  300. return this.soundEffectVolume;
  301. }
  302. /**
  303. * 获取UI音效音量
  304. */
  305. public getUISoundVolume(): number {
  306. return this.uiSoundVolume;
  307. }
  308. /**
  309. * 获取敌人音效音量
  310. */
  311. public getEnemySoundVolume(): number {
  312. return this.enemySoundVolume;
  313. }
  314. /**
  315. * 获取环境音效音量
  316. */
  317. public getEnvironmentSoundVolume(): number {
  318. return this.environmentSoundVolume;
  319. }
  320. /**
  321. * 获取武器音效音量
  322. */
  323. public getWeaponSoundVolume(): number {
  324. return this.weaponSoundVolume;
  325. }
  326. /**
  327. * 更新音乐音量
  328. */
  329. private updateMusicVolume() {
  330. if (this.musicAudioSource) {
  331. this.musicAudioSource.volume = this.musicVolume;
  332. }
  333. }
  334. /**
  335. * 更新UI音效音量
  336. */
  337. private updateUISoundVolume() {
  338. if (this.uiSoundAudioSource) {
  339. this.uiSoundAudioSource.volume = this.uiSoundVolume;
  340. }
  341. }
  342. /**
  343. * 更新敌人音效音量
  344. */
  345. private updateEnemySoundVolume() {
  346. if (this.enemySoundAudioSource) {
  347. this.enemySoundAudioSource.volume = this.enemySoundVolume;
  348. }
  349. }
  350. /**
  351. * 更新环境音效音量
  352. */
  353. private updateEnvironmentSoundVolume() {
  354. if (this.environmentSoundAudioSource) {
  355. this.environmentSoundAudioSource.volume = this.environmentSoundVolume;
  356. }
  357. }
  358. /**
  359. * 更新武器音效音量
  360. */
  361. private updateWeaponSoundVolume() {
  362. if (this.weaponSoundAudioSource) {
  363. this.weaponSoundAudioSource.volume = this.weaponSoundVolume;
  364. }
  365. }
  366. /**
  367. * 更新所有音量
  368. */
  369. private updateAllVolumes() {
  370. this.updateMusicVolume();
  371. this.updateUISoundVolume();
  372. this.updateEnemySoundVolume();
  373. this.updateEnvironmentSoundVolume();
  374. this.updateWeaponSoundVolume();
  375. }
  376. /**
  377. * 静音所有音频
  378. */
  379. public muteAll() {
  380. this.setMusicVolume(0);
  381. this.setAllSoundVolume(0);
  382. }
  383. /**
  384. * 恢复所有音频音量
  385. */
  386. public unmuteAll() {
  387. this.setMusicVolume(0.8);
  388. this.setAllSoundVolume(0.8);
  389. }
  390. /**
  391. * 静音所有音效(保留音乐)
  392. */
  393. public muteAllSounds() {
  394. this.setAllSoundVolume(0);
  395. }
  396. /**
  397. * 恢复所有音效音量(保留音乐)
  398. */
  399. public unmuteAllSounds() {
  400. this.setAllSoundVolume(0.8);
  401. }
  402. /**
  403. * 检查音乐是否正在播放
  404. */
  405. public isMusicPlaying(): boolean {
  406. return this.musicAudioSource && this.musicAudioSource.playing;
  407. }
  408. /**
  409. * 清理音频缓存
  410. */
  411. public clearAudioCache() {
  412. console.log(`[AudioManager] 清理音频缓存,共${this.audioClipCache.size}个音效`);
  413. this.audioClipCache.clear();
  414. }
  415. /**
  416. * 获取缓存状态
  417. */
  418. public getCacheInfo(): {count: number, paths: string[]} {
  419. return {
  420. count: this.audioClipCache.size,
  421. paths: Array.from(this.audioClipCache.keys())
  422. };
  423. }
  424. onDestroy() {
  425. // 清理音频缓存
  426. this.clearAudioCache();
  427. if (AudioManager._instance === this) {
  428. AudioManager._instance = null;
  429. }
  430. }
  431. }
  432. /**
  433. * 音频管理器的静态接口
  434. * 提供便捷的全局访问方法
  435. */
  436. export class Audio {
  437. /**
  438. * 播放背景音乐
  439. */
  440. static playMusic(musicPath: string, loop: boolean = true) {
  441. const manager = AudioManager.getInstance();
  442. if (manager) {
  443. manager.playMusic(musicPath, loop);
  444. }
  445. }
  446. /**
  447. * 播放音效(向后兼容)
  448. */
  449. static playSFX(sfxPath: string, volume?: number) {
  450. const manager = AudioManager.getInstance();
  451. if (manager) {
  452. manager.playSoundEffect(sfxPath, volume);
  453. }
  454. }
  455. /**
  456. * 播放UI音效
  457. */
  458. static playUISound(sfxPath: string, volume?: number) {
  459. const manager = AudioManager.getInstance();
  460. if (manager) {
  461. manager.playUISound(sfxPath, volume);
  462. }
  463. }
  464. /**
  465. * 播放敌人音效
  466. */
  467. static playEnemySound(sfxPath: string, volume?: number) {
  468. const manager = AudioManager.getInstance();
  469. if (manager) {
  470. manager.playEnemySound(sfxPath, volume);
  471. }
  472. }
  473. /**
  474. * 播放环境音效
  475. */
  476. static playEnvironmentSound(sfxPath: string, volume?: number) {
  477. const manager = AudioManager.getInstance();
  478. if (manager) {
  479. manager.playEnvironmentSound(sfxPath, volume);
  480. }
  481. }
  482. /**
  483. * 播放武器音效
  484. */
  485. static playWeaponSound(sfxPath: string, volume?: number) {
  486. const manager = AudioManager.getInstance();
  487. if (manager) {
  488. manager.playWeaponSound(sfxPath, volume);
  489. }
  490. }
  491. /**
  492. * 设置音乐音量
  493. */
  494. static setMusicVolume(volume: number) {
  495. const manager = AudioManager.getInstance();
  496. if (manager) {
  497. manager.setMusicVolume(volume);
  498. }
  499. }
  500. /**
  501. * 设置音效音量(向后兼容)
  502. */
  503. static setSFXVolume(volume: number) {
  504. const manager = AudioManager.getInstance();
  505. if (manager) {
  506. manager.setSoundEffectVolume(volume);
  507. }
  508. }
  509. /**
  510. * 设置UI音效音量
  511. */
  512. static setUISoundVolume(volume: number) {
  513. const manager = AudioManager.getInstance();
  514. if (manager) {
  515. manager.setUISoundVolume(volume);
  516. }
  517. }
  518. /**
  519. * 设置敌人音效音量
  520. */
  521. static setEnemySoundVolume(volume: number) {
  522. const manager = AudioManager.getInstance();
  523. if (manager) {
  524. manager.setEnemySoundVolume(volume);
  525. }
  526. }
  527. /**
  528. * 设置环境音效音量
  529. */
  530. static setEnvironmentSoundVolume(volume: number) {
  531. const manager = AudioManager.getInstance();
  532. if (manager) {
  533. manager.setEnvironmentSoundVolume(volume);
  534. }
  535. }
  536. /**
  537. * 设置武器音效音量
  538. */
  539. static setWeaponSoundVolume(volume: number) {
  540. const manager = AudioManager.getInstance();
  541. if (manager) {
  542. manager.setWeaponSoundVolume(volume);
  543. }
  544. }
  545. /**
  546. * 设置所有音效音量
  547. */
  548. static setAllSoundVolume(volume: number) {
  549. const manager = AudioManager.getInstance();
  550. if (manager) {
  551. manager.setAllSoundVolume(volume);
  552. }
  553. }
  554. /**
  555. * 停止音乐
  556. */
  557. static stopMusic() {
  558. const manager = AudioManager.getInstance();
  559. if (manager) {
  560. manager.stopMusic();
  561. }
  562. }
  563. /**
  564. * 暂停音乐
  565. */
  566. static pauseMusic() {
  567. const manager = AudioManager.getInstance();
  568. if (manager) {
  569. manager.pauseMusic();
  570. }
  571. }
  572. /**
  573. * 恢复音乐
  574. */
  575. static resumeMusic() {
  576. const manager = AudioManager.getInstance();
  577. if (manager) {
  578. manager.resumeMusic();
  579. }
  580. }
  581. /**
  582. * 静音所有音频
  583. */
  584. static muteAll() {
  585. const manager = AudioManager.getInstance();
  586. if (manager) {
  587. manager.muteAll();
  588. }
  589. }
  590. /**
  591. * 恢复所有音频
  592. */
  593. static unmuteAll() {
  594. const manager = AudioManager.getInstance();
  595. if (manager) {
  596. manager.unmuteAll();
  597. }
  598. }
  599. /**
  600. * 静音所有音效(保留音乐)
  601. */
  602. static muteAllSounds() {
  603. const manager = AudioManager.getInstance();
  604. if (manager) {
  605. manager.muteAllSounds();
  606. }
  607. }
  608. /**
  609. * 恢复所有音效(保留音乐)
  610. */
  611. static unmuteAllSounds() {
  612. const manager = AudioManager.getInstance();
  613. if (manager) {
  614. manager.unmuteAllSounds();
  615. }
  616. }
  617. /**
  618. * 清理音频缓存
  619. */
  620. static clearAudioCache() {
  621. const manager = AudioManager.getInstance();
  622. if (manager) {
  623. manager.clearAudioCache();
  624. }
  625. }
  626. /**
  627. * 获取缓存信息
  628. */
  629. static getCacheInfo(): {count: number, paths: string[]} {
  630. const manager = AudioManager.getInstance();
  631. if (manager) {
  632. return manager.getCacheInfo();
  633. }
  634. return {count: 0, paths: []};
  635. }
  636. }