我理解的 cocosCreator asset Bundle 版本2.4.4(跨域资源服务器+大厅子游戏模式+热更新+相关详细功能+相当易懂的介绍+demo)

cocosCreator 推出了2.4.x版本后,使得开发游戏/教育app更加得心应手,个人觉得这有一大部分得益于 cocoscreator2.4.x 正式支持了Asset Bundle 功能。它几乎贯穿整个开发流程和构建发布项目当中。于是准备 记录一下 个人对于 asset Bundle的理解,后面会写点内存管理,优化的小东西

本文有测试asset Bundle 的demo(文末),和搭建本地资源服务器的方法(支持跨域)入口

操作系统:windows 10

cocosCreator版本:2.4.4(一般情况下2.4.x的都可以)

本文参考官方文档:文档入口

如果图片挂了,可以去我公众号 亮亮同学TT 查看本篇文章

Asset Bundle 综述:

打开 百度翻译输入 asset Bundle 会直接翻译成 资产包。哈啊哈,翻译的有些生硬。我习惯叫他资源包。然而实际上它是一个资源模块化工具 直白的说,他可以把最终的项目分割成好多部分,你需要哪些部分,就加载并使用哪些部分。换句话说就是 他可以把好多资源分成很多可以直接使用的资源包,你需要哪个包就加载使用哪个包,这个包是完整的有逻辑的(如果包含有场景或可以加载到已有场景上的资源,放到assets下面加载后是可以运行起来的)。

作为一个资源模块化工具大致有以下3个特性:

1,Asset Bundle 是通过逻辑划分对资源进行模块化,是完整的、有逻辑的,即场景,图片资源,动画资源,脚本资源,等,甚至可以加载后“独立运行”。

2,可以放在本地,远程,随时加载随时应用。

3,跨项目复用。

到这里,应该对assetBundle有了初步印象了。

那么asset Bundle可以做些什么呢?

1,加载优化:

因为资源包是几乎可以包含一个完整项目所需要的任何资源的,又是可以随时加载随时使用的,我们可以在需要的时候再去加载它,减少启动时需要加载的资源数量,从而减少首次下载和加载游戏时所需的时间。

2,包体优化:

资源包是可以放在本地,远程,随时加载随时应用,所以,可以把资源包放在远程资源服务器,当需要加载时 从远程加载资源包到项目中使用,从而减小包体大小。setting.js? 打包后的项目完全是基于 Asset Bundle 的,setting.js 不再存储跟资源相关的任何配置信息,所有的配置信息都会存储在每个 Asset Bundle 的 config.json 中(构建后)。每一个 config.json 只存储各自 Asset Bundle 中的资源信息,也就减小了首包的包体。可以简单地理解为所有的 config.json 加起来等于之前的 settings.js

3,更友好的多人协作模式和 大厅+子游戏模式

因为资源包是几乎可以包含一个完整项目所需要的任何资源的,又是可以随时加载随时使用的,并且可以跨项目复用,这标志着,一个团队/n个团队,都可以在相同环境下自己的项目中开发不同的独立的功能 几乎不需要考虑别人干的是啥,设置成资源包(需要先构建)然后拼合成一个大的项目。同时 这也将会是大厅+子游戏的一个模型。比如 斗地主就是一个例子 ,进入游戏后会到一个主界面,这个主界面就是大厅,在主界面上有很多不同类型的斗地主玩法,如癞子玩法,传统玩法,等等。再比如:教育行业的 叫叫绘本,伴鱼绘本,洪恩学堂等等,进入app后的主界面就是大厅,我们可以看见有不同种类的绘本故事,点进去会加载/下载当前的绘本/故事。

4,热更新(app版本)

cocosCreator提供了比较完善的热更新机制(web项目利用md5区分资源,所以用不到),但是随着项目的需求,这种热更新给项目带来的帮助 越来越微弱,甚至需要自己写很多的逻辑去实现需求,说白了是“不是现成的我要的东西”,然而 asset Bundle 的 支持,弥补了一大部分不足,比如 一个app中有很多个小游戏,或者其他的模块 ,需求 需要每个小游戏或 模块单独的更新(可以理解为大厅+子游戏模式),而不需要 重新启动游戏,这样的需求 cocosCreator提供的热更新 实现起来就很 吃力,同时,利用asset Bundle 可以远程加载,本项目bundle跨项目bundle实现起来 就很容易。

cocosCreator热更新可以参考我之前写的一篇文章全面讲解cocoscreator热更新(第一期)

到这里应该对asset Bundle有了进一步的了解。是不是已经有点迫切的想更深入的了解 asset Bundle了?嘿嘿

Asset Bundle详细介绍

2.4.x版本后 asset Bundle 几乎贯穿整个项目,打包后的项目完全是基于 Asset Bundle 的。

1,asset bundle 是什么样子的?

先看张图

如图,一个sub文件夹右侧的属性 是这样的说明他就是一个asset Bundle(资源模块化工具,我习惯叫他资源包).

2,项目中的asset bundle.

那么项目中有哪些 默认的 asset bundle呢?
引擎提供了四个内置默认的asset Bundle 分别是
internal,main,resources,start-scene。

internal 用于存放所有内置资源以及其依赖资源,项目的internal -> resources 就是这个bundle。如图

当你构建项目后会在build的下对应构建模式的assets中看到对应的bundle.

main 在项目中是看不到的 只有构建后会在build下对应构建模式的assets中看到对应的bundle。它的配置可以通过 构建发布 面板的 主包压缩类型 和 配置主包为远程包 两项配置。

resources 需要手动创建一个同名的文件夹,它是默认配置,也就是说 ,创建一个resources文件夹系统自动配置他为asset Bundle,目前不支持修改默认配置。

构建后的assets目录如图

start-scene 如果在 构建发布 面板中勾选了 初始场景分包,则首场景将会被构建到 start-scene 中(仅支持小游戏平台),它是不可以手动配置的 如图

构建后main asset Bundle会被放在 remote目录下,其他asset Bundle会放在 对应平台目录的 assets下面 这里是选择微信小游戏构建的如图

接着我们看一下构建后的asset Bundle结构,先看图

这是一个拥有完整数据的 asset Bundle 结构,所有的bundle结构都是如此,包括内置bundle,也就是说每个bundle都可以是一个独立模块存在

在构建时,配置为 Asset Bundle 的文件夹中的所有 代码 和 资源,会进行以下处理:

代码文件夹中的所有代码会根据发布平台合并成一个 index.js 或 game.js 的入口脚本文件,并从主包中剔除。如图

Asset Bundle 支持脚本分包。如果开发者的 Asset Bundle 中包含脚本文件,则所有脚本会被合并为一个 js 文件,并从主包中剔除。在加载 Asset Bundle 时,就会去加载这个 js 文件。

注意:

有些平台不允许加载远程的脚本文件,例如微信小游戏,在这些平台上,Creator 会将 Asset Bundle 的代码拷贝到 src/scripts 目录下,从而保证正常加载。

不同 Asset Bundle 中的脚本建议最好不要互相引用,否则可能会导致在运行时找不到对应脚本。如果需要引用某些类或变量,可以将该类和变量暴露在一个你自己的全局命名空间中,从而实现共享。

资源文件夹中的所有资源以及文件夹外的相关依赖资源都会放到 import 或 native 目录下。

资源配置:所有资源的配置信息包括路径、类型、版本信息都会被合并成一个 config.json 文件。

因为 项目构建后是完全基于asset Bundle的 setting.js 不再存储跟资源相关的任何配置信息,所有的配置信息都会存储在每个 Asset Bundle 的 config.json 中(构建后)。每一个 config.json 只存储各自 Asset Bundle 中的资源信息,也就减小了首包的包体。可以简单地理解为所有的 config.json 加起来等于之前的 settings.js。
如图:

我们会发现 congfig文件和index文件上都有一个hash值

Asset Bundle 在更新上延续了 Creator 的 MD5 方案。当你需要更新远程服务器上的 Asset Bundle 时,请在 构建发布 面板中勾选 MD5 Cache 选项,此时构建出来的 Asset Bundle 中的 config.json 文件名会附带 Hash 值。

在加载 Asset Bundle 时 不需要 额外提供对应的 Hash 值,Creator 会在 settings.js 中查询对应的 Hash 值,并自动做出调整。
但如果你想要将相关版本配置信息存储在服务器上,启动时动态获取版本信息以实现热更新,你也可以手动指定一个版本 Hash 值并传入 loadBundle 中,此时将会以传入的 Hash 值为准,可以手动更改congfig.js和index.j的hash值。只要版本能对应上就好

这样就能绕过缓存中的老版本文件,重新下载最新版本的 Asset Bundle。

继续看下去 才会有更深入的了解嘛!!奥利给!

3, asset bundle配置内容

看着这张图来说

1)当我们点击一个普通文件夹时,文件夹的属性上会显示 配置为Bundle 选项,勾选该选项,并点击右上角应用按钮,这个文件夹就被配置为一个asset Bundle了.

2)配置为asset Bundle后会显示一些属性

(1)Bundle名称这里默认用文件夹得名字,构建后的bundle名字会使用这里填写的名字

(2)Bundle优先级

为什么会有优先级呢?

当文件夹设置为 Asset Bundle 之后,会将文件夹中的资源以及文件夹外的相关依赖资源都合并到同一个 Asset Bundle 中。这样就会出现资源如何包含,如何分配的问题。

情况1:某个资源Asset C 虽然不在 Asset Bundle 文件夹中,但因为同时被两个 Asset Bundle A, B所依赖(引用到,使用到),所以属于两个 Asset Bundle 的情况,如图

情况2:资源在一个 Asset Bundle B 文件夹中,但同时又被 Asset BundleA, Asset BundleB 所依赖(引用到,使用到),如图:

在这两种情况下,资源 c 既属于 Asset Bundle A,也属于 Asset Bundle B。那资源 c 究竟存在于哪一个 Asset Bundle 中呢?此时就需要通过调整 Asset Bundle 的优先级来指定了。

Creator 开放了 10 个可供配置的优先级,编辑器在构建时将会按照优先级 从大到小 的顺序对 Asset Bundle 依次进行构建


当同个资源被 不同优先级 的多个 Asset Bundle 引用时,资源会优先放在优先级高的 Asset Bundle 中,低优先级的 Asset Bundle 只会存储一条记录信息。此时低优先级的 Asset Bundle 会依赖高优先级的 Asset Bundle。
如果你想在低优先级的 Asset Bundle 中加载此共享资源,必须在加载低优先级的 Asset Bundle 之前 先加载高优先级的 Asset Bundle。
*


当同个资源被 相同优先级 的多个 Asset Bundle 引用时,资源会在每个 Asset Bundle 中都复制一份。此时不同的 Asset Bundle 之间没有依赖关系,可按任意顺序加载所以请尽量确保共享的资源(例如 Texture、SpriteFrame、Audio 等)所在的 Asset Bundle 优先级更高,以便让更多低优先级的 Asset Bundle 共享资源,从而最小化包体。


四个内置 Asset Bundle 文件夹的优先级分别为:
Asset Bundle 优先级

internal 11

main 7

resources 8

start-scene 9

当四个内置 Asset Bundle 中有相同的资源时,资源会优先存储在优先级高的 Asset Bundle — internal 文件夹中。建议其他自定义的 Asset Bundle 优先级 不要高于 内置的 Asset Bundle,以便尽可能共享内置 Asset Bundle 中的资源

(3)目标平台和压缩类型

除了web平台 都会有一个配置为远程包选项

若勾选了该项,则 Asset Bundle 在构建后会被放到 remote 文件夹,你需要将整个 remote 文件夹放到远程服务器上。需要时从远程加载
构建 OPPO、vivo、华为等小游戏平台时,若勾选了该项,则不会将 Asset Bundle 打包到 rpk 中。

选择不同的平台 会支持不同的压缩类型。

Creator 目前提供了 默认、无压缩、合并所有 JSON、小游戏分包、Zip 这几种压缩类型用于优化 Asset Bundle。所有 Asset Bundle 默认使用 默认 压缩类型,开发者可重新设置包括内置 Asset Bundle(除了 internal)在内的所有 Asset Bundle 的压缩类型。

压缩类型 功能说明

压缩类型的选择是根据不同需求 来优化项目运行时的加载请求次数。

默认 构建 Asset Bundle 时会将相互依赖的资源的 JSON 文件合并在一起,从而减少运行时的加载请求次数

无压缩 构建 Asset Bundle 时没有任何压缩操作
合并所有 JSON 构建 Asset Bundle 时会将所有资源的 JSON 文件合并为一个,从而最大化减少请求数量,但可能会增加单个资源的加载时间

小游戏分包 在提供了分包功能的小游戏平台,会将 Asset Bundle 设置为对应平台上的分包。

Zip 在部分小游戏平台,构建 Asset Bundle 时会将资源文件压缩成一个 Zip 文件,从而减少运行时的加载请求数量
如果开发者在不同平台对 Asset Bundle 设置了不同的压缩类型,那么在构建时将根据对应平台的设置来构建 Asset Bundle。

如果开发者在旧项目中使用了分包功能,也就是在 属性检查器 中勾选了 配置为子包 选项,那么当项目升级到 v2.4 之后,将自动转变为 Asset Bundle,并将 Asset Bundle 的压缩类型在支持的平台上设置为 小游戏分包。

到这里,基本上在理论上就已经 熟悉了asset Bundle。

Asset Bundle的使用

加载bundle

cocosCreator提供了统一的API cc.assetManager.loadBundle 来加载 Asset Bundle。可以加载本地,也可以加载远程bundle

该API 在引擎CCAssetManager.js 文件 AssetManager类中定义。完整的函数声明,有兴趣的可以找到看下源码,这里只贴一部分。

/**
     * !#en
     * load bundle
     * 
     * !#zh
     * 加载资源包
     * 
     * @method loadBundle
     * @param {string} nameOrUrl - The name or root path of bundle
     * @param {Object} [options] - Some optional paramter, same like downloader.downloadFile
     * @param {string} [options.version] - The version of this bundle, you can check config.json in this bundle
     * @param {Function} [onComplete] - Callback when bundle loaded or failed
     * @param {Error} onComplete.err - The occurred error, null indicetes success
     * @param {Bundle} onComplete.bundle - The loaded bundle
     * 
     * @example
     * loadBundle('http://localhost:8080/test', null, (err, bundle) => console.log(err));
     * 
     * @typescript
     * loadBundle(nameOrUrl: string, options: Record<string, any>, onComplete: (err: Error, bundle: cc.AssetManager.Bundle) => void): void
     * loadBundle(nameOrUrl: string, onComplete: (err: Error, bundle: cc.AssetManager.Bundle) => void): void
     * loadBundle(nameOrUrl: string, options: Record<string, any>): void
     * loadBundle(nameOrUrl: string): void
     */
    loadBundle (nameOrUrl, options, onComplete) {
    
    }

本地加载
本地加载,第一个参数写要加载的bundle名字

cc.assetManager.loadBundle('bundleName', (err, bundle) => {
   //todo
});

例子:

 /**加载本地Bundle */
    private loadLocalBundle(){
        cc.log("\n time"+new Date().toTimeString())
        cc.log(cc.assetManager.assets )
        cc.assetManager.loadBundle("localBundle",(err,bundle)=>{
            if(!err){
                this.txtState.string = "加载bundle完毕"
            }
        })
       
    }

代码调用:按顺序调用,事实上是走的下载文件的逻辑

CCAssetManager.js

loadBundle (nameOrUrl, options, onComplete) {
        var { options, onComplete } = parseParameters(options, undefined, onComplete);

        let bundleName = cc.path.basename(nameOrUrl);

        if (this.bundles.has(bundleName)) {
            return asyncify(onComplete)(null, this.getBundle(bundleName));
        }

        options.preset = options.preset || 'bundle';
        options.ext = 'bundle';
        this.loadRemote(nameOrUrl, options, onComplete);
    },
    


 loadRemote (url, options, onComplete) {
        var { options, onComplete } = parseParameters(options, undefined, onComplete);

        if (this.assets.has(url)) {
            return asyncify(onComplete)(null, this.assets.get(url));
        }

        options.__isNative__ = true;
        options.preset = options.preset || 'remote';
        this.loadAny({url}, options, null, function (err, data) {
            if (err) {
                cc.error(err.message, err.stack);
                onComplete && onComplete(err, null);
            }
            else {
                factory.create(url, data, options.ext || cc.path.extname(url), options, function (err, out) {
                    onComplete && onComplete(err, out);
                });
            }
        });
    },
 loadAny (requests, options, onProgress, onComplete) {
        var { options, onProgress, onComplete } = parseParameters(options, onProgress, onComplete);
        
        options.preset = options.preset || 'default';
        requests = Array.isArray(requests) ? requests.concat() : requests;
        let task = new Task({input: requests, onProgress, onComplete: asyncify(onComplete), options});
        pipeline.async(task);
    },

pipeline.js

 async (task) {
        var pipes = this.pipes;
        if (!(task instanceof Task) || pipes.length === 0) return;
        if (task.output != null) {
            task.input = task.output;
            task.output = null;
        }
        task._isFinish = false;
        this._flow(0, task);
    },

  _flow (index, task) {
        var self = this;
        var pipe = this.pipes[index];
        pipe(task, function (result) {
            if (result) {
                task._isFinish = true;
                task.onComplete && task.onComplete(result);
            }
            else {
                index++;
                if (index < self.pipes.length) {
                    // move output to input
                    task.input = task.output;
                    task.output = null;
                    self._flow(index, task);
                }
                else {
                    task._isFinish = true;
                    task.onComplete && task.onComplete(result, task.output);
                }
            }
        });
    }

…一系列的下载逻辑调用后进入到下载bundle

downloader.js

var downloadBundle = function (nameOrUrl, options, onComplete) {
    let bundleName = cc.path.basename(nameOrUrl);
    let url = nameOrUrl;
    if (!REGEX.test(url)) url = 'assets/' + bundleName;
    var version = options.version || downloader.bundleVers[bundleName];
    var count = 0;
    var config = `${url}/config.${version ? version + '.' : ''}json`;
    let out = null, error = null;
    //下载json文件,
    downloadJson(config, options, function (err, response) {
        if (err) {
            error = err;
        }
        out = response;
        out && (out.base = url + '/');
        count++;
        if (count === 2) {
            onComplete(error, out);
        }
    });

    var js = `${url}/index.${version ? version + '.' : ''}js`;
    //下载js文件
    downloadScript(js, options, function (err) {
        if (err) {
            error = err;
        }
        count++;
        if (count === 2) {
            onComplete(error, out);
        }
    });
};

通过以上代码可以看出 ;当引擎加载bundle的时候 只会下载并加载config和js文件,也就是说只会下载并加载资源配置文件和代码文件

远程加载
远程加载第一个参数需要填写要加载Bundle的url。一般加载其他项目的复用bundle用这种方法(大厅+子游戏模式)

需要大家资源服务器。这是我写的 node.js本地资源服务器的方法 支持跨域 入口

cc.assetManager.loadBundle(url, (err, bundle) => {
   //todo
});

例子:

  /**加载远程Bundle */
     private loadRemoteBundle(){
     //options是可选参数,引擎会根据保留字段 进行对应的操作,这里添加了version和onFileProgress,可用来记录热更资源版本和下载进度
        const options = {
            version:"1314",
            onFileProgress:(loaded:number,total:number)=>{
                cc.log(`已经下载资源数:"${loaded}  总资源数:${total}`);
                this.progressBar.progress = loaded / total;
            }
        }
        cc.assetManager.loadBundle("http://127.0.0.1:8080/sub",options,(err,bundle)=>{
            if(!err){
                this.txtState.string = "加载远程bundle完毕"
                cc.log("time"+new Date().toTimeString())
                cc.log(cc.assetManager.assets )
            }

        })
      
    }

代码调用顺序:同理,稍有不同,可自行尝试。

用模拟器加载的远程bundle会放在 Creator\2.4.4\resources\cocos2d-x\simulator\win32\gamecaches路径下 如图

通过 Asset Bundle 在用户空间中的路径进行加载

这种加载方式 需要 提前下载某个 Asset Bundle 到用户空间(本地存储路径) pathToBundle 目录下。(cocosCreator2.4.3版本开始支持)

// 原生平台
cc.assetManager.loadBundle(jsb.fileUtils.getWritablePath() + '/pathToBundle/bundleName', (err, bundle) => {
    // todo
});

// 微信小游戏平台
cc.assetManager.loadBundle(wx.env.USER_DATA_PATH + '/pathToBundle/bundleName', (err, bundle) => {
    // todo
});

注意:在配置 Asset Bundle 时,若勾选了 配置为远程包,那么构建时请在 构建发布 面板中填写 资源服务器地址。

加载 Asset Bundle 时,引擎不会 Asset Bundle 中的所有资源,而是加载 Asset Bundle 的 资源清单,以及包含的 所有脚本。
当 Asset Bundle 加载完成后,会触发回调并返回错误信息和 cc.AssetManager.Bundle 类的实例,这个实例就是 Asset Bundle API 的主要入口,开发者可以使用它去加载 Asset Bundle 中的各类资源。

2,利用bundle加载资源

在 Asset Bundle 加载完成后,返回了一个 cc.AssetManager.Bundle 类的实例。我们可以通过实例上的 load 方法来加载 Asset Bundle 中的资源,此方法的参数与 cc.resources.load 相同,只需要传入资源相对 Asset Bundle 的路径即可。但需要注意的是,路径的结尾处 不能 包含文件扩展名。

// 加载 Prefab
bundle.load(`prefabPath`, cc.Prefab, function (err, prefab) {
    //todo
});

例子:

 bundle.load("fab",cc.Prefab,(err,fab)=>{
            if(!err){
                let node = cc.instantiate(<cc.Prefab>fab);
                node.parent = this.node;
                node.active = true;
                node.name = "fab";
            }
        })
// 加载 Texture
bundle.load(`imagepath`, cc.Texture2D, function (err, texture) {
    //todo
});
// 加载 spriteframe
bundle.load(`imagepath`, cc.spriteFrame, function (err, frame) {
    //todo
});

例子:

 bundle.load("res/bag",cc.SpriteFrame,(err,frame)=>{
            if(!err){
               this.switchNode.getComponent(cc.Sprite).spriteFrame = <cc.SpriteFrame>frame;
            }
        })

其他的:

// 加载 textures 目录下的所有资源
bundle.loadDir("textures", function (err, assets) {
    // todo
});

// 加载 textures 目录下的所有 Texture 资源
bundle.loadDir("textures", cc.Texture2D, function (err, assets) {
    // todo
});
//加载场景
//loadScene 与 cc.director.loadScene 不同的地方在于 loadScene 只会加载指定 bundle 中的场景,而不会运行场景,你还需要使用 cc.director.runScene 来运行场景。

bundle.loadScene('sceneName', function (err, scene) {
    cc.director.runScene(scene);
});

获取bundle

let bundle = cc.assetManager.getBundle('bundleName');

实例:

       let bundle = cc.assetManager.getBundle("localBundle")
        if(!bundle){
            this.txtState.string = "localBundle未加载";
            return;
        }
        bundle.load("res/bag",cc.SpriteFrame,(err,frame)=>{
            if(!err){
               this.switchNode.getComponent(cc.Sprite).spriteFrame = <cc.SpriteFrame>frame;
            }
        })

bundle也可以预加载资源

除了场景,其他资源也可以进行预加载。预加载的加载参数和正常加载时一样,不过因为预加载只会去下载必要的资源,并不会进行资源的反序列化和初始化工作,所以性能消耗更小,更适合在游戏过程中使用。

Asset Bundle 中提供了 preload 和 preloadDir 接口用于预加载 Asset Bundle 中的资源。具体的使用方式和 cc.assetManager 一致

释放 Asset Bundle 中的资源

在资源加载完成后,所有的资源都会被临时缓存到 cc.assetManager 中,以避免重复加载。当然,缓存中的资源也会占用内存,有些资源如果不再需要用到,可以通过以下三种方式进行释放:
1,

使用常规的 cc.assetManager.releaseAsset 方法进行释放。

 bundle.load(`imagepath`, cc.SpriteFrame, function (err, spriteFrame) {
     cc.assetManager.releaseAsset(spriteFrame);
 });

使用 Asset Bundle 提供的 release 方法,通过传入路径和类型进行释放,只能释放在 Asset Bundle 中的单个资源。参数可以与 Asset Bundle 的 load 方法中使用的参数一致。

bundle.load(`imagepath`, cc.SpriteFrame, function (err, spriteFrame) {
     bundle.release(`imagepath`, cc.SpriteFrame);
 });

使用 Asset Bundle 提供的 releaseAll 方法,此方法与 cc.assetManager.releaseAll 相似,releaseAll 方法会释放所有属于该 bundle 的资源(包括在 Asset Bundle 中的资源以及其外部的相关依赖资源,多用于 做独立资源的bundle 大厅+子游戏模式),请慎重使用。

 bundle.load(`imagepath`, cc.SpriteFrame, function (err, spriteFrame) {
     bundle.releaseAll();
 });

注意:在释放资源时,Creator 会自动处理该资源的依赖资源,开发者不需要对其依赖资源进行管理。,另外开发人员不需要手动为动态加载的资源进行引用计数管理(不需要手动管理内存,addRef/decRef)

bundle.release / bundle.releaseAll引擎源码剖析:

bundle.release

//没有对_ref判断 直接调用了tryRelease,第二个参数是true
release (path, type) {
        releaseManager.tryRelease(this.get(path, type), true);
    },
    
  //尝试释放,force为true直接调用_free方法进行释放
 tryRelease (asset, force) {
        if (!(asset instanceof cc.Asset)) return;
        if (force) {
            releaseManager._free(asset, force);
        }
        else {
            _toDelete.add(asset._uuid, asset);
            if (!eventListener) {
                eventListener = true;
                callInNextTick(freeAssets);
            }
        }
    }
    
    
    

释放逻辑

 _free (asset, force) {
        _toDelete.remove(asset._uuid);

        if (!cc.isValid(asset, true)) return;

        if (!force) {
            if (asset.refCount > 0) {
                if (checkCircularReference(asset) > 0) return; 
            }
        }
    
        // remove from cache
        assets.remove(asset._uuid);
        var depends = dependUtil.getDeps(asset._uuid);
        for (let i = 0, l = depends.length; i < l; i++) {
            var dependAsset = assets.get(depends[i]);
            if (dependAsset) {
                dependAsset.decRef(false);
                releaseManager._free(dependAsset, false);
            }
        }
        asset.destroy();
        dependUtil.remove(asset._uuid);
    },

bundle.releaseAll

//同理

 releaseAll () {
        var self = this;
        assets.forEach(function (asset) {
            let info = self.getAssetInfo(asset._uuid);
            if (info && !info.redirect) {
                releaseManager.tryRelease(asset, true);
            }
        });
    },

释放 移除 Asset Bundle

在加载了 Asset Bundle 之后,此 bundle 会一直存在整个游戏过程中,除非开发者手动移除。当手动移除了某个不需要的 bundle,那么此 bundle 的缓存也会被移除,如果需要再次使用,则必须再重新加载一次。

let bundle = cc.assetManager.getBundle('bundle1Name');
cc.assetManager.removeBundle(bundle);

实例:

 let bundle = cc.assetManager.getBundle("localBundle");
 cc.assetManager.removeBundle(bundle);
 

Asset Bundle 之间的通讯(本文测试demo有,或者在公众号 亮亮同学TT 发送 eventMgr 获得demo)

个人觉得bundle之间的通讯 利用事件机制 比较合适

1,低耦合,不会出现资源依赖等问题

2,易管理,事件类型全部统一管理

推荐全局事件管理类或静态事件类(也可以说是全局)

Asset Bundle的注意事项

1,Asset Bundle ,跨项目复用的要求

1)引擎版本相同。

2)Asset Bundle 中引用到的所有脚本都要放在 Asset bundle 下。

3)Asset Bundle 没有其他外部依赖 bundle,如果有的话,必须加载。

2,Asset Bundle 不支持嵌套

3,移除asset Bundle后不会释放 该bundle加载的资源

4,有些平台不允许加载远程的脚本文件,例如微信小游戏,在这些平台上,Creator 会将 Asset Bundle 的代码拷贝到 src/scripts 目录下,从而保证正常加载

5,
不同 Asset Bundle 中的脚本建议最好不要互相引用,否则可能会导致在运行时找不到对应脚本。如果需要引用某些类或变量,可以将该类和变量暴露在一个你自己的全局命名空间中,从而实现共享

6,bundle之间的互相调用 比较优雅的方式 是用 事件通讯

7,如果开发者的 Asset Bundle 中包含脚本文件,则所有脚本会被合并为一个 js 文件,并从主包中剔除。在加载 Asset Bundle 时,就会去加载这个 js 文件

8,加载asset Bundle不会加载其资源,只会下载 他的配置文件和代码文件

9,Creator 有 4 个 内置 Asset Bundle,包括 resources、internal、main、start-scene,在设置 Bundle 名称 时请不要使用这四个名称

10,小游戏分包 只能放在本地,不能配置为远程包。所以当 压缩类型 设置为 小游戏分包 时,配置为远程包 项不可勾选

11,Zip 压缩类型主要是为了降低网络请求数量,如果放在本地,不用网络请求,则没什么必要。所以要求与 配置为远程包 搭配使用

最后强调一下 assetBundle的特性和能做哪些事情

特性:

1,Asset Bundle 是通过逻辑划分对资源进行模块化,是完整的、有逻辑的,即场景,图片资源,动画资源,脚本资源,等,甚至可以加载后“独立运行”。

2,可以放在本地,远程,随时加载随时应用。

3,跨项目复用。

能做些啥

1,加载优化

2,包体优化

3,更友好的多人协作模式和 大厅+子游戏模式

4,更友好的热更新机制

嗯,到这里 我相信 应该可以比较深入的应用asset bundle啦。

因为时间原因,很多东西没有写出来,如果有更好的意见可以提出来

后面会写一点 内存管理和优化的小东西。

本文测试demo获取: 微信公众号 亮亮同学TT 发送bundle 即可获得,同时公众号内也有此篇文章

20赞

mark 支持亮哥

:ok_hand:哈哈哈。

链接失效了

可以了 :rofl:

你的图都挂了

去公众号 看吧 :sweat_smile: 游戏开发->cocoscreator 第一篇文章

远程bundle更新呢?在加了md5的情况下。已经下载了所有的缓存,现在远程bundl更新了一张图片。怎么保证重新进游戏或者在游戏里里面能更新到新的图片呢?

通过版本号:更新远程资源 需要重新构建作为远程的bundle,再通过 修改hash值 (作为版本号) ,然后替换程资源服务器的bundle,代码里根据 版本号是否相同(可以自己设计更新逻辑)判断是否进行更新。

mark~

bundle包优先级为7的没有加载,优先级为6的使用会有问题,应该怎么解决

设置合理的优先级,或者每个bundle独立资源

bundle资源没有穿插使用,但优先级高的不加载,优先级低的就会加载失败

:sweat:,根据官方的描述,不存在这种情况的,检查一下自己的代码逻辑,不行就问问引擎组。

好的,谢谢

优先级 设置成一样的就可以了 ,我用的2.4.4版本 ,目前是这样的。很尴尬

bundle 并不能实现代码的热更新,只能实现资源的热更新。。

2赞

亮哥。请问可以利用bundle进行子游戏的热更新吗,我发现下载只下载了.json,.js,那么下载下来的子游戏不是没有资源了吗

用的时候加载的

好的,谢谢亮哥,麻烦问下,那子游戏热更岂不是代码要完全与大厅的隔离?