V7投稿 |【杨宗宝】小游戏如何完美的获取Bundle加载进度

写在前边

image

宗宝的独立游戏【穿越奇迹】现已上线:

  • Cocos插件,
  • 微信小游戏,
  • 抖音小游戏,
  • 头条小游戏,
  • TapTap平台。

大家可以搜索游戏名称:穿越奇迹 进行游戏体验。欢迎大家为游戏提出宝贵的意见

商城源码 商城源码已更新,并开启限时折扣

正文

f242fad047e6a360855482ac7f469c8



对于游戏来说,详细的资源加载进度展示是相当重要的。可以让玩家了解游戏加载的进度,避免了玩家长时间等待而感到沮丧或无聊。可以让玩家更好地了解游戏加载的时间和进度,更好地规划自己的时间,知道何时可以开始游戏,而不会中途离开或失去耐心。

1.Bundle介绍

  • 从 v2.4 开始,Creator 正式支持 Asset Bundle 功能。
  • Asset Bundle 作为资源模块化工具,允许开发者按照项目需求将贴图、脚本、场景等资源划分在多个 Asset Bundle 中,然后在游戏运行过程中,按照需求去加载不同的 Asset Bundle,以减少启动时需要加载的资源数量,从而减少首次下载和加载游戏时所需的时间
  • Asset Bundle 可以按需求放置在不同地方,比如可以放在远程服务器、本地、或者小游戏平台的分包中

2.Bundle加载

assetManager.loadBundle('xxx', (err, bundle) => {
    bundle.load('xxx');
});

通过以上代码,我们可以对Bundle进行加载;


需要注意的是此加载非彼加载,什么意思呢?

  • 在通过 API 加载 Asset Bundle 时,引擎并没有加载 Asset Bundle 中的所有资源,而是加载 Asset Bundle 的 资源清单,以及包含的 所有脚本

也就是说,当我们调用了开头的loadBundle加载了Bundle后,如果使用到其中的某个资源,还需要单独对某个资源进行加载

bundle.load(`prefab`, Prefab, function (err, prefab) {
    let newNode = instantiate(prefab);
    director.getScene().addChild(newNode);
});

3.远程Bundle加载

来到了本文的关键部分

  • Bundle加载本身只是对资源清单和脚本进行加载,这个步骤其实并不会有太大的耗时,所以loadBundle接口并为提供加载进度的回调,但是前提是Bundle包是被存放再本地
  • 当Bundle包被存放在远程服务器时。我们在调用loadBundle时,中间多了一步下载,首先将Bundle包下载到了本地,然后再进行加载

由于不存在进度的回调监听,所以在实际调用loadBundle时我们没办法获得Bundle下载的实时进度,只能耐心等待其成功回调的触发。就和前边提到的,这个毫无明确数据显示的加载会导致大量的玩家没有耐心去等待,从而流失。

这种情况最常见的就是有包体大小限制的小游戏平台,大多数的资源都是存放在服务器的,所以加载远程Bundle,并可以完美的显示其加载进度就尤为重要。

在宗宝的游戏中,对微信小游戏和字节小游戏进行了适配,借助平台自身的API和Cocos现有的API来实现一套可以获取远程下载实时进度的方案:

public downLoadBundle(name: string, onProgress: (progress: number, writeByte: number, allByte: number) => void, onComplete: (isSuccess) => void): void {
  .....
}

1.初始化

  • 1.初始化Bundle的远程服务器路径
  • 2.初始化平台本地路径
  • 3.初始化本地记录本版号和当前项目使用到的最新版本号
let remoteUrl: string = `${assetManager.downloader.remoteServerAddress}remote/${name}`;
let savePath: string = wx.env.USER_DATA_PATH + "/gamecaches/" + name; // 微信本地路径
//本地版本
let localVersion: string = sys.localStorage.getItem(name);
//最新对应
let version: string = assetManager.downloader.bundleVers[name];

2.版本匹配

  • 1.进行版本匹配检测,如果相同,则本地已是最新版本,直接调用loadBundle进行加载
  • 2.不匹配,则服务器存在最新版本需要更新
 if (version === localVersion) {
    // 已经是最新的版本了
    console.log(`最新版本  ${name} bunder ${version}`);
    this.loadBundle(savePath, onComplete);
} else {
  ...
}

3.清除旧版本

  • 1.如果存在旧版本Bundle,在更新新版本前,将旧的资源进行删除
wx.getFileSystemManager().rmdirSync(savePath, true);

4.更新Config文件

  • 1.下载config.json文件,下载成功后被存放在临时目录
let configFile: string = `/config.${version ? `${version}.` : ''}json`;
let downloadTask: any = wx.downloadFile({
  url: remoteUrl + configFile,
  success: (res) => {
      if (res.statusCode === 200) {
          if (onComplete) onComplete(res.tempFilePath);
      }
  },
  fail: (res) => {
      console.log(`下载  ${remoteUrl + configFile} 失败, err ${res}`);
      if (onComplete) onComplete(null);
  },
});
  • 2.将临时目录的config.json文件移至本地路劲下
wx.getFileSystemManager().saveFileSync(file, savePath + configFile);
let data = wx.getFileSystemManager().readFileSync(savePath + configFile, "utf8");
configData = JSON.parse(data);

5.更新资源文件

在宗宝的游戏中,所有的远程资源都是在Cocos Creator中对Bundle的进行配置,压缩类型选择的是ZIP,所以只需要下载对应版本的zip资源文件

  • 1.对zip文件进行下载,下载成功后默认存放在临时目录
  • 2.监听下载进度,可以获取整个包体的字节大小,以及已下载的字节大小和进度
let resFile: string = `/res.${resVersion ? `${resVersion}.` : ''}zip`;
console.log(`下载res zip 文件 ${resFile}`);
let downloadTask: any = wx.downloadFile({
  url: remoteUrl + resFile,
  success: (res) => {
      if (res.statusCode === 200) {
          console.log(`下载  ${remoteUrl + resFile} 成功,临时目录:${res.tempFilePath}`);
          if (onComplete) onComplete(res.tempFilePath);
      }
  },
  fail: (res) => {
      console.log(`下载  ${remoteUrl + resFile} 失败, err ${res}`);
      if (onComplete) onComplete(null);
  },
});
downloadTask.onProgressUpdate((res) => {
  if (onProgress) onProgress(res.progress, res.totalBytesWritten, res.totalBytesExpectedToWrite);
});
  • 3.将临时目录下载zip资源移至与config.json相同的本地路径下
  • 4.所有资源下载成功,保存版本号
wx.getFileSystemManager().saveFileSync(file, savePath + resFile);
console.log(`bundle文件 ${name} 已经从 ${file} 移动到 ${savePath + resFile}`);
sys.localStorage.setItem(name, version);

6.加载本地路劲下的Bundle包

assetManager.loadBundle(savePath, (err, bundle) => {
    if (err) {
        console.warn(`杨宗宝:load bundle ${path} task err, err msg:${err}...`);
        onComplete(false);
    } else {
        console.log(`杨宗宝:load bundle ${path} task success...`);
        onComplete(true);
    }
});

总结

以上是宗宝个人的一些观点,以及对于微信小游戏平台显示远程Bundle加载进度的实现方案。同理对于字节小游戏也是相同的逻辑,只需要简单进行调整。希望可以帮助到大家,欢迎大家一起讨论,一起进步。

写在最后

宗宝公众号:穿越的杨宗宝

宗宝微信:Carlos13207

5赞

支持大佬,顶啊

1赞

顶顶顶!大佬牛逼!

1赞

表达下个人看法,加载进度条其实要由引擎层提供 API。像 Laya 一样的加载 API,是比较符合个人预期的。如果要开发者再造轮子,个人认为是设计上的问题。
而实际上需要显示进度的地方不仅仅是下载,还有预加载资源,提前创建一些对象,网络请求等。本质上,用户只关心我们在做什么,而不关心非常精确的进度。
所以简化的方式的一个思路是: 在大任务上精确,在小进度上模糊。解决方案用”假进度条“即可。
比如真实的任务总量为 totoal = 1, 当前进度为 cur = 0,
假进度同时递增 2 个变量。(为了避免初始完成 50% 的情况,可以加入一个 500ms 的时间进度任务)
以上完美解决所有加载进度显示问题。
附上登陆成功后的加载代码:
image
参考我的游戏合集中的进度表现:
小程序码

2赞

加载的进度实现,一般1秒内涨到97,然后慢慢涨等到success后变成100就行了。。。

确实是,bundle首次加载时的下载进度这个确实感觉有官方提供统一的API会比较好。
至于说加载进度的实现方式这个就看需求了,可以精确到下载的字节大小,也可以简单的进行进度显示

哈哈哈,这种也是可以的,但是随着游戏慢慢重度化,随便下个资源就有几十M :rofl:

loadBundle不是就下载了个config吗,这个文件顶多几百b几kb吧。是load资源的时候才下载zip?那个 时候引擎也不给回调么

这里有个问题,调用微信下zip包并且用LocalStorage存版本号的方式,似乎绕过了cacheManager的那套gamecache的机制。建议也同时更新cache manager的那个json。
平时因为都下到local了,可以绕过去也没关系。但是碰到200M缓存满的时候,解压zip将失败并且调用clearCacheLRU,这时候绕过了那个机制的话,清除不了数据的,就会卡死了。
另外其实用zip包都有这个问题,一旦缓存用尽了,解压不出来,清除不了cache还是会卡死。不用zip的时候不存缓存也有原始数据,不会报错。所以后来我都不用zip了,经常有人玩几次就满了。

另外zip包的形式我试过,在bundle稍微大一点的情况下,需要asset的时候会卡一段时间。建议资源比较多的游戏尽量用散文件,配置表用个zip就可以了。

1赞

感谢反馈的这些问题,宗宝的包体比较小,这些情况可能测试的并不是很全面。
看来都是趟过坑的 :joy:

大佬 完整版的这个远程加载包的代码能给一个吗,我按你的一行一行粘贴没发现有问题,又不熟悉,所以解决不了运行错误。image

image

加我微信Carlos13207,私聊

Bundle做成ZIP的方式后 你就算是有了一个字节的变化 你也得重新下载整个包 这个本身不是一个好的设计方式,如果有几个Bundle 都只改动了 很少部分 你都得要重新下载 这种方案就是过度浪费了

看个人理解与项目需求吧。bundle本身就是一个整体,不管是否使用zip,如果要更新都是整体更新。开发者完全可以不使用bundle,自己封装更新模块。

不使用ZIP 也都要全部更新? 有这个文档说明吗 你的包里有一共100个文件 你更新了其中一个文件 其他99个也要被迫更新重新下载? 为什么要重新下载

不会重新下

非常抱歉,说的有问题。纠正一下,首先使用zip和非zip本身是两种不同的加载方式:

非zip模式:

这种模式,他的远程bundle加载其实和本地bundle加载是一个逻辑,每次更新bundle的时候只会下载最新config文件,并不会下载资源文件,也就是后边你所有首次使用到的资源都是一个个的从服务器下载的

  • 优点:首次下载成功后,如果某个资源本身未发生变化,在bundle更新的时候并不会再次下载,使用的是缓存资源
  • 缺点:如果文件数量过度,影响下载速度,并且数量过多在微信小游戏会高概率出现下载异常问题

zip模式

这种模式,bundle更新的时候,除了下载最新config文件以外,对应的res.zip文件也会一并下载到本地,并且进行解压

  • 优点:一定程度减少碎文件数量,对下载速度有提升。同时可避免因为文件数量过多导致的异常
  • 缺点:就是你说的,其实只是改动了一个文件,但是每次更新都会动整个bundle进行下载

所以至于用那种模式,主要就是看项目需求吧,比如我的项目中bundle 分的比较细,对应的地方加载对应的bundle,bundle本省包体并不是很大,所以zip 模式可能更适合

所以 从实际的使用中看 不用zip的方式 更灵活 更节省和更具有随意控制性,
你这个是完全为了一个进度 改成 zip方式, 实际用处不明显 就算是不用 zip ,也能95%的体现出你目前的这个进度效果,具体的方法 这里不多说 办法也不少
就从实际的项目中看 我这里的 和身边同事的 都没有用 zip,当然 我并不知道就这个论坛里 有多少人用 但我猜 不会太多 应该比非 zip的要少很多, 因为我认为 zip的优点 非 zip的也都有 反过来不行 下载速度这种 忽略掉吧 现在都什么网络状况了

楼主的zip模式复现了,想看下完整的非zip的要怎么写,有没有成熟案例贴图或链接