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.
353 lines
11 KiB
353 lines
11 KiB
|
1 week ago
|
//*********************
|
||
|
|
// create by 流云
|
||
|
|
// time: 2022年2月15日
|
||
|
|
// desc: 音频管理器
|
||
|
|
//*********************
|
||
|
|
|
||
|
|
import { _decorator, Node, AudioSource, AudioClip, game, Game, assetManager, director, Asset, AssetManager, sys } from 'cc';
|
||
|
|
import { BattlePerformance } from '../../../script/ui/BattleGame/BattlePerformance';
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 音频管理器
|
||
|
|
*/
|
||
|
|
export class SoundManager {
|
||
|
|
|
||
|
|
private static _ins: SoundManager = null;
|
||
|
|
public static get Ins() {
|
||
|
|
if (!this._ins) this._ins = new SoundManager();
|
||
|
|
return this._ins;
|
||
|
|
}
|
||
|
|
|
||
|
|
private _node: Node = null;
|
||
|
|
|
||
|
|
private _music: AudioSource = null;
|
||
|
|
private _effect: AudioSource = null;
|
||
|
|
|
||
|
|
private _game_id = "default_game";
|
||
|
|
private _user_name = "default_user";
|
||
|
|
private _click_name = "";
|
||
|
|
private _bundle = "";
|
||
|
|
private _path = "";
|
||
|
|
private _curent_music = "";
|
||
|
|
private _isPause = false;
|
||
|
|
|
||
|
|
MusicVolume = 1;
|
||
|
|
EffectVolume = 1;
|
||
|
|
MuteMusic = false;
|
||
|
|
MuteEffect = false;
|
||
|
|
|
||
|
|
|
||
|
|
private _clip_map: Map<string, AudioRecord> = new Map<string, AudioRecord>();
|
||
|
|
private _clip_loading: Map<string, Promise<AudioClip | null>> = new Map();
|
||
|
|
|
||
|
|
|
||
|
|
init({
|
||
|
|
gameid = "default_game",
|
||
|
|
userName = "default_user",
|
||
|
|
bundle = "resources",
|
||
|
|
path = "sound/",
|
||
|
|
clickEffname = ``,
|
||
|
|
music_volume = 1,
|
||
|
|
effect_volume = 1,
|
||
|
|
mute_music = false,
|
||
|
|
mute_effect = false
|
||
|
|
}) {
|
||
|
|
this._game_id = gameid;
|
||
|
|
this._user_name = userName;
|
||
|
|
this._click_name = clickEffname;
|
||
|
|
this._bundle = bundle;
|
||
|
|
this._path = path;
|
||
|
|
this.MusicVolume = music_volume;
|
||
|
|
this.EffectVolume = effect_volume;
|
||
|
|
this.MuteMusic = mute_music;
|
||
|
|
this.MuteEffect = mute_effect;
|
||
|
|
if (!this._node) {
|
||
|
|
this._node = new Node("soundNode");
|
||
|
|
director.addPersistRootNode(this._node);
|
||
|
|
}
|
||
|
|
|
||
|
|
this._music = this._node.addComponent(AudioSource);
|
||
|
|
this._effect = this._node.addComponent(AudioSource);
|
||
|
|
game.on(Game.EVENT_HIDE, this.onHide, this);
|
||
|
|
game.on(Game.EVENT_SHOW, this.onShow, this);
|
||
|
|
this._node.off(AudioSource.EventType.ENDED, this.onAudioEnded, this);
|
||
|
|
this._node.on(AudioSource.EventType.ENDED, this.onAudioEnded, this);
|
||
|
|
this.getStatus();
|
||
|
|
|
||
|
|
|
||
|
|
}
|
||
|
|
|
||
|
|
clear() {
|
||
|
|
this._clip_map.clear();
|
||
|
|
director.removePersistRootNode(this._node);
|
||
|
|
this._node = null;
|
||
|
|
}
|
||
|
|
|
||
|
|
onHide() {
|
||
|
|
this.pause();
|
||
|
|
}
|
||
|
|
|
||
|
|
onShow() {
|
||
|
|
this.resume();
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 暂停
|
||
|
|
*/
|
||
|
|
pause() {
|
||
|
|
this._isPause = true;
|
||
|
|
if (this._music.playing) this._music.pause();
|
||
|
|
if (this._effect.playing) this._effect.pause();
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 恢复
|
||
|
|
*/
|
||
|
|
resume() {
|
||
|
|
this._isPause = false;
|
||
|
|
this.updateStatus();
|
||
|
|
}
|
||
|
|
|
||
|
|
updateStatus() {
|
||
|
|
this._music.volume = this._isPause || this.MuteMusic ? 0 : this.MusicVolume;
|
||
|
|
this._effect.volume = this._isPause || this.MuteEffect ? 0 : this.EffectVolume;
|
||
|
|
if (this._music) this._music.play();
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 从本地存储获取音频设置状态
|
||
|
|
*/
|
||
|
|
private getStatus() {
|
||
|
|
let temp = sys.localStorage.getItem(`${this._game_id + this._user_name}_music_volume`);
|
||
|
|
if (temp) this.MusicVolume = Number(temp);
|
||
|
|
temp = sys.localStorage.getItem(`${this._game_id + this._user_name}_music_mute`);
|
||
|
|
if (temp) this.MuteMusic = Boolean(temp);
|
||
|
|
temp = sys.localStorage.getItem(`${this._game_id + this._user_name}_effect_volume`);
|
||
|
|
if (temp) this.EffectVolume = Number(temp);
|
||
|
|
temp = sys.localStorage.getItem(`${this._game_id + this._user_name}_effect_mute`);
|
||
|
|
if (temp) this.MuteEffect = Boolean(temp);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 保存音频设置到持久化存储
|
||
|
|
*/
|
||
|
|
saveStatus() {
|
||
|
|
sys.localStorage.setItem(`${this._game_id + this._user_name}_music_volume`, this.MusicVolume + ``);
|
||
|
|
sys.localStorage.setItem(`${this._game_id + this._user_name}_music_mute`, this.MuteMusic + ``);
|
||
|
|
sys.localStorage.setItem(`${this._game_id + this._user_name}_effect_volume`, this.EffectVolume + ``);
|
||
|
|
sys.localStorage.setItem(`${this._game_id + this._user_name}_effect_mute`, this.MuteEffect + ``);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 释放所有允许释放的音频资源
|
||
|
|
*/
|
||
|
|
release() {
|
||
|
|
this._clip_map.forEach((v, k) => {
|
||
|
|
if (v.isRelease && v.clip) {
|
||
|
|
v.clip.decRef();
|
||
|
|
v.clip = null;
|
||
|
|
}
|
||
|
|
})
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 播放音乐
|
||
|
|
* @param param
|
||
|
|
* @returns
|
||
|
|
*/
|
||
|
|
async playMusic({ name, path = ``, bundle = ``, loop = true, isRelease = false, onEnd = null, clip = null }) {
|
||
|
|
//console.log("开始播放音乐:" + name, this._curent_music, this._isPause);
|
||
|
|
if (this._isPause || this.MuteMusic) return;
|
||
|
|
if (this._music && this._music.playing && name == this._curent_music) return;
|
||
|
|
this.stopMusic();
|
||
|
|
this._curent_music = name;
|
||
|
|
if (clip != null)
|
||
|
|
this._music.clip = clip;
|
||
|
|
else {
|
||
|
|
const rec = await this.getClip(bundle, path, name, isRelease);
|
||
|
|
if (!rec?.clip) return;
|
||
|
|
this._music.clip = rec.clip;
|
||
|
|
}
|
||
|
|
this._music.loop = loop;
|
||
|
|
this._music.volume = this.MusicVolume;
|
||
|
|
this._music.play();
|
||
|
|
}
|
||
|
|
|
||
|
|
stopMusic() {
|
||
|
|
this._curent_music = "";
|
||
|
|
if (this._music) {
|
||
|
|
this._music.stop();
|
||
|
|
this._music.clip = null;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
private _PlayingEffects: AudioRecord[] = [];
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 播放音效
|
||
|
|
* @param param
|
||
|
|
*/
|
||
|
|
async playEffect({ name, path = ``, bundle = ``, isRelease = true, onEnd = null, clip = null, finishRelease = false, stopBefore = false }) {
|
||
|
|
if (this._isPause || this.MuteEffect) {
|
||
|
|
let t = 2;
|
||
|
|
if (clip != null) t = clip.getDuration();
|
||
|
|
if (onEnd) setTimeout(() => { onEnd(); }, t * 1000);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
if (!BattlePerformance.shouldPlayWeaponSfx(path, name)) {
|
||
|
|
if (onEnd) setTimeout(() => { onEnd(); }, 0);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
let a: AudioRecord = null;
|
||
|
|
if (clip == null) {
|
||
|
|
a = await this.getClip(bundle, path, name, isRelease);
|
||
|
|
} else {
|
||
|
|
a = new AudioRecord();
|
||
|
|
a.clip = clip;
|
||
|
|
a.bundle = bundle;
|
||
|
|
a.path = path;
|
||
|
|
a.name = name;
|
||
|
|
a.key = `${bundle}_${path}_${name}`;
|
||
|
|
a.isRelease = isRelease;
|
||
|
|
}
|
||
|
|
if (stopBefore) {
|
||
|
|
let arr = this._PlayingEffects.filter(v => v.key == a.key);
|
||
|
|
for (let i = 0; i < arr.length; i++) {
|
||
|
|
arr[i].isPlaying = false;
|
||
|
|
let index = this._PlayingEffects.indexOf(arr[i])
|
||
|
|
if (index > -1 && arr[i] != a) {
|
||
|
|
this._PlayingEffects.splice(index, 1);
|
||
|
|
if (arr[i].clip) {
|
||
|
|
arr[i].clip.decRef();
|
||
|
|
arr[i].clip.destroy();
|
||
|
|
arr[i].clip = null;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!a.clip) {
|
||
|
|
if (onEnd) {
|
||
|
|
setTimeout(() => { onEnd(); }, 2 * 1000);
|
||
|
|
}
|
||
|
|
return a;
|
||
|
|
}
|
||
|
|
|
||
|
|
this._effect.playOneShot(a.clip, this.EffectVolume);
|
||
|
|
let len = a.clip.getDuration();
|
||
|
|
a.isPlaying = true;
|
||
|
|
this._PlayingEffects.push(a);
|
||
|
|
setTimeout(() => {
|
||
|
|
a.isPlaying = false;
|
||
|
|
let index = this._PlayingEffects.indexOf(a);
|
||
|
|
if (index > -1) {
|
||
|
|
this._PlayingEffects.splice(index, 1);
|
||
|
|
}
|
||
|
|
if (finishRelease) {
|
||
|
|
if (a && a.clip) {
|
||
|
|
a.clip.decRef();
|
||
|
|
a.clip = null;
|
||
|
|
}
|
||
|
|
if (this._clip_map.has(a.key))
|
||
|
|
this._clip_map.delete(a.key);
|
||
|
|
}
|
||
|
|
if (onEnd) onEnd();
|
||
|
|
}, len * 1000);
|
||
|
|
return a;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 播放点击音效
|
||
|
|
*/
|
||
|
|
playClick() {
|
||
|
|
if (this._click_name != "")
|
||
|
|
this.playEffect({ name: this._click_name });
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 当音效播放结束
|
||
|
|
* @param e
|
||
|
|
*/
|
||
|
|
onAudioEnded(e) {
|
||
|
|
if (e["onSoundEnd"]) {
|
||
|
|
e.onSoundEnd();
|
||
|
|
e.onSoundEnd = null;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
private async getClip(bundle, path, audioName, isRelease) {
|
||
|
|
let b = bundle == `` ? this._bundle : bundle;
|
||
|
|
let p = path == `` ? this._path : path;
|
||
|
|
let k = `${b}_${p}_${audioName}`;
|
||
|
|
let a: AudioRecord = this._clip_map.get(k);
|
||
|
|
if (!a) {
|
||
|
|
a = new AudioRecord();
|
||
|
|
this._clip_map.set(k, a);
|
||
|
|
}
|
||
|
|
a.bundle = b;
|
||
|
|
a.path = p;
|
||
|
|
a.name = audioName;
|
||
|
|
a.key = k;
|
||
|
|
a.isRelease = isRelease;
|
||
|
|
if (!a.clip) {
|
||
|
|
let loading = this._clip_loading.get(k);
|
||
|
|
if (!loading) {
|
||
|
|
loading = this.loadClip(b, p + audioName);
|
||
|
|
this._clip_loading.set(k, loading);
|
||
|
|
loading.finally(() => this._clip_loading.delete(k));
|
||
|
|
}
|
||
|
|
a.clip = await loading;
|
||
|
|
if (!a.clip) {
|
||
|
|
console.warn(`[Sound] 音效加载失败: ${k}`);
|
||
|
|
return a;
|
||
|
|
}
|
||
|
|
a.clip.addRef();
|
||
|
|
}
|
||
|
|
return a;
|
||
|
|
}
|
||
|
|
|
||
|
|
private async loadClip(bundleName, assetPath): Promise<AudioClip | null> {
|
||
|
|
let bundle = assetManager.getBundle(bundleName);
|
||
|
|
if (!bundle) bundle = await this.loadBundle(bundleName);
|
||
|
|
if (!bundle) return null;
|
||
|
|
return (await this.loadRes(bundle, assetPath)) as AudioClip | null;
|
||
|
|
}
|
||
|
|
|
||
|
|
private async loadBundle(bundleName) {
|
||
|
|
return new Promise<AssetManager.Bundle>((resolve, reject) => {
|
||
|
|
assetManager.loadBundle(bundleName, (err, bundle) => {
|
||
|
|
if (err) {
|
||
|
|
console.log(`加载Bundle:${bundleName}错误`, err);
|
||
|
|
resolve(null);
|
||
|
|
} else {
|
||
|
|
resolve(bundle);
|
||
|
|
}
|
||
|
|
})
|
||
|
|
})
|
||
|
|
}
|
||
|
|
|
||
|
|
private async loadRes(bundle: AssetManager.Bundle, assetPath) {
|
||
|
|
return new Promise<Asset>((resolve, reject) => {
|
||
|
|
bundle.load(assetPath, AudioClip, (err, asset) => {
|
||
|
|
if (err) {
|
||
|
|
console.log(`加载Bundle:${bundle.name},path:${assetPath}资源错误`, err);
|
||
|
|
resolve(null);
|
||
|
|
} else {
|
||
|
|
//asset.addRef();
|
||
|
|
resolve(asset);
|
||
|
|
}
|
||
|
|
})
|
||
|
|
})
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
class AudioRecord {
|
||
|
|
bundle: string = ``;
|
||
|
|
path: string = ``;
|
||
|
|
name: string = ``;
|
||
|
|
key: string = ``;
|
||
|
|
clip: AudioClip = null;
|
||
|
|
isRelease: boolean = true;
|
||
|
|
isPlaying: boolean = false;
|
||
|
|
}
|