CocosCreator的音频管理以及如何打断音效、如何切换BGM
音频管理
Cocos Creator 3.x 使用 AudioSource 控制音频的播放。AudioSource 是组件,可以添加到场景中,由 编辑器 设置,也可以在 脚本 中进行调用。
这里就演示如何在脚本中创建音频管理模块。
- 创建一个空项目
- 创建音频管理脚本AudioMgr
- 把AudioMgr设为单例模式提供全局访问接口
- 在构造函数中创建一个空节点,并挂载AudioSource组件,把该节点设置为常驻节点
- 提供播放背景音乐的接口并实现背景音乐的播放
- 提供播放音效的接口并实现音效的播放
- 提供一个设置开关的接口并实现音频的打开和关闭
- 提供音频暂停的接口并实现音频的暂停
- 提供音频恢复的接口并实现音频的恢复
- 音频管理脚本
import { _decorator, AudioClip, AudioSource, Component, director, Node, resources } from 'cc';
const { ccclass, property } = _decorator;
@ccclass('AudioMgr')
export class AudioMgr {
//设计成单例
private static _instance: AudioMgr;
public static get Instance(): AudioMgr {
if (!this._instance) {
this._instance = new AudioMgr();
}
return this._instance;
}
//AudioSource组件用于控制音频播放
private _audioSource: AudioSource;
private _volume: number = 0.8;//默认音量(部分web平台(ios)限制并不会生效)
private _sound_on: boolean = true;//音效开关
constructor() {
//创建一个空节点
let audio_node = new Node();
audio_node.name = '__audio_node__';
//把创建的节点添加到场景中
director.getScene().addChild(audio_node);
//设置为常驻节点
director.addPersistRootNode(audio_node);
//添加AudioSource组件
this._audioSource = audio_node.addComponent(AudioSource);
//把音频设置为非加载完自动播放(部分web平台不允许未交互就有音频播放)
this._audioSource.playOnAwake = false;
}
public get AudioSource(): AudioSource {
return this._audioSource;
}
/**
* 播放背景音乐
* @param sound 音频剪辑或者resources的音频路径
* @param volume
*/
playBgm(clip: AudioClip | string, volume: number = 1.0) {
if (!this._sound_on) {
this._volume = 0;
} else {
this._volume = volume;
}
let sound = clip;
if (!sound) {
console.error("背景音乐为空");
return;
}
if (sound instanceof AudioClip) {
this._audioSource.stop();
this._audioSource.clip = sound; //考虑到切换背景音乐时,先停止后播放,防止出现播放叠加或不播放的问题
this._audioSource.play();
this._audioSource.loop = true;
this._audioSource.volume = this._volume;
} else {
//(实际上不放在这里,一般来说会在ResMgr中预先加载)
resources.load(sound, (err, audio: AudioClip) => {
if (err) {
console.error("背景音乐加载失败", sound);
} else {
this._audioSource.stop();
this._audioSource.clip = audio;
this._audioSource.play();
this._audioSource.loop = true;
this._audioSource.volume = this._volume;
}
});
}
};
/**
* 播放音效1 预加载方式
* @param soundtype 音频类型(定义)
* @param volume
*/
playEffect(sound_clip: AudioClip | string, volume: number = 1.0) {
if (!this._sound_on) return;
let sound = sound_clip;
if (!sound) {
console.error("音效为空");
return;
}
//判断是不是音频剪辑
if (sound instanceof AudioClip) {
this._audioSource.playOneShot(sound, this._volume);
} else {
//动态加载(实际上不放在这里,一般来说会在ResMgr中预先加载)
resources.load(sound, (err, audio: AudioClip) => {
if (err) {
console.error("音效加载失败", sound);
} else {
this._audioSource.playOneShot(audio, this._volume);
}
});
}
}
/**
* 客户端音效开关
* @param soundOpen
*/
setMenu(soundOpen: boolean) {
this._sound_on = soundOpen;
if (!this._sound_on) {
this._audioSource.volume = 0;
this._volume = 0;
} else {
this._audioSource.volume = 1.0;
this._volume = 1.0;
}
}
/**
* 停止背景音乐
*/
stopBgm() {
this._audioSource.stop();
}
/**
* 暂停背景音乐
*/
pauseBgm() {
this._audioSource.pause();
}
/**
* 恢复背景音乐
*/
resumeBgm() {
this._audioSource.play();
}
}
- 测试脚本
import { _decorator, AudioClip, Button, Component, EventTouch, Label, Node, Sprite, SpriteFrame } from 'cc';
import { AudioMgr } from './AudioMgr';
const { ccclass, property } = _decorator;
@ccclass('AudioCtrl')
export class AudioCtrl extends Component {
@property({ type: AudioClip, displayName: "背景音乐1" })
bgm1: AudioClip = null;
@property({ type: AudioClip, displayName: "背景音乐2" })
bgm2: AudioClip = null;
@property({ type: AudioClip, displayName: "音效" })
se: AudioClip = null;
@property({ type: SpriteFrame, displayName: "音效开" })
soundOn: SpriteFrame = null;
@property({ type: SpriteFrame, displayName: "音效关" })
soundOff: SpriteFrame = null;
@property(Node)
firstInactiveNode: Node = null;
_soundOn: boolean = true;
_bgmIndex: number = 1;
start() {
this.firstInactiveNode.once(Node.EventType.TOUCH_START, this.onFirstInactive, this);
}
onFirstInactive(event: EventTouch) {
event.preventSwallow = true;//事件向下传递
this.firstInactiveNode.active = false;
AudioMgr.Instance.playBgm(this.bgm1);
}
/**
* 播放/停止背景音乐
*/
onBgm1(btn: Button) {
let btn_node = btn.target;
if (AudioMgr.Instance.AudioSource.playing) {
btn_node.getChildByName("Label").getComponent(Label).string = "播放背景音乐";
AudioMgr.Instance.stopBgm();
} else {
AudioMgr.Instance.playBgm(this.bgm1);
btn_node.getChildByName("Label").getComponent(Label).string = "停止背景音乐";
}
}
/**
* 暂停/恢复背景音乐
*/
onBgm2(btn: Button) {
let btn_node = btn.target;
if (AudioMgr.Instance.AudioSource.playing) {
btn_node.getChildByName("Label").getComponent(Label).string = "恢复背景音乐";
AudioMgr.Instance.pauseBgm();
} else {
AudioMgr.Instance.resumeBgm();
btn_node.getChildByName("Label").getComponent(Label).string = "暂停背景音乐";
}
}
/**
* 播放音效
*/
onSe(btn: Button) {
let btn_node = btn.target;
AudioMgr.Instance.playEffect(this.se);
}
/**
* 音频开关设置
*/
onSeSet(btn: Button) {
let btn_node = btn.target;
this._soundOn = !this._soundOn;
if (this._soundOn) {
btn_node.getComponent(Sprite).spriteFrame = this.soundOn;
} else {
btn_node.getComponent(Sprite).spriteFrame = this.soundOff;
};
AudioMgr.Instance.setMenu(this._soundOn);
}
onChangeBgm() {
this._bgmIndex = this._bgmIndex == 1 ? 2 : 1;
AudioMgr.Instance.playBgm(this["bgm" + this._bgmIndex]);
}
update(deltaTime: number) {
}
}
这里需要注意一点,在切换背景音乐时,需要先停止当前的audioSource,再赋值新的clip,再播放,顺序不要搞错了,如果搞错了,音频会不播放或不同的背景音乐重叠播放。
大概像这样:

但是在播放音效时遇到了一个问题,播放短音效时不明显,短时间内播放几个长音效时会重叠,这是因为音效的播放统一使用了官方推荐的playOneShot ,但playOneShot 是一次性播放操作,播放后的音效无法暂停或停止播放,也无法监听播放结束的事件回调。

不仅体现在无法暂停或停止播放,音效按钮的开关也不会影响到正在播放到音效,直到它被播放完毕。
这在实际应用中显得不那么灵活,比如释放技能类的(长/大于1s)音效反馈我们希望它能播放完毕,所以使用playOneShot没问题。但想一些互动语音反馈,我们希望在下一次交互时终端上一次的语音播放,这里如果再去使用playOneShot就会出现上述无法终止的问题。
- 完善音频管理
这里的想法是一些短音效或不需要被打断的音效依然使用用playOneShot播放。
一些需要即时可以打断的音效,沿用上面播放/切换bgm的函数实现,只需要把audioSource的loop设置为false,注意需要另外创建一个audioSource,不要和Bgm使用同一个,否则会把背景音乐也给打断(切换)掉。
实现的函数也另外定义一个比如叫“playEffectCanBreak”。
import { _decorator, AudioClip, AudioSource, Component, director, Node, resources } from 'cc';
const { ccclass, property } = _decorator;
@ccclass('AudioMgr')
export class AudioMgr {
//设计成单例
private static _instance: AudioMgr;
public static get Instance(): AudioMgr {
if (!this._instance) {
this._instance = new AudioMgr();
}
return this._instance;
}
//AudioSource组件用于控制音频播放
private _audioSource: AudioSource;
private _volume: number = 0.8;//默认音量(部分web平台(ios)限制并不会生效)
private _sound_on: boolean = true;//音效开关
//特别:用于播放可以被打断的音效
private _audioSource2: AudioSource;
constructor() {
//创建一个空节点
let audio_node = new Node();
audio_node.name = '__audio_node__';
//把创建的节点添加到场景中
director.getScene().addChild(audio_node);
//设置为常驻节点
director.addPersistRootNode(audio_node);
//添加AudioSource组件
this._audioSource = audio_node.addComponent(AudioSource);
//把音频设置为非加载完自动播放(部分web平台不允许未交互就有音频播放)
this._audioSource.playOnAwake = false;
let audio_node2 = new Node();
audio_node2.name = '__audio_node2__';
director.getScene().addChild(audio_node2);
director.addPersistRootNode(audio_node2);
this._audioSource2 = audio_node2.addComponent(AudioSource);
this._audioSource2.playOnAwake = false;
}
public get AudioSource(): AudioSource {
return this._audioSource;
}
public get AudioSource2(): AudioSource {
return this._audioSource2;
}
/**
* 播放背景音乐
* @param sound 音频剪辑或者resources的音频路径
* @param volume
*/
playBgm(clip: AudioClip | string, volume: number = 1.0) {
if (!this._sound_on) {
this._volume = 0;
} else {
this._volume = volume;
}
let sound = clip;
if (!sound) {
console.error("背景音乐为空");
return;
}
if (sound instanceof AudioClip) {
this._audioSource.stop();
this._audioSource.clip = sound; //考虑到切换背景音乐时,先停止后播放,防止出现播放叠加或不播放的问题
this._audioSource.play();
this._audioSource.loop = true;
this._audioSource.volume = this._volume;
} else {
//(实际上不放在这里,一般来说会在ResMgr中预先加载)
resources.load(sound, (err, audio: AudioClip) => {
if (err) {
console.error("背景音乐加载失败", sound);
} else {
this._audioSource.stop();
this._audioSource.clip = audio;
this._audioSource.play();
this._audioSource.loop = true;
this._audioSource.volume = this._volume;
}
});
}
};
/**
* 播放音效1 预加载方式
* @param soundtype 音频类型(定义)
* @param volume
*/
playEffect(sound_clip: AudioClip | string, volume: number = 1.0) {
if (!this._sound_on) return;
let sound = sound_clip;
if (!sound) {
console.error("音效为空");
return;
}
//判断是不是音频剪辑
if (sound instanceof AudioClip) {
this._audioSource.playOneShot(sound, this._volume);
} else {
//动态加载(实际上不放在这里,一般来说会在ResMgr中预先加载)
resources.load(sound, (err, audio: AudioClip) => {
if (err) {
console.error("音效加载失败", sound);
} else {
this._audioSource.playOneShot(audio, this._volume);
}
});
}
}
/**
* 播放音效2 可以被打断的音效
* @param sound_clip
* @param volume
*/
playEffectCanBreak(sound_clip: AudioClip | string, volume: number = 1.0) {
if (!this._sound_on) {
this._volume = 0;
} else {
this._volume = volume;
}
let sound = sound_clip;
if (!sound) {
console.error("音效为空");
return;
}
if (sound instanceof AudioClip) {
this._audioSource2.stop();
this._audioSource2.clip = sound;
this._audioSource2.play();
this._audioSource2.loop = false;
this._audioSource2.volume = this._volume;
} else {
resources.load(sound, (err, audio: AudioClip) => {
if (err) {
console.error("音效加载失败", sound);
} else {
this._audioSource2.stop();
this._audioSource2.clip = audio;
this._audioSource2.play();
this._audioSource2.loop = false;
this._audioSource2.volume = this._volume;
}
});
}
}
/**
* 客户端音效开关
* @param soundOpen
*/
setMenu(soundOpen: boolean) {
this._sound_on = soundOpen;
if (!this._sound_on) {
this._audioSource.volume = 0;
this._audioSource2.volume = 0;
this._volume = 0;
} else {
this._audioSource.volume = 1.0;
this._audioSource2.volume = 1.0;
this._volume = 1.0;
}
}
/**
* 停止背景音乐
*/
stopBgm() {
this._audioSource.stop();
}
/**
* 暂停背景音乐
*/
pauseBgm() {
this._audioSource.pause();
}
/**
* 恢复背景音乐
*/
resumeBgm() {
this._audioSource.play();
}
}
浙公网安备 33010602011771号