import { AssetManager, AudioClip, Component, director, isValid, misc, Node, sys, tween, UIOpacity, Vec3 } from "cc"; import { PREVIEW } from "cc/env"; import { EBundleName } from "../const/GameEnum"; import { SoundType } from "../const/GameInterface"; import { EGlobalEvent } from "../MesEvent/EGlobalEvent"; import { ESounds } from "../MesEvent/ESounds"; import { EResUrls } from "../ResUrl/EResUrls"; import ResLoad from "../ResUrl/ResLoad"; import AudioEx from "./AudioEx"; import SoundNodePool from "./SoundNodePool"; import MesMgr from "../MesEvent/MesMgr"; import LocalStorage from "../Mgr_LocalData/LocalStorage"; const PreUIType = (value: SoundType)=>{ return value.url + "_" + value.bundle; } /** * 音效工具类 * 必须要先预加载音效,不然播放会有延迟 */ export default class AudioUtils extends Component { // private static _ins: AudioUtils; /** 单例 */ public static get ins(): AudioUtils { if (this._ins == null) { let audio = new Node("AudioUtils"); director.addPersistRootNode(audio); audio.addComponent(UIOpacity).opacity = 0; this._ins = audio.addComponent(AudioUtils); } return this._ins; } private readonly audioSaveKey = "audioSetting"; private audioSetting = { music: true, effect: true, musicVolume: 1, effectVolume: 1, } private _firstClick: boolean = false; set firstClick(va: boolean) { if (this._firstClick != va) { this._firstClick = va; if (this._firstClick) { if (this._MusicRes && sys.os == sys.OS.IOS) { this.rePlayBgm(); } }else{ if (this._audioBgm) { if(this._audioBgm.volume != this.audioSetting.musicVolume){ this.setMusicVolume(this.audioSetting.musicVolume); this.rePlayBgm(); }else if(!this._audioBgm.isPlaying() && this._MusicRes){ this.rePlayBgm(); } } } } } get firstClick(): boolean { return this._firstClick; } /**背景音效 */ private _audioBgm: AudioEx; private _MusicRes: SoundType; /**辅助列表 */ private keyFrameRate: Map = new Map(); /**记录所有正在播放的音效 */ private playingSounds: {key: string, audio: AudioEx, timestamp: number}[] = []; /**同一key最大同时播放数 */ private readonly MAX_SAME_KEY_SOUNDS: number = 2; /**所有音效最大同时播放数 */ private readonly MAX_TOTAL_SOUNDS: number = 8; protected onLoad(): void { this._audioBgm = new Node("Bgm").addComponent(AudioEx); this._audioBgm.init(false, this.node); MesMgr.on(EGlobalEvent.停止所有音效, this.StopAllSound, this); let Value = LocalStorage.getItem(this.audioSaveKey); ConsoleEx.Log("获取的音效配置 ",Value) if (!Value) { Value = this.audioSetting; } this.audioSetting = Value; this.setMusicVolume(this.audioSetting.musicVolume); this.initsoundCoold(); this.setonAudioInterruptionEnd(); } setonAudioInterruptionEnd(){ if( window["wx"] && window["wx"].onAudioInterruptionEnd){ window["wx"].onAudioInterruptionEnd(()=>{ if(!isValid(this.node)){return;} this.scheduleOnce(()=>{ this.rePlayBgm(); },0.1) ConsoleEx.Warn("音乐播放过长自动中断") }); } } protected onDestroy(): void { this.StopAllSound(); SoundNodePool.soundNodePool.length = 0; AudioUtils._ins = null; MesMgr.offAllCaller(this) } private saveSetting(){ LocalStorage.setItem(this.audioSaveKey,this.audioSetting); } /**切换背景音开关 */ switchMusic() { this.audioSetting.music = !this.audioSetting.music; if (!this.audioSetting.music) { //停止音乐播放 this._audioBgm.stop(); }else{ this._MusicRes && this.playBgm(this._MusicRes); } this.saveSetting(); return this.audioSetting.music; } /** * 获取背景音开关状态 */ get Music() { return this.audioSetting.music; } /** * 获取背景音音量状态 */ get MusicVolume() { return this.audioSetting.musicVolume; } /**切换音效开关 */ switchEffect() { this.audioSetting.effect = !this.audioSetting.effect; if (!this.audioSetting.effect) { //停止音效播放 this.StopAllSound(); } this.saveSetting(); } /** * 获取音效开关状态 */ get Effect() { return this.audioSetting.effect; } /** * 获取音效音量状态 */ get EffectVolume() { return this.audioSetting.effectVolume; } /**设置背景音量 */ setMusicVolume(volume:number){ volume = +volume.toFixed(3) volume = misc.clampf(volume,0,0.25); this.audioSetting.musicVolume = volume; this._audioBgm.volume = volume; this.saveSetting(); } /**设置音效音量 */ setEffectVolume(volume:number){ volume = +volume.toFixed(3) volume = misc.clampf(volume,0,1); this.audioSetting.effectVolume = volume; this.saveSetting(); } //初始化 public init() { this.preLoadSounds(); } /**音效预加载处理 */ private preLoadSounds(){ let tempMap:Map = new Map(); for (const key in ESounds) { const tConfig = ESounds[key] as SoundType; if (!tempMap.has(tConfig.bundle)) { tempMap.set(tConfig.bundle, ["Sound"]) } } let pretimes = Date.now(); tempMap.forEach((value:string[],key:string)=>{ ResLoad.Ins.loadBundle(key,(e,bundle:AssetManager.Bundle)=>{ for (let i = 0; i < value.length; i++) { const tdir = value[i]; bundle.preloadDir(tdir, (e, data: any[]) => { if (e) { ConsoleEx.Error("音效资源预加载错误"); return; } ConsoleEx.Com("音效资源预加载完成,耗时", Date.now() - pretimes); }); } }) }) } private getSoundUrl(url: string, bundle:string| EBundleName): AudioClip { return ResLoad.Ins.GetRes(url, bundle, true, AudioClip); } private StopAllSound(){ this.keyFrameRate.clear(); this.playingSounds.length = 0; } //#region 音效处理 //#endregion /**播放音效 */ private _soundCooldowns: Map = new Map(); private readonly DEFAULT_COOLDOWN: number = 0; // 默认0秒冷却 private _soundCooldownConfig: Partial> = { }; initsoundCoold(){ // this._soundCooldownConfig[PreUIType(ESounds.So_prompt)] = 0.3; // this._soundCooldownConfig[PreUIType(ESounds.So_huode2)] = 0.1; } playEffect(value: SoundType, callback?: Function, Pos?: Vec3) { if(!value || !this.audioSetting.effect){return;} if(!value._key){ value._key = PreUIType(value); } const Soundkey = value._key; // 获取冷却时间配置 const cooldown = this._soundCooldownConfig[Soundkey] || this.DEFAULT_COOLDOWN; if (cooldown) { const now = Date.now(); const lastPlayTime = this._soundCooldowns.get(Soundkey) || 0; if (now - lastPlayTime < cooldown * 1000) { return; // 在冷却时间内,不播放 } this._soundCooldowns.set(Soundkey, now); } // 检查是否超过同key最大播放数 if (this.keyFrameRate.has(Soundkey)) { const sameKeySounds = this.keyFrameRate.get(Soundkey); if (sameKeySounds.length >= this.MAX_SAME_KEY_SOUNDS) { // 停止最早的一个 const oldest = sameKeySounds.shift(); if (oldest) { oldest.audio.stop(); // SoundNodePool.pushPoxy(oldest.audio); // 从总播放列表中移除 const index = this.playingSounds.findIndex(item => item.audio === oldest.audio); if (index !== -1) { this.playingSounds.splice(index, 1); } } } } else { this.keyFrameRate.set(Soundkey, []); } // 检查是否超过总最大播放数 if (this.playingSounds.length >= this.MAX_TOTAL_SOUNDS) { // 停止最早的一个 const oldest = this.playingSounds.shift(); if (oldest) { oldest.audio.stop(); // SoundNodePool.pushPoxy(oldest.audio); // 从同key列表中移除 if (this.keyFrameRate.has(oldest.key)) { const sameKeySounds = this.keyFrameRate.get(oldest.key); const index = sameKeySounds.findIndex(item => item.audio === oldest.audio); if (index !== -1) { sameKeySounds.splice(index, 1); } } } } const audioEx = this.TryGetPoxy(); const timestamp = Date.now(); // 添加到同key列表 this.keyFrameRate.get(Soundkey).push({audio: audioEx, timestamp}); // 添加到总播放列表 this.playingSounds.push({key: Soundkey, audio: audioEx, timestamp}); this.LoadSound(Soundkey, value.url, value.bundle, callback, audioEx); } /**播放音效 */ private LoadSound(key:string, url: string, bundle: EBundleName | string, callback:Function, audioEx: AudioEx) { let sound = this.getSoundUrl(EResUrls.Sound(url), bundle); if (sound) { this.playSoundClip(key, sound, callback, audioEx); } else { ResLoad.Ins.AudioClip(EResUrls.Sound(url),bundle, (err, res) => { if (err) { ConsoleEx.Warn("资源加载错误", EResUrls.Sound(url),bundle); // 从列表中移除并回收 this.removeAndRecycle(key, audioEx); return; } this.playSoundClip(key, res, callback, audioEx); }); } } private removeAndRecycle(key: string, audioEx: AudioEx, recycle: boolean = true) { // 从同key列表中移除 if (this.keyFrameRate.has(key)) { const sameKeySounds = this.keyFrameRate.get(key); const index = sameKeySounds.findIndex(item => item.audio === audioEx); if (index !== -1) { sameKeySounds.splice(index, 1); } } // 从总播放列表中移除 const index = this.playingSounds.findIndex(item => item.audio === audioEx); if (index !== -1) { this.playingSounds.splice(index, 1); } // 回收到对象池 recycle && SoundNodePool.pushPoxy(audioEx); } private playSoundClip(key:string, audio: AudioClip, callback:Function, audioEx: AudioEx) { audioEx.setVolume(this.audioSetting.effectVolume); audioEx.playOne(audio, () => { callback && callback(); // 播放完毕后从列表中移除并回收 this.removeAndRecycle(key, audioEx, false); }); } private TryGetPoxy():AudioEx{ let temp: AudioEx = null; if (SoundNodePool.soundNodePool.length > 0) { temp = SoundNodePool.soundNodePool.pop(); } else { temp = new Node("AudioEx").addComponent(AudioEx); temp.init(true, this.node); } return temp; } //#region 背景音乐处理 private playBgmClip(audio: AudioClip) { if(!this.audioSetting.music){return;} this._audioBgm.playloop(audio); } /**播放背景音效 */ private _playBgm(url: string, bundle: string|EBundleName) { let bgm = this.getSoundUrl(EResUrls.Sound(url), bundle); if (bgm) { this.playBgmClip(bgm); } else { ResLoad.Ins.AudioClip(EResUrls.Sound(url),bundle, (err, res) => { if (err) { ConsoleEx.Warn("资源加载错误", EResUrls.Sound(url)) return; } this.playBgmClip(res); }); } } playBgm(value:SoundType){ if(!value){ConsoleEx.Error("播放出错!"); return;} this._MusicRes = value; this._playBgm(value.url,value.bundle); } rePlayBgm() { ConsoleEx.Log("开始重复播放背景音效",this._MusicRes) this.playBgm(this._MusicRes); } //#endregion PlayWordAudio(_ESounds: SoundType, Pos: Vec3, callback?: Function) { this.playEffect(_ESounds, callback, Pos); } protected update(dt: number): void { // this.keyFrameRate.length = 0; } } if (PREVIEW) { window["AudioUtils"] = AudioUtils; }