摘要
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~~