- Creator 版本:3.6.1
- 官方文档说明:
Cocos Creator 3.x 移除了 v2.x 中的
audioEngineAPI,统一使用 AudioSource 组件播放音频。
Cocos Creator 3.6 手册 - AudioSource 组件参考
项目升级,因官方移除了2.x的 audioEngine 接口,原来写的模块不能用了,重新基于3.x 编写了一个新的声音模块,附上代码,如有问题,可以指正出来,积极完善ing
import { assert, assetManager, AudioClip, AudioSource, find, isValid, log, Node, resources, warn } from "cc";
import Tools from "./Tools";
export enum AUDIO_LOCAL_KEY {
MUSIC_VOLUME = "MUSIC_VOLUME",
EFFECT_VOLUME = "EFFECT_VOLUME"
}
export class AudioManager {
private static _instance: AudioManager = null;
public static get Instance(): AudioManager {
this._instance = this._instance || new AudioManager();
return this._instance;
}
//声音资源的缓存
private _cachedAudioClipMap: Record<string, AudioClip> = {};
//音乐节点
private m_musicPlayerNode: Node = null;
//音乐节点组件
private m_musicSource?: AudioSource;
private m_soundPlayingNodes: Array<Node> = [];
private m_soundWaitingNodes: Array<Node> = [];
//音乐和音效的音量大小
private m_musicVolume: number = -1;
private m_soundVolume: number = -1;
//音乐音效是否静音
// private m_musicMute: boolean = false;
// private m_soundMute: boolean = false;
//模块初始化
public init() {
let musicVolume_record = Number(Tools.getLocalRecord(AUDIO_LOCAL_KEY.EFFECT_VOLUME));
let soundVolume_record = Number(Tools.getLocalRecord(AUDIO_LOCAL_KEY.MUSIC_VOLUME));
this.m_musicVolume = (this.m_musicVolume === -1 && musicVolume_record === 0) ? 0.2 : musicVolume_record;
this.m_soundVolume = (this.m_soundVolume === -1 && soundVolume_record === 0) ? 0.7 : soundVolume_record;
}
//***********************************音乐********************************************************** */
//获取音乐节点
private getMusicPlayerNode(): Node {
let musicNode: Node = find('Canvas/MusicPlayer');
if (!musicNode) {
musicNode = new Node('MusicPlayer');
musicNode.addComponent(AudioSource);
musicNode.parent = find('Canvas');
}
return musicNode;
}
/**
* 播放音乐
* @param {String} name 音乐名称
* @param {Number} volumeScale 音乐声音大小
* @param {Boolean} loop 是否循环播放
*/
public playMusic(name: string, volumeScale: number = 1, loop: boolean = true) {
if (this.musicVolume === 0) return;
if (!isValid(this.m_musicPlayerNode)) {
this.m_musicPlayerNode = this.getMusicPlayerNode();
this.m_musicSource = this.m_musicPlayerNode.getComponent(AudioSource);
}
const musicAudioSource = this.m_musicSource!;
if (musicAudioSource.clip && musicAudioSource.clip.name === name) {
musicAudioSource.loop = loop;
if (!musicAudioSource.playing) {
let volume = Math.min(1, Math.max(0, this.musicVolume * volumeScale));
musicAudioSource.volume = volume;
musicAudioSource.play();
}
} else {
let clip: AudioClip = this._cachedAudioClipMap[name];
if (clip) {
musicAudioSource.clip = clip;
musicAudioSource.loop = loop;
let volume = Math.min(1, Math.max(0, this.musicVolume * volumeScale));
musicAudioSource.volume = volume;
musicAudioSource.play();
} else {
let path = 'sounds/' + name;
resources.load(path, AudioClip, (err: Error, audioClip: AudioClip) => {
if (err) {
warn(`load audioClip ${name} failed: `, err.message);
return;
}
this._cachedAudioClipMap[name] = audioClip;
musicAudioSource.clip = audioClip;
musicAudioSource.loop = loop;
let volume = Math.min(1, Math.max(0, this.musicVolume * volumeScale));
musicAudioSource.volume = volume;
musicAudioSource.play();
})
}
}
}
/** 音量 */
get musicVolume(): number { return this.m_musicVolume; }
set musicVolume(value: number) {
this.m_musicVolume = Math.min(1, Math.max(0, value));
this.m_musicSource.volume = value;
}
public stopMusic() {
if (this.m_musicSource.clip) {
this.m_musicSource.stop();
}
}
public pauseMusic(): void {
if (this.m_musicSource.playing) {
this.m_musicSource.pause();
}
}
public resumeMusic(): void {
if (this.m_musicSource.clip) {
this.m_musicSource.play();
}
}
//***********************************音效********************************************************** */
private createSoundPlayerNode(): Node {
let soundNode = new Node('SoundPlayer');
soundNode.addComponent(AudioSource);
soundNode.parent = find('Canvas');
return soundNode;
}
private getAudioNode(): Node {
let node = this.m_soundWaitingNodes.pop();
if (!isValid(node)) {
node = this.createSoundPlayerNode();
}
this.m_soundPlayingNodes.push(node);
return node;
}
private putAudioNode(node: Node) {
const idx = this.m_soundPlayingNodes.indexOf(node);
if (idx !== -1) this.m_soundPlayingNodes.splice(idx, 1);
this.m_soundWaitingNodes.push(node);
}
/**
* 播放音效
* @param {String} name 音效名称
* @param {Number} volumeScale 音效声音缩放值
* @param {Function} callback 回调函数
*/
public playSound(name: string, volumeScale: number = 1, callback: Function = null) {
if (this.soundVolume === 0) return;
let soundNode = this.getAudioNode();
let soundAudioSource = soundNode.getComponent(AudioSource);
let clip = this._cachedAudioClipMap[name];
if (clip) {
soundNode.on(AudioSource.EventType.ENDED, () => {
this.putAudioNode(soundNode);
if (callback) callback();
}, this);
soundAudioSource.loop = false;
let volume = Math.min(1, Math.max(0, this.soundVolume * volumeScale));
soundAudioSource.playOneShot(clip, volume);
} else {
let path = 'sounds/' + name;
resources.load(path, AudioClip, (err: Error, audioClip: AudioClip) => {
if (err) {
warn(`load audioClip ${name} failed: `, err.message);
return;
}
this._cachedAudioClipMap[name] = audioClip;
soundNode.on(AudioSource.EventType.ENDED, () => {
this.putAudioNode(soundNode);
if (callback) callback();
}, this);
soundAudioSource.loop = false;
let volume = Math.min(1, Math.max(0, this.soundVolume * volumeScale));
soundAudioSource.playOneShot(audioClip, volume);
})
}
}
/** 音量 */
get soundVolume(): number { return this.m_soundVolume; }
set soundVolume(value: number) {
this.m_soundVolume = Math.min(1, Math.max(0, value));
}
public clearAllSoundNode() {
this.m_soundPlayingNodes = [];
this.m_soundWaitingNodes = [];
}
}
备注:
1,Tools 为工具脚本,基于字符串进行数据本地化存储,默认返回值为0,所以需要初始化声音大小。
2,声音资源存储在本地,“resources/sounds/” 其下,所以是直接动态加载。
3,clearAllSoundNode 需要在场景主线脚本的onDestroy 生命周期函数中调用一次,避免因场景销毁,创建的音效节点读取问题。
4,模块是分别控制背景音乐和音效的,采用滑动的方式进行控制,所以静音属性未编写控制函数。