Cocos Creator音频播放管理器

由于 Cocos Creator 3.x 移除了 v2.x cc.audioEngine 系列的 API,统一使用 AudioSource 控制音频播放。

根据cocos官网给的例子按自己实际使用修改的一个小工具🔧

先看官网给的例子,官方现在推荐使用的是audioSource, 播放音乐用 play, 播放音效用 playOneShot。但是一个audioSource只能同时操作单个音频,所以当我们有多个音频需要同时播放时就需要做一些“加工”了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
//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

1
2
3
4
/**
* 一个音频对应一个audioSource
*/
private effectList: Map<string, AudioSource> = new Map();

接下来在播放音频时,如果是第一次播放那就创建一个节点用来控制该音频,同时添加AudioSource组件到节点,然后把加载好的音频Clip绑定到AudioSource组件上,最后就是往effectListset新创建的音频组件。这样一顿操作,第二次播放时就可以直接复用已经存在的组件了,节省了系统开销。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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);
}
}

那么长音频该怎么处理呢?

我这里以背景音乐举例,首先定义一个属性😂

1
2
3
4
/**
* 背景音
*/
private bgMusic: AudioSource

很简单,直接看代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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();
}
}

最后完整代码奉上

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
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.play();
// 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();
}

}