摘要
CocosCreator 的音频资源本地加载有两种办法,其一是在脚本中声明并拖入属性面板,其二是利用 cc.loader 做动态加载。如何优雅的做音频资源预加载呢?KUOKUO 通过一个小例子带你学习。
正文
使用版本
CocosCreator 版本 2.2.2
明确目标
我们要做一个音频资源加载模块,与场景解耦,通过名称获取音频资源。(预制体、图片资源同理)如下图,我们的目标是优雅的实现这些资源的加载。
单例模式
音频资源加载模块,全局一份即可,自然我们就想到了单例模式。实现单例很简单,我们暴露出一个 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 的方法能够动态加载一个文件夹下的资源,我们分类好音频资源后正好都在一个文件夹下即可。那我们先声明下音频资源的路径,后期手动修改,或者你再写一个修改路径的方法都可以。
/** resources 下音乐文件夹目录 */
private static audioClipsUrl: string = 'music'
然后让我们写一下加载代码:
/** 缓存所有音频资源 */
public preLoadAllAudioClips () {
/** 加载代码,参数为 url,资源类型,进度回调,完成回调 */
cc.loader.loadResDir(AudioClipManager.audioClipsUrl, cc.AudioClip, (completedCount, totalCount, item) => {
}, (error, audioClips, urls) => {
})
}
我们能够获取到加载的一些参数,让我们计算下进度,丰富下代码:
/** 缓存所有音频资源 */
public preLoadAllAudioClips () {
/** 加载代码,参数为 url,资源类型,进度回调,完成回调 */
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) {
/** 加载代码,参数为 url,资源类型,进度回调,完成回调 */
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!键值为字符串资源名称,方便获取!
/** 存放音频资源的 Map */
private audioClipMap: Map<string, cc.AudioClip> = new Map()
在预加载完毕的回调中有资源的数组:
// 获取完毕后装入 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
/** resources 下音乐文件夹目录 */
private static audioClipsUrl: string = 'music'
/** 存放音频资源的 Map */
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) {
/** 加载代码,参数为 url,资源类型,进度回调,完成回调 */
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
}
// 获取完毕后装入 Map
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
}
// 将预加载的所有音频存入map
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~~