前言

资源管理是内存优化的一部分,对于大型游戏,资源管理不明确,很容易出现内存不足而闪退的情况。
说到资源也就涉及到了资源划分,这部分内容可以看另一篇文章《游戏开发之目录划分》。

资源管理器需要考虑的情况

  1. 加载完成的回调
  2. 加载失败后的尝试
  3. 多个相同请求的处理。
  4. 未加载成功之前已经删除。
  5. 资源的使用情况,记数。
  6. 跨引擎使用。

各个引擎需要提供的辅助类需要实现的接口

/** * 自定义的资源分类,对应各个引擎中相同的资源。 */export enum ResType {    Texture2D,    SpriteFrame,    SpriteAtlas,    Prefab,    Json,    Scene,    Material,    AnimationClip,    Mesh,    Particle2D,//粒子效果    AudioClip,}export type ResCallback = (err: any, res: any) => void/** * 是否使用引用记数  * 对于一些资源很少的小游戏不需要清理资源,所以可以设置为false。 */export let RECORD_RES_COUNT: boolean = true/** * 各个引擎需要提供资源的辅助类需要实现的接口 */export default interface ResInterface {    /**     *      * @param url 加载资源     * @param type      * @param callback      */    loadRes(url: string, type: ResType, callback: ResCallback): void;    /**     * 清理资源     * @param url      */    release(url: string): void;    /**     * 获取资源     * @param url      * @param ResType 自定义的资源类型      */    getRes(url: string, type: ResType): any;    /**     * 获得资源的依赖资源     * @param url      */    getDependsRecursively(url: any): any;}

资源类的封装和记数处理

import ResHelper from "../../engine/ResHelper";import { ResType, RECORD_RES_COUNT } from "./ResInterface";export default class ResItem {    // 全局资源使用计数器。    protected static resCountMap: {} = {};    //尝试加载次数    private loadCount: number = 0;    //以来资源    protected resources: {} = {};    //使用次数    protected useCount: number = 0;    //资源id    private url: string;    //资源类型    private type: ResType;    //加载是否结束    protected loadFinish: boolean = false;    //资源本身    private res: any;    //需要通知的函数    private callbackList: Function[] = []    constructor(url: string, type?: ResType) {        this.url = url;        this.type = type;    }    addCallback(func: Function) {        this.callbackList.push(func)    }    //是否加载完毕    isDone() {        return this.loadFinish;    }    getUrl() {        return this.url;    }    getType() {        return this.type;    }    getRes() {        if (RECORD_RES_COUNT) {                       this.addCount();        }        if (!this.res) {            this.res = ResHelper.instance().getRes(this.url, this.type)        }        return this.res;    }    /**     * 加载完成调用     * @param flag      */    setLoadingFlag(flag: boolean) {        this.loadFinish = flag;        if (flag) {            while (this.callbackList.length > 0) {                let func = this.callbackList.shift();                func(null, this)            }        }    }    /**     * 由于引擎加载机制,加载完成就已经使用,     */    cacheRes(res: any) {        this.res = res;        if (RECORD_RES_COUNT) {            let depands = ResHelper.instance().getDependsRecursively(res)            for (let key of depands) {                this.resources[key] = true;            }            //加载成功后直接加1,以免被其他模块的记载器清理掉。            this.addCount()        }    }    //获得加载次数    getLoadCount() {        return this.loadCount;    }    //更新加载次数    updateLoadCount() {        this.loadCount++;    }    //获得使用次数    getUseCount() {        return this.useCount;    }    releaseAll() {        if (RECORD_RES_COUNT) {            while (this.useCount > 0) {                this.release();            }        }    }    release() {        if (RECORD_RES_COUNT) {            if (this.useCount > 0) {                this.subCount();                if (this.useCount == 0) {                    return true;                } else {                    return false;                }            } else {                return true;            }        }    }    subCount() {        this.useCount --;        let resources: string[] = Object.keys(this.resources);        for (let index = 0; index < resources.length; index++) {            const key = resources[index];            if (ResItem.resCountMap[key] > 0) {                ResItem.resCountMap[key]--;                if (ResItem.resCountMap[key] == 0) {                    ResHelper.instance().release(key)                    delete this.resources[key];                    delete ResItem.resCountMap[key];                }            }        }    }    addCount() {        this.useCount++;        let resources: string[] = Object.keys(this.resources);        for (let index = 0; index < resources.length; index++) {            const key = resources[index];            ResItem.resCountMap[key]++;        }    }    /**     * 删除没有使用的资源     */    static removeUnUsedRes() {        let resources: string[] = Object.keys(this.resCountMap);        for (let index = 0; index < resources.length; index++) {            const key = resources[index];            const count = this.resCountMap[key];            if (count === 1) {                // cc.log("removeUnUsedRes uuid  " + key + "  count  " + ResItem.resCountMap[key])                ResHelper.instance().release(key)                delete this.resCountMap[key];            }        }    }}

资源管理器

import ResItem from "./ResItem";import ResInterface, { ResCallback, ResType } from "./ResInterface";import ResHelper from "../../engine/ResHelper";export default class ResLoader {    private helper: ResInterface = null;    constructor() {        this.helper = ResHelper.instance();    }    protected resCache = {}    /**     * 清理单个资源     * @param url      * @param type      */    releaseRes(url: string, type: ResType) {        let ts = this.getKey(url, type);        let item = this.resCache[ts];        if (item) {            if (item.release()) {                this.resCache[ts] = null;            }        }    }    /**    * 删除所有资源    */    release() {        console.log(' ResLoader release ================== ')        let resources: string[] = Object.keys(this.resCache);        for (let index = 0; index < resources.length; index++) {            const key = resources[index];            const element: ResItem = this.resCache[key];            if (element) {                element.releaseAll();                this.resCache[key] = null;            } else {                // console.warn("ResLoader release url  =  is error  ",key)            }        }    }    private getKey(url: string, type: ResType) {        let key = url + type;        return key;    }    /**     * 同时加载多个资源。     * @param list 需要加载的资源列表     * @param type 需要加载的资源类型,要求所有资源统一类型     * @param func 加载后的回调     * @param loader 资源加载管理器,默认是全局管理器。     */    loadArray(list: Array<string>, type: ResType, func: (err: string, process: number) => void) {        let resCount = 0;        for (let index = 0; index < list.length; index++) {            const element = list[index];            this.loadRes(element, type, (err) => {                // 不论是否都加载成功都返回。                if (err) {                    console.log(err);                    func(err, resCount / list.length);                    return;                }                resCount++;                func(err, resCount / list.length);            });        }    }    getItem(url: string, type: ResType) {        let ts = this.getKey(url, type)        if (this.resCache[ts]) {            return this.resCache[ts]        } else {            let item = new ResItem(url, type);            this.resCache[ts] = item;        }    }    /**     * 加载单个文件     * @param url      * @param type      * @param callback      */    loadRes(url: string, type: ResType, callback: (err: string, res: ResItem) => void) {        let ts = this.getKey(url, type);        let item: ResItem = this.resCache[ts]        // cc.log(" loadRes url ",url,' ts ',ts);        if (item && item.isDone()) {            callback(null, item);            return;        } else {            if (item) {                item.addCallback(callback)                return;            } else {                item = new ResItem(url, type);                this.resCache[ts] = item;            }        }        let func: ResCallback = (err: any, res: any) => {            item.updateLoadCount();            if (err) {                if (item.getLoadCount() <= 3) {                    console.warn(" item.getLoadCount()  =========== ", item.getLoadCount())                    this.helper.loadRes(url, type, func);                } else {                    console.warn(" res load fail url is " + url);                    this.resCache[ts] = null;                    callback(err, null);                }            } else {                item.cacheRes(res);                if (this.resCache[ts]) {                    item.setLoadingFlag(true)                    callback(err, item);                } else {                    //处理加载完之前已经删除的资源                    item.subCount();                }            }        }        this.helper.loadRes(url, type, func);    }    /**     * 获取资源的唯一方式      * @param url      * @param type      */    getRes(url: string, type: ResType) {        let ts = this.getKey(url, type)        let item = this.resCache[ts];        if (item) {            return item.getRes();        } else {            let res = this.helper.getRes(url, type);            if (res) { // 如果其他管理器已经加载了资源,直接使用。                console.log(' 其他加载器已经加载了次资源 ', url)                let item = new ResItem(url, type);                item.cacheRes(item)                this.resCache[ts] = item                return item.getRes();            } else {                console.warn('getRes url ', url, ' ts ', ts)            }        }        return null;    }}

CocosCreator资源辅助类

import ResInterface, { ResType, ResCallback } from "../cfw/res/ResInterface";/** * 各个引擎提供的资源辅助类。需要实现ResInterface接口 */export default class ResHelper implements ResInterface {    private static ins: ResInterface;    static instance() {        if (!this.ins) {            this.ins = new ResHelper()        }        return this.ins;    }    /**      * 加载资源      * @param url       * @param type       * @param callback       */    loadRes(url: string, type: ResType, callback: ResCallback): void {        switch (type) {            case ResType.Prefab:                cc.loader.loadRes(url, cc.Prefab, callback)                break;            case ResType.Texture2D:                cc.loader.loadRes(url, cc.Texture2D, callback)                break;            case ResType.SpriteFrame:                cc.loader.loadRes(url, cc.SpriteFrame, callback)                break;            case ResType.Json:                cc.loader.loadRes(url, cc.JsonAsset, callback)                break;            case ResType.SpriteAtlas:                cc.loader.loadRes(url, cc.SpriteAtlas, callback)                break;            case ResType.Particle2D:                cc.loader.loadRes(url, cc.ParticleAsset, callback)                break;            case ResType.AudioClip:                cc.loader.loadRes(url, cc.AudioClip, callback)                break;        }    }    /**     * 清理资源     * @param url      */    release(url: string): void {        cc.loader.release(url);    }    getRes(url: string, type: ResType): any {        switch (type) {            case ResType.Prefab:                return cc.loader.getRes(url, cc.Prefab);            case ResType.Texture2D:                return cc.loader.getRes(url, cc.Texture2D);            case ResType.SpriteFrame:                return cc.loader.getRes(url, cc.SpriteFrame);            case ResType.Json:                return cc.loader.getRes(url, cc.JsonAsset);            case ResType.SpriteAtlas:                return cc.loader.getRes(url, cc.SpriteAtlas);            case ResType.Particle2D:                return cc.loader.getRes(url, cc.ParticleAsset)            case ResType.AudioClip:                return cc.loader.getRes(url, cc.AudioClip)            default:                console.error(' getRes error url is ', url, ' type is ', type)                return null;        }    }    getDependsRecursively(res: any): any {        return cc.loader.getDependsRecursively(res)    }}

如何使用

  1. 我一般会先定义一个模块类,管理资源
//模块idexport enum ModuleID {    LOGIN,    LOADING,    GAME,    LOBBY,    PUBLIC,    MAX}
import ResLoader from "../../cfw/res/ResLoader";import AudioManager from "../../cfw/audio/AudioManager";export default class Module {    private loader: ResLoader;    protected audio: AudioManager;    protected name: string = ''    constructor(moduleName: string) {        this.name = moduleName;        this.loader = new ResLoader()        this.audio = new AudioManager(moduleName, this.loader)    }    getName() {        return this.name;    }    getLoader() {        return this.loader;    }    getAudio() {        return this.audio;    }}
  1. 然后使用模块管理器管理模块
import { ModuleID } from "./Config";import Module from "./Module";export default class ModuleManager {    private static mgrMap: Module[] = []    private static moduleID: ModuleID = ModuleID.LOADING;    static init(projectName: string) {        for (let index = 0; index < ModuleID.MAX; index++) {            this.mgrMap[index] = new Module(projectName + index);        }    }    static getAudio(id: ModuleID = this.moduleID) {        return this.mgrMap[id].getAudio()    }    static publicAudio() {        return this.mgrMap[ModuleID.PUBLIC].getAudio()    }    static publicLoader() {        return this.mgrMap[ModuleID.PUBLIC].getLoader()    }    static setModuleID(id: ModuleID) {        this.moduleID = id;    }    static getLoader(id: ModuleID = this.moduleID) {        return this.mgrMap[id].getLoader()    }}
  1. 使用

结语

欢迎扫码关注公众号《微笑游戏》,浏览更多内容。

欢迎扫码关注公众号《微笑游戏》,浏览更多内容。