zoukankan      html  css  js  c++  java
  • 小游戏开发之资源管理(跨引擎)

    前言

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

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

    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

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

  • 相关阅读:
    下载及爬取网页内容
    对于for循环的理解
    记录安装fiddle出现的问题
    Django
    12种可以参考的思路关于代码能干什么
    “字符文本中字符太多”错误及解决方法
    jQuery参考:jquery中的$(document).ready()与window.onload的区别
    页面定时刷新功能实现
    HTML:关于位置的几个概念
    Lambda表达式
  • 原文地址:https://www.cnblogs.com/cgw0827/p/13052621.html
Copyright © 2011-2022 走看看