86°

小游戏开发之资源管理(跨引擎)

前言

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

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

  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) } }

结语

欢迎扫码关注公众号《微笑游戏》,浏览更多内容。
微信图片_20190904220029.jpg

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

本文转载自博客园,原文链接:https://www.cnblogs.com/cgw0827/p/13052621.html

全部评论: 0

    我有话说: