摘要
CocosCreator 的音频资源本地加载有两种办法,其一是在脚本中声明并拖入属性面板,其二是利用 cc.loader 做动态加载。如何优雅的做音频资源预加载呢?KUOKUO 通过一个小例子带你学习。
正文
使用版本
明确目标
我们要做一个音频资源加载模块,与场景解耦,通过名称获取音频资源。(预制体、图片资源同理)如下图,我们的目标是优雅的实现这些资源的加载。

单例模式
音频资源加载模块,全局一份即可,自然我们就想到了单例模式。实现单例很简单,我们暴露出一个 getInstance 方法,始终返回一份实例,私有化构造函数,使得类无法被 new 即可!
export class AudioClipManager {
private static instance: AudioClipManager
private constructor () { }
public static getInstance (): AudioClipManager { if (!this.instance) { this.instance = new AudioClipManager() } return this.instance }
}
|
预加载
单例写好了,接下来就是预加载的方法了,cocos 中有一个 cc.loader.loadResDir 的方法能够动态加载一个文件夹下的资源,我们分类好音频资源后正好都在一个文件夹下即可。那我们先声明下音频资源的路径,后期手动修改,或者你再写一个修改路径的方法都可以。
private static audioClipsUrl: string = 'music'
|
然后让我们写一下加载代码:
public preLoadAllAudioClips () { cc.loader.loadResDir(AudioClipManager.audioClipsUrl, cc.AudioClip, (completedCount, totalCount, item) => {
}, (error, audioClips, urls) => {
}) }
|
我们能够获取到加载的一些参数,让我们计算下进度,丰富下代码:
public preLoadAllAudioClips () { cc.loader.loadResDir(AudioClipManager.audioClipsUrl, cc.AudioClip, (completedCount, totalCount, item) => { let progress = (completedCount / totalCount) * 100 cc.log(`缓存音频资源中: ${completedCount}/${totalCount}`) }, (error, audioClips, urls) => { if (error) { cc.error(error) return } cc.log('缓存完毕!') }) }
|
新建个脚本使用一波:
import { AudioClipManager } from "./module/AudioClipManager"
const {ccclass, property} = cc._decorator
@ccclass export default class Login extends cc.Component {
audioClipManager: AudioClipManager
onLoad () { this.audioClipManager = AudioClipManager.getInstance() }
start () { this.audioClipManager.preLoadAllAudioClips() }
}
|

进度回调
我们已经能够正常的使用了,但是在 Login 脚本中怎么知道进度回调呢?简单,写个 callback !
public preLoadAllAudioClips (callback: (progress: number, isCompleted: boolean) => void) { cc.loader.loadResDir(AudioClipManager.audioClipsUrl, cc.AudioClip, (completedCount, totalCount, item) => { let progress = (completedCount / totalCount) * 100 callback(progress, false) cc.log(`缓存音频资源中: ${completedCount}/${totalCount}`) }, (error, audioClips, urls) => { if (error) { cc.error(error) return } callback(100, true) cc.log('缓存完毕!') }) }
|
在加载场景中使用:
this.audioClipManager.preLoadAllAudioClips((progress, isCompleted) => { if (isCompleted) { cc.log('预加载完成,进入游戏') cc.director.loadScene('game') } else { cc.log(`回调进度: ${progress}`) } })
|
效果:

Map 存储
现在我们已经知道资源什么时候被加载完毕了,那么我们如何获取这些资源呢?用 Map!键值为字符串资源名称,方便获取!
private audioClipMap: Map<string, cc.AudioClip> = new Map()
|
在预加载完毕的回调中有资源的数组:
audioClips.forEach(ele => { this.audioClipMap.set(ele.name, ele) })
|
封装一个获取方法:
public getAudioClip (clipName: string): cc.AudioClip { if (!this.audioClipMap.has(clipName)) { cc.warn(`未缓存的音频资源: ${clipName}`) return } return this.audioClipMap.get(clipName) }
|
枚举名称
直接用音频资源的名称也是可以,但是不好维护,我们建个脚本,写个枚举列表。
export enum MusicType { BGM = 'bgm', CLICK = 'click', ACTION = 'action', COIN = 'getcoin', GAME_OVER = 'gameover', }
|
游戏场景中试试效果(demo 里一个 login 场景,一个 game 场景):
import { MusicType } from "./enum" import { AudioClipManager } from "./module/AudioClipManager"
const {ccclass, property} = cc._decorator
@ccclass export default class Game extends cc.Component {
audioClipManager: AudioClipManager
onLoad () { this.audioClipManager = AudioClipManager.getInstance() }
start () { const bgmAudioClip = this.audioClipManager.getAudioClip(MusicType.BGM) cc.audioEngine.playMusic(bgmAudioClip, true) }
}
|
加一点细节
我们丰富一下方法,给出所有代码:
export class AudioClipManager {
private static instance: AudioClipManager
private static audioClipsUrl: string = 'music'
private audioClipMap: Map<string, cc.AudioClip> = new Map()
private constructor () { }
public static getInstance (): AudioClipManager { if (!this.instance) { this.instance = new AudioClipManager() } return this.instance }
public getAudioClip (clipName: string): cc.AudioClip { if (!this.audioClipMap.has(clipName)) { cc.warn(`未缓存的音频资源: ${clipName}`) return } return this.audioClipMap.get(clipName) }
public getAudioClipsByArray (clipNames: string[]): cc.AudioClip[] { const audioClips: cc.AudioClip[] = [] clipNames.forEach(clipName => { if (!this.audioClipMap.has(clipName)) { cc.warn(`未缓存的音频资源: ${clipName}`) return } audioClips.push(this.audioClipMap.get(clipName)) }) return audioClips }
public preLoadAllAudioClips (callback: (progress: number, isCompleted: boolean) => void) { cc.loader.loadResDir(AudioClipManager.audioClipsUrl, cc.AudioClip, (completedCount, totalCount, item) => { let progress = (completedCount / totalCount) * 100 callback(progress, false) cc.log(`缓存音频资源中: ${completedCount}/${totalCount}`) }, (error, audioClips, urls) => { if (error) { cc.error(error) return } audioClips.forEach(ele => { this.audioClipMap.set(ele.name, ele) }) callback(100, true) cc.log('缓存完毕!') }) }
public preloadAudioClipsByArray (clipNames: string[], callback: (progress: number, isCompleted: boolean) => void) { const urls = clipNames.map(clipName => `${AudioClipManager.audioClipsUrl}/${clipName}`) cc.loader.loadResArray(urls, cc.AudioClip, (completedCount, totalCount, item) => { let progress = completedCount / totalCount * 100 cc.log(`缓存音频资源中: ${completedCount}/${totalCount}`) callback(Math.floor(progress), false) }, (error, audioClips: cc.AudioClip[]) => { if (error) { cc.error(error) return } audioClips.forEach(ele => { this.audioClipMap.set(ele.name, ele) }) cc.log('缓存完毕!') callback(100, true) }) }
public releaseAudioClipsByArray (clipNames: string[]) { clipNames.forEach(clipName => { if (!this.audioClipMap.has(clipName)) { cc.warn(`未缓存的音频: ${clipName}`) return } cc.log(`释放了音频资源: ${clipName}`) cc.loader.releaseRes(`${AudioClipManager.audioClipsUrl}/${clipName}`, cc.AudioClip) this.audioClipMap.delete(clipName) }) }
public releaseAllAudioClips () { cc.log('释放了所有音频资源') cc.loader.releaseResDir(AudioClipManager.audioClipsUrl, cc.AudioClip) this.audioClipMap.clear() }
}
|
结语
文章有没有带给你收获呢!O(∩_∩)O~~
微信公众号
