优雅的模块化-单例模式-音频资源预加载

摘要

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

微信公众号

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

KUOKUO众享

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值