小话Bundle使用

一、引擎版本

2.4.0正式版

二、概念简述 详细描述

Bundle即为包,将cc.Asset资源如: Texture, AudioClip, Prefab, Scripts...封装在一个容器里。

三、Bundle的优势

1. 模块划分清晰,一包一世界,具有完善性,独立性,复用性
2. 减少首包大小,之前都是构建在settings里面,bundle模式下,拆分到各bundle下的config.json里
3. 热更处理将更加简单

四、简单演示

加载变迁史:cc.loader -> cc.assetManager
接口关系: cc.loader == cc.resources === cc.assetManager.getBundle(‘resources’)

大厅 + 子游戏,或者 多个功能独立热更的项目结构,升级后不建议将全部资源放置在resources目录下,也不建议使用cc.resources接口进行资源加载(resources是一个内置bundle,在main.js中默认加载不需要的可以修改这个地方),更多的应该是使用:

  • 已加载过bundle: cc.getBundle(‘xxx’).load(“资源相对路径”)
  • 未加载过bundle: cc.assetManager.loadBundle(“xxx”, (err, bundle)=> bundle.load(“资源相对路径”))

这里简单演示A精灵的spriteFrame应该如赋值,假设资源路径assets/games/xxx/res/image/bg.jpg

//未加过载bundle,勾选了MD5 Cache 这里传入最新版本号: 
cc.assetManager.loadBundle("xxxOrRemotexxxl", {version: 'abcdef'}, (err, bundle)=> {
    bundle.load("res/image/bg", cc.SpriteFrame, (err, frame) => A.spriteFrame = frame); 
});

//已加载过bundle:  
A.spriteFrame = cc.assetManager.getBundle("xxx->注意这里只能是名称而不是Remotexxxl").get("res/image/bg", cc.SpriteFrame);

五、注意事项

1. 防止脚本重名,类重名

当重名时,引擎底层将抛出异常。为了防止错误或代码覆盖情况,良好的解决方案是添加脚本前缀,如:XXXMain, XXXGameManager, XXXDefine

2. 类重名

    //A包:A.ts
    class MyTest { }
    window.MyTest = MyTest;

    //B包: B.ts 
    class MyTest { }
    //覆盖A的MyTest
    window.MyTest = MyTest;

3. 插件脚本不入包

某脚本在属性面板中应用了导入为插件,那么此脚本将不能并入bundle的index.js中,在bundle的加载操作中,除了下载config.json,同时也下载并执行了index.js脚本,此时index.js脚本的中并没有插件脚本,但引用了插件中的脚本,那么将抛出 xxx undefine 异常

解决方案:

  • 取消导入为插件,你需要清楚知道插件脚本与普通脚本的区别
  • 扩展构建流程,在onBuildFish的回调里将插件脚本并入index.js的头部
'use strict';
var path = require('path');
var fs = require('fs');
const libPath = "assets/games/public/scripts/3rd/lib.js";

function onBeforeBuildFinish (options, callback) {
  let bundle = options.bundles.find(t => t.name =="public");
  if(!bundle) callback();

  let libSrc = path.join(Editor.Project.path,libPath);
  let destScript = path.join(bundle.scriptDest, "index.js");

  let libContent = fs.readFileSync(libSrc);
  let destContent = fs.readFileSync(destScript);
  let newContent = libContent + destContent;
  fs.writeFileSync(destScript, newContent, {encoding: 'utf-8', flag: 'w'});
  callback();
}

module.exports = {
  load () {
    Editor.Builder.on('before-change-files', onBeforeBuildFinish);
  },

  unload () {
    Editor.Builder.removeListener('before-change-files', onBeforeBuildFinish);
  },
};

4. 碰撞分组不入包

解决方案:

  • 在构建流程中,将settings里的groupIndex与collisionMatrix追加到index.js中
  • 在游戏逻辑中,自行添加碰撞信息, 如下所示:
    /**
     * 当初始化
     */
    protected onInit(): void {
        super.onInit();
        //碰撞分组目前引擎是在构建时候记录在settings文件夹下,bundle模式下获取不到,需要自行添加处理
        cc.game["groupList"] = ["default", "aa", "bb"];
        cc.game["collisionMatrix"] = [[false, true],[true, false],[false, false, false]];
    }

5. 项目模块裁剪(分工程模式)

当模块裁剪后,子包却用到了裁剪的模块将不能正常运转逻辑

6. 勾选MD5 Cache后,加载时携带最新版本号(分工程)

因为存在多个工程,各工程构建发布时,引擎将bundleVersion记录在各自工程的主settings里,我们除非将自身工程中settings里的BuildVersion同步到主工程(通用大厅版本的工程)的settings里,才可以不传version,否则需要传版本号,但是这样的操作对热更情况不太友好,我们应该是在服务器上配置bundle对应的版本,使用的时候传入最新的版本号(Hash)。

cc.assetManager.loadBundle("xxx或者fishRemotexxx", {version: 'abcdef'}, (err, bundle)=> {
    bundle.load("res/image/bg", cc.SpriteFrame, (err, frame) => A.spriteFrame = frame); 
});

六、【分工程】还是【同工程分文件夹】研发

  • 同工程下,公共部分能正确、快速同步
  • 同工程下,客户端管理人员能便利的review code,review resources
  • 同工程下,脚本重名引擎会自动添加序号,TS类重名VSCode会报错提示已存在类
  • 分工程下,能确保各bundle之间不会依赖,保证独立性
  • 分工程下,公共部分最好使用git,svn来部署单独目录,便于拉取

七、发布流程(Web-Mobile)

  1. 构建发布
  2. 将构建后的bundle上传至服务器地址
5赞

请问, 我构建后把resounces 的native 传到cdn 为什么没有用, 在微信上, 之前不是只要把res 传到cdn就可以吗,现在这个不懂怎么搞了

https://docs.cocos.com/creator/manual/zh/publish/publish-wechatgame.html
文档写的很清楚,你照着弄就可以了

好贴 支持一下 顺带提出一点自己的小疑问 Bundle 是否可以替代热更新 有新版本号的时候 bundle 是所有都重新下吧

是可以的,但是比较粗暴。毕竟hash并不是我们常用的version,不能用来做版本对比,其本质就是通过拼接后的绝对地址去拉取。个人见解:

优雅的热更新或者叫补丁修复的过程,重点就是版本对比和各文件的md5对比,通过这个对比我们能更灵活处理和给与更好的应用表现,如通过对比,我们可以知道此次修复类型是:
1.app整包下载
2.强制更新
3.可忽略更新

除此,我们应该还能捕获本次热更的大小,给予玩家是否选择热更,还应该考虑将旧版资源删除减少本地缓存大小,断点续更,网络状态检测,等等。这些都是完整热更流程的工作。

4.0之前这么写
settings[“scenes”].push({
“url”: “db://assets/scenes/SceneHall.fire”,
“uuid”: “585gm43GNAvY3WsT75wTd8”
});
请问4.0现在要怎么写呢

2.4.0后场景可以打包进bundle,你直接加载bundle后启动场景就好了。

请问bundle可以用代码添加场景么

MARK清晰明了

mark1111111111

bundle的核心就是config.json和index.js,config.json里配置着本bundle下所有资源相对路径,你想动态添加场景可以做到但要遵循这些配置规则,在没有搞清bundle的结构之前不建议你这样做,了解之后甚至可以改变bundle的管线来做到自定义。场景资源比较特殊,可以嵌入在各包之中,如果没有嵌入,那么会嵌入在官方内置的start-scene, main的bundle里。我想你深入后,你这个问题会迎刃而解,不用纠结着动态添加场景的问题。

因为现在有需求动态添加场景,2.4之前是:
settings[“scenes”].push({
“url”: “db://assets/scenes/SceneHall.fire”,
“uuid”: “585gm43GNAvY3WsT75wTd8”
});
现在升级到2.4.1后,不能用了

mark 以后再看