AudioManager.ts 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245
  1. import { _decorator,AudioClip, AudioSource} from 'cc';
  2. import { Singleton } from './Singleton';
  3. import { bundleMgr } from './BundleManager';
  4. import { Constants } from '../../data/Constants';
  5. import { settingData } from '../../data/SettingData';
  6. import { Logger } from '../../extend/Logger';
  7. import { Utils } from '../../utils/Utils';
  8. const { ccclass, property } = _decorator;
  9. /**音乐播放数据结构*/
  10. export interface IAudioData {
  11. clip: AudioClip;//音乐片段
  12. audioSource: AudioSource;//音乐具柄
  13. path: string //音乐名称路径
  14. playStatus: boolean //播放状态
  15. totalTime: number;//音乐总长度时间
  16. time_id: number;//定时器句柄
  17. t: number;//统计记时
  18. }
  19. @ccclass('AudioManager')
  20. class AudioManager extends Singleton{
  21. //缓存的音乐
  22. private cachedAudioArr: Array<IAudioData> = [];
  23. /**
  24. * 预加载音乐
  25. * 这儿前面 bundleManager资源包已经 loadLaunchBundles加载过了
  26. * 可以直接 再次
  27. */
  28. public async initialize(): Promise<void> {
  29. const audioAssets = Object.entries(Constants.audios)
  30. .map(([key, path]) => ({
  31. path: path,
  32. type: AudioClip
  33. }))
  34. .filter(asset => !!asset.path);
  35. await bundleMgr.loadMultipleAssets(Constants.bundleName.audios,
  36. audioAssets,
  37. (progress) => {/*进度条0~1*/},
  38. (err:Error, clip: AudioClip, path) => {
  39. if(err) {
  40. Logger.error("加载资源失败:", err);
  41. }else{
  42. this.cachedAudioArr.push({
  43. clip: clip,
  44. audioSource: null,
  45. path: path,
  46. playStatus: false,
  47. totalTime: clip.getDuration(),
  48. time_id: -1,
  49. t: 0
  50. });
  51. }
  52. }
  53. );
  54. }
  55. /**
  56. * 播放音效 不会两个相同的音乐重复播放 可以停止
  57. * @param path 播放音乐的路径
  58. * @param loop 是否循环播放
  59. * @param s 如果为-1则最大音量立即播放 如果为其他则慢慢音量递增起来播放
  60. * @returns
  61. */
  62. private bgVolumeMax: number = 0.8;
  63. public async play(path: string,loop: boolean = false,s: number = -1): Promise<void> {
  64. if(Utils.isNull(path)
  65. ||!settingData.data.bgMusic)return;
  66. const fun = (audio:IAudioData)=>{
  67. if(audio.audioSource == null){
  68. audio.audioSource = this.audioSourceLoop(loop);
  69. }
  70. if(audio.playStatus)return;
  71. audio.audioSource.clip = audio.clip;
  72. if(audio.time_id){
  73. clearInterval(audio.time_id);
  74. if(audio.audioSource.playing){
  75. audio.audioSource.pause();
  76. }
  77. }
  78. audio.audioSource.play();
  79. if(s == -1){
  80. audio.audioSource.volume = this.bgVolumeMax;
  81. }else{
  82. audio.t = 0;
  83. audio.audioSource.volume = 0;
  84. let interval: number = ((1 / s) / 10);
  85. audio.time_id = setInterval(()=>{
  86. audio.t += interval;
  87. let volume: number = audio.t / s;
  88. if(volume >= this.bgVolumeMax){
  89. clearInterval(audio.time_id);
  90. }else{
  91. audio.audioSource.volume = volume;
  92. }
  93. },interval * 1000);
  94. }
  95. audio.playStatus = true;
  96. }
  97. let a: IAudioData = this.cachedAudioArr.find(e =>e.path == path);
  98. if(a){
  99. fun(a);
  100. }else{
  101. await bundleMgr.loadAsset(Constants.bundleName.audios,path,
  102. AudioClip,(err: Error, clip: AudioClip) => {
  103. if(err) {
  104. Logger.error("加载资源失败:", err);
  105. }else{
  106. let data:IAudioData = {
  107. path: path,
  108. clip: clip,
  109. audioSource: null,
  110. playStatus: false,
  111. totalTime: clip.getDuration(),
  112. time_id: -1,
  113. t: 0
  114. };
  115. this.cachedAudioArr.push(data);
  116. fun(data);
  117. }
  118. });
  119. }
  120. }
  121. /**
  122. * 单次播放一次的音效 可以停止
  123. * @param path 音乐路径
  124. * @param overlap 是否可以重复两个同一个音效连续播放
  125. * @returns
  126. */
  127. public async playOneShot(path: string,overlap: boolean = true): Promise<void> {
  128. if(Utils.isNull(path)
  129. ||!settingData.data.soundFx)return;
  130. const fun = (audio:IAudioData)=>{
  131. if(overlap || !audio.playStatus){
  132. if(audio.audioSource == null){
  133. audio.audioSource = this.audioSourceLoop(false);
  134. }
  135. audio.audioSource.clip = audio.clip;
  136. audio.audioSource.play();
  137. audio.audioSource.volume = 1;
  138. audio.playStatus = true;
  139. setTimeout(()=>{
  140. audio.playStatus = false;
  141. },audio.totalTime * 1000)
  142. }
  143. }
  144. let a: IAudioData = this.cachedAudioArr.find(e =>e.path == path);
  145. if(a){
  146. fun(a);
  147. }else{
  148. await bundleMgr.loadAsset(Constants.bundleName.audios,path,
  149. AudioClip,(err: Error, clip: AudioClip) => {
  150. if(err) {
  151. Logger.error("加载资源失败:", err);
  152. }else{
  153. let data:IAudioData = {
  154. path: path,
  155. clip: clip,
  156. audioSource: null,
  157. playStatus: false,
  158. totalTime: clip.getDuration(),
  159. time_id: -1,
  160. t: 0
  161. };
  162. this.cachedAudioArr.push(data);
  163. fun(data);
  164. }
  165. });
  166. }
  167. }
  168. /**
  169. * 停止播放音效
  170. * @param path 音效路径名称
  171. * @param s 为-1立即停止播放 非-1则是慢慢的声音越来越小的停止播放
  172. * @returns
  173. */
  174. public stop(path: string,s: number = -1): void {
  175. if(Utils.isNull(path))return;
  176. let audio: IAudioData = this.cachedAudioArr.find(e =>e.path == path);
  177. if(audio){
  178. if(!audio.audioSource)return;
  179. if(!audio.audioSource.playing)return;
  180. if(audio.time_id){
  181. clearInterval(audio.time_id);
  182. }
  183. let audioSource = audio.audioSource;
  184. if(s == -1){
  185. audioSource?.pause();
  186. }else{
  187. audio.t = 0;
  188. let interval: number = ((1 / s) / 10);
  189. audio.time_id = setInterval(()=>{
  190. if(audioSource){
  191. audio.t += interval;
  192. let volume: number = this.bgVolumeMax - audio.t / s;
  193. if(volume <= 0){
  194. clearInterval(audio.time_id);
  195. audioSource?.pause();
  196. }else{
  197. audioSource.volume = volume <= 0 ? 0 :volume;
  198. }
  199. }
  200. },interval * 1000);
  201. }
  202. audio.playStatus = false;
  203. }
  204. }
  205. /**
  206. * 加载音效
  207. * @param loop 是否循环
  208. */
  209. public audioSourceLoop(loop: boolean): AudioSource{
  210. let audioSource: AudioSource = null;
  211. if(loop){//播放单次的时候具柄
  212. audioSource = this.getAudioSource(null,true,this.bgVolumeMax)
  213. }else{//循环播放的时候具柄
  214. audioSource = this.getAudioSource(null,false,1);
  215. }
  216. return audioSource;
  217. }
  218. /**
  219. * 得到一个播放音乐的控制类
  220. * @param clip 音效片段
  221. * @param loop 是否循环
  222. * @param volume 音量
  223. * @returns 播放类
  224. */
  225. public getAudioSource(clip: AudioClip = null, loop: boolean = false, volume: number = 1): AudioSource{
  226. let audioSource: AudioSource = new AudioSource();
  227. if(clip){
  228. audioSource.clip = clip;
  229. }
  230. audioSource.loop = loop;
  231. audioSource.volume = volume;
  232. return audioSource;
  233. }
  234. }
  235. //全局单例
  236. export const audioMgr = AudioManager.ins();