由于 Cocos Creator 3.x 移除了 v2.x
cc.audioEngine
系列的 API,统一使用 AudioSource 控制音频播放。
根据cocos官网给的例子按自己实际使用修改的一个小工具🔧
先看官网给的例子,官方现在推荐使用的是audioSource
, 播放音乐用 play
, 播放音效用 playOneShot
。但是一个audioSource
只能同时操作单个音频,所以当我们有多个音频需要同时播放时就需要做一些“加工”了。
//AudioMgr.ts
import { Node, AudioSource, AudioClip, resources, director } from 'cc';
/**
* @en
* this is a sington class for audio play, can be easily called from anywhere in you project.
* @zh
* 这是一个用于播放音频的单件类,可以很方便地在项目的任何地方调用。
*/
export class AudioMgr {
private static _inst: AudioMgr;
public static get inst(): AudioMgr {
if (this._inst == null) {
this._inst = new AudioMgr();
}
return this._inst;
}
private _audioSource: AudioSource;
constructor() {
//@en create a node as audioMgr
//@zh 创建一个节点作为 audioMgr
let audioMgr = new Node();
audioMgr.name = '__audioMgr__';
//@en add to the scene.
//@zh 添加节点到场景
director.getScene().addChild(audioMgr);
//@en make it as a persistent node, so it won't be destroied when scene change.
//@zh 标记为常驻节点,这样场景切换的时候就不会被销毁了
director.addPersistRootNode(audioMgr);
//@en add AudioSource componrnt to play audios.
//@zh 添加 AudioSource 组件,用于播放音频。
this._audioSource = audioMgr.addComponent(AudioSource);
}
public get audioSource() {
return this._audioSource;
}
/**
* @en
* play short audio, such as strikes,explosions
* @zh
* 播放短音频,比如 打击音效,爆炸音效等
* @param sound clip or url for the audio
* @param volume
*/
playOneShot(sound: AudioClip | string, volume: number = 1.0) {
if (sound instanceof AudioClip) {
this._audioSource.playOneShot(sound, volume);
}
else {
resources.load(sound, (err, clip: AudioClip) => {
if (err) {
console.log(err);
}
else {
this._audioSource.playOneShot(clip, volume);
}
});
}
}
/**
* @en
* play long audio, such as the bg music
* @zh
* 播放长音频,比如 背景音乐
* @param sound clip or url for the sound
* @param volume
*/
play(sound: AudioClip | string, volume: number = 1.0) {
if (sound instanceof AudioClip) {
this._audioSource.stop();
this._audioSource.clip = sound;
this._audioSource.play();
this.audioSource.volume = volume;
}
else {
resources.load(sound, (err, clip: AudioClip) => {
if (err) {
console.log(err);
}
else {
this._audioSource.stop();
this._audioSource.clip = clip;
this._audioSource.play();
this.audioSource.volume = volume;
}
});
}
}
/**
* stop the audio play
*/
stop() {
this._audioSource.stop();
}
/**
* pause the audio play
*/
pause() {
this._audioSource.pause();
}
/**
* resume the audio play
*/
resume(){
this._audioSource.play();
}
}
那么我们需要做哪些改动呢?其实也很简单,就是一个音频对应一个audioSource
,这里我用Map
存储了音效组件AudioSource
。
/**
* 一个音频对应一个audioSource
*/
private effectList: Map<string, AudioSource> = new Map();
接下来在播放音频时,如果是第一次播放那就创建一个节点用来控制该音频,同时添加AudioSource
组件到节点,然后把加载好的音频Clip
绑定到AudioSource
组件上,最后就是往effectList
里set
新创建的音频组件。这样一顿操作,第二次播放时就可以直接复用已经存在的组件了,节省了系统开销。
async playEffect(sound: string, volume: number = 1.0) {
let audioSource: AudioSource | undefined = this.effectList.get(sound);
if (!audioSource) {
const audioClip = await BundleManager.get().loadAudio(sound);
if (!audioClip) return; // 如果加载音频失败,则提前返回
const audioNode = new Node();
audioNode.name = sound;
audioNode.parent = this.audioMgr;
audioSource = audioNode.addComponent(AudioSource);
audioSource.name = sound;
audioSource.clip = audioClip;
audioSource.volume = volume;
this.effectList.set(sound, audioSource);
}
if (audioSource.clip) {
//播放短音效使用playOneShot
audioSource.playOneShot(audioSource.clip);
}
}
那么长音频该怎么处理呢?
我这里以背景音乐举例,首先定义一个属性😂
/**
* 背景音
*/
private bgMusic: AudioSource
很简单,直接看代码
async playMusic(sound: string, loop: boolean = true, volume: number = 1.0) {
if (!this.bgMusic) {
const audioClip = await BundleManager.get().loadAudio(sound);
if (!audioClip) return; // 如果加载音频失败,则提前返回
const audioNode = new Node();
audioNode.name = sound;
audioNode.parent = this.audioMgr;
const audioSource = audioNode.addComponent(AudioSource);
audioSource.name = sound;
audioSource.clip = audioClip;
audioSource.volume = volume;
audioSource.loop = loop;
this.bgMusic = audioSource
}
if (this.bgMusic.clip) {
this.bgMusic.play();
}
}
最后完整代码奉上
import { Node, AudioSource, director } from 'cc';
import BundleManager from '../bundle/BundleManager';
export class AudioManager {
private static _inst: AudioManager;
public static get inst(): AudioManager {
if (this._inst == null) {
this._inst = new AudioManager();
}
return this._inst;
}
/**
* 音频root节点
*/
private audioMgr: Node;
/**
* 一个音频对应一个audioSource
*/
private effectList: Map<string, AudioSource> = new Map();
/**
* 背景音
*/
private bgMusic: AudioSource
constructor() {
this.audioMgr = new Node();
this.audioMgr.name = '__audioMgr__';
director.getScene().addChild(this.audioMgr);
director.addPersistRootNode(this.audioMgr);
}
async playEffect(sound: string, volume: number = 1.0) {
let audioSource: AudioSource | undefined = this.effectList.get(sound);
if (!audioSource) {
const audioClip = await BundleManager.get().loadAudio(sound);
if (!audioClip) return; // 如果加载音频失败,则提前返回
const audioNode = new Node();
audioNode.name = sound;
audioNode.parent = this.audioMgr;
audioSource = audioNode.addComponent(AudioSource);
audioSource.name = sound;
audioSource.clip = audioClip;
audioSource.volume = volume;
this.effectList.set(sound, audioSource);
}
if (audioSource.clip) {
audioSource.playOneShot(audioSource.clip);
}
}
async playMusic(sound: string, loop: boolean = true, volume: number = 1.0) {
if (!this.bgMusic) {
const audioClip = await BundleManager.get().loadAudio(sound);
if (!audioClip) return; // 如果加载音频失败,则提前返回
const audioNode = new Node();
audioNode.name = sound;
audioNode.parent = this.audioMgr;
const audioSource = audioNode.addComponent(AudioSource);
audioSource.name = sound;
audioSource.clip = audioClip;
audioSource.volume = volume;
audioSource.loop = loop;
this.bgMusic = audioSource
}
if (this.bgMusic.clip) {
this.bgMusic.play();
}
}
public stopMusic(): void {
if (!this.bgMusic || !this.bgMusic.playing) return;
this.bgMusic.stop();
}
public stopEffect(sound: string): void {
let audioSource: AudioSource | undefined = this.effectList.get(sound);
if (!audioSource || !audioSource.playing) return;
audioSource.stop();
}
}