SoundManager.ts 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256
  1. import { _decorator, Component, AudioSource, AudioClip, Node, sys } from 'cc';
  2. const { ccclass, property, menu } = _decorator;
  3. @ccclass
  4. @menu('Audio/SoundManager')
  5. export class SoundManager extends Component {
  6. // ==================== 播放器通道 ====================
  7. @property(AudioSource)
  8. bgmSource: AudioSource = null!; // 背景音乐
  9. @property(AudioSource)
  10. normalSource: AudioSource = null!; // 常规音效(playOneShot)
  11. @property(AudioSource)
  12. stopableSource: AudioSource = null!; // 可停止音效(如技能)优先使用此音效,多个音效要一起播时使用下面两个备用
  13. @property(AudioSource)
  14. stopableSource2: AudioSource = null!; // 可停止音效(如技能)
  15. @property(AudioSource)
  16. stopableSource3: AudioSource = null!; // 可停止音效(如技能)
  17. @property(AudioSource)
  18. loopSource: AudioSource = null!; // 循环音效(游戏中)
  19. @property(AudioSource)
  20. resultLoopSource: AudioSource = null!; // 循环音效(结果界面)
  21. // ==================== 音效资源(可选预览) ====================
  22. @property(AudioClip)
  23. bgmClip: AudioClip = null!;
  24. @property(AudioClip)
  25. clickClip: AudioClip = null!;
  26. // ==================== 开关状态 ====================
  27. private _musicEnabled: boolean = true;
  28. private _soundEnabled: boolean = true;
  29. private static readonly KEY_MUSIC = 'SoundManager_Music';
  30. private static readonly KEY_SOUND = 'SoundManager_Sound';
  31. // ==================== 生命周期 ====================
  32. start() {
  33. // this.initAudioSources();
  34. this.loadSettings();
  35. this.applyVolume();
  36. // 开关开启时自动播放 BGM
  37. // if (this._musicEnabled && this.bgmClip) {
  38. // this.playBGM();
  39. // }
  40. }
  41. private initAudioSources() {
  42. // 自动创建缺失的 AudioSource
  43. const createSource = (name: string): AudioSource => {
  44. const node = new Node(name);
  45. node.parent = this.node;
  46. const source = node.addComponent(AudioSource);
  47. source.playOnAwake = false;
  48. return source;
  49. };
  50. if (!this.bgmSource) this.bgmSource = createSource('BGM_Source');
  51. if (!this.normalSource) this.normalSource = createSource('Normal_Source');
  52. if (!this.stopableSource) this.stopableSource = createSource('Stopable_Source');
  53. if (!this.loopSource) this.loopSource = createSource('Loop_Source');
  54. }
  55. // ==================== 本地设置 ====================
  56. private loadSettings() {
  57. const m = sys.localStorage.getItem(SoundManager.KEY_MUSIC);
  58. const s = sys.localStorage.getItem(SoundManager.KEY_SOUND);
  59. this._musicEnabled = m === null ? true : m === 'true';
  60. this._soundEnabled = s === null ? true : s === 'true';
  61. }
  62. private saveSettings() {
  63. sys.localStorage.setItem(SoundManager.KEY_MUSIC, this._musicEnabled.toString());
  64. sys.localStorage.setItem(SoundManager.KEY_SOUND, this._soundEnabled.toString());
  65. }
  66. private applyVolume() {
  67. this.bgmSource.volume = this._musicEnabled ? 1 : 0;
  68. // 其他 source 播放时动态控制
  69. }
  70. // ==================== 背景音乐 ====================
  71. public playBGM(clip?: AudioClip) {
  72. if (!this._musicEnabled) return;
  73. const target = clip || this.bgmClip;
  74. if (!target) return;
  75. this.bgmSource.stop();
  76. this.bgmSource.clip = target;
  77. this.bgmSource.loop = true;
  78. this.bgmSource.play();
  79. }
  80. public stopBGM() {
  81. this.bgmSource.stop();
  82. }
  83. public pauseBGM() {
  84. this.bgmSource.pause();
  85. }
  86. public resumeBGM() {
  87. if (this._musicEnabled) this.bgmSource.play();
  88. }
  89. // ==================== 按钮点击常规音效 ====================
  90. public playClick(clip?: AudioClip) {
  91. if (!this._soundEnabled) return;
  92. const target = clip || this.clickClip;
  93. if (target) {
  94. this.normalSource.playOneShot(target, 1.0);
  95. }
  96. }
  97. // ==================== 可停止音效 ====================
  98. public playEffect1(clip?: AudioClip, volume: number = 1) {
  99. if (!this._soundEnabled) return;
  100. const target = clip;
  101. if (!target) return;
  102. this.stopableSource.clip = target;
  103. this.stopableSource.loop = false;
  104. this.stopableSource.volume = volume;
  105. this.stopableSource.play();
  106. }
  107. public stopEffect1() {
  108. this.stopableSource.stop();
  109. }
  110. // ==================== 播放可停止音效 ====================
  111. public playEffect2(clip?: AudioClip, volume: number = 1) {
  112. if (!this._soundEnabled) return;
  113. const target = clip;
  114. if (!target) return;
  115. this.stopableSource2.clip = target;
  116. this.stopableSource2.loop = false;
  117. this.stopableSource.volume = volume;
  118. this.stopableSource2.play();
  119. }
  120. public stopEffect2() {
  121. this.stopableSource2.stop();
  122. }
  123. // ==================== 播放可停止音效 ====================
  124. public playEffect3(clip?: AudioClip, volume: number = 1) {
  125. if (!this._soundEnabled) return;
  126. const target = clip;
  127. if (!target) return;
  128. this.stopableSource3.clip = target;
  129. this.stopableSource3.loop = false;
  130. this.stopableSource.volume = volume;
  131. this.stopableSource3.play();
  132. }
  133. public stopEffect3() {
  134. this.stopableSource3.stop();
  135. }
  136. // ==================== 游戏中循环音效 ====================
  137. public playLoopEffect(clip?: AudioClip, volume?: number) {
  138. if (!this._soundEnabled) return;
  139. const target = clip;
  140. if (!target) return;
  141. this.loopSource.clip = target;
  142. this.loopSource.loop = true;
  143. this.loopSource.volume = volume;
  144. this.loopSource.play();
  145. }
  146. public stopLoopEffect() {
  147. this.loopSource.stop();
  148. }
  149. public setLoopEffectVolume(volume?: number) {
  150. if (!this._soundEnabled) return;
  151. this.loopSource.volume = volume;
  152. }
  153. // ==================== 结果循环音效 ====================
  154. public playResultLoopEffect(clip?: AudioClip, volume?: number) {
  155. if (!this._soundEnabled) return;
  156. const target = clip;
  157. if (!target) return;
  158. this.resultLoopSource.clip = target;
  159. this.resultLoopSource.loop = true;
  160. this.resultLoopSource.volume = volume;
  161. this.resultLoopSource.play();
  162. }
  163. public stopResultLoopEffect() {
  164. this.resultLoopSource.stop();
  165. }
  166. public setResultLoopEffectVolume(volume?: number) {
  167. this.resultLoopSource.volume = volume;
  168. }
  169. // ==================== 全局开关 ====================
  170. public setMusicEnabled(enabled: boolean) {
  171. this._musicEnabled = enabled;
  172. this.bgmSource.volume = enabled ? 1 : 0;
  173. if (!enabled) this.pauseBGM();
  174. else if (this.bgmSource.clip) this.resumeBGM();
  175. this.saveSettings();
  176. }
  177. public setSoundEnabled(enabled: boolean) {
  178. this._soundEnabled = enabled;
  179. if (!enabled) {
  180. this.stopEffect1();
  181. this.stopEffect2();
  182. this.stopEffect3();
  183. this.stopLoopEffect();
  184. }
  185. this.saveSettings();
  186. }
  187. public toggleMusic() { this.setMusicEnabled(!this._musicEnabled); }
  188. public toggleSound() { this.setSoundEnabled(!this._soundEnabled); }
  189. public get musicEnabled() { return this._musicEnabled; }
  190. public get soundEnabled() { return this._soundEnabled; }
  191. // ==================== 生命周期结束 ====================
  192. onDestroy() {
  193. this.stopBGM();
  194. this.stopEffect1();
  195. this.stopEffect2();
  196. this.stopEffect3();
  197. this.stopLoopEffect();
  198. }
  199. }