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
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;
|
|
}
|