大侠
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

412 lines
13 KiB

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<string, {audio: AudioEx, timestamp: number}[]> = new Map<string, {audio: AudioEx, timestamp: number}[]>();
/**记录所有正在播放的音效 */
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<string,string[]> = 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<string, number> = new Map();
private readonly DEFAULT_COOLDOWN: number = 0; // 默认0秒冷却
private _soundCooldownConfig: Partial<Record<string, number>> = {
};
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;
}