cocos 热更新最佳实现

        最近一段时间在整理热更新,顺便做一些总结。creator没有自带热更新,有带示例,但都没有很好的解决问题。

        1.引擎热更新部分没有考虑远程包的热更新,不想把所有包当成正常包来更新下载,这样一开始要检查特别多的文件,特别是项目很大的时候。希望是游戏一进入更新检查主包,需要进入子游戏(设置为远程包)部分,游戏再检查下载远程包更新。

       2.引擎代码热更完之后都得重新启动(虽然是热重启),首次进入检查更新时重启还可以接收,但当游戏进入一半更新子游戏时还重启就不够好。

      3.自带远程bundle版本更新,Asset Bundle 在更新上延续了 Creator 的 MD5 方案:         assetManager.loadBundle(远程url+bundleName, {version: 'fbc07'}, function (err, bundle) {

         if (err) return console.error(err); console.log('load bundle successfully.');

        });

        这种方式更新,是更据远程bundle下config.json下读取文件名,需要时下载远程url加文件名下载文件后,缓存在 writablePath/files/gamecache/下,然后所有文件重新以时间命名,并添加一个url与新文件之间的映射关系文件gamecahe.txt。所以,更新只能用md5方式,每次以新文件方式更新,就产生许多的旧文件,然后多一层映射关系文件gamecahe.txt,同时也不会删除旧文件映射关系只会越来越大,也降低了性能。

        loadBundle 首次只加载config.json和index.js,并会判断index.js是否执行过,未执行就会执行,所有md5方式会重新执行,而非md5不执行(因为引擎从来都是考虑代码更新后必须重启的方式)。

        因为上面3个问题,所以想自己实现一个更完美的热更新方案:首次进入加载首包,如果有更新下载,热重启应用进入游戏。进入游戏后再进入子游戏时,检查并更新下载子游戏(远程包),如果有新的更新,删除旧包数据及代码,直接进入子游戏,并不重启APP。。下面是实现在细节:

   1.添加cocoscreator 插件,主要实现以下功能:

         (1)在build完成后onAfterBuild(),在main.js中注入代码(参考官方示例),从热更新目录里优化查找资源(比包内资源优化),从而能使用最新资源,那如果要更新main.js呢??因为main.js里的代码是固定的不必更新,如果非为更新,只能再c++代码里添加热更新目录而不是main.js中添加了。。

         (2)为主包以及所有的远程包(remote文件夹里所有的文件夹)生成version.manifes和project.manifest,基中version.manifest只有在服务端需要,请求是否有新版本的时候(只要版本号不一样就更新,从而支持版本回滚),并不需要完整的project.manifest,从而节省流量并加快速度。

  2.项目添加热更新管理代码(hotTool单例)

          为什么不像示例一样做成组件?官方示例只有一个地方更新,所以做成组件也只有一个地方用,而我们是要在大厅加载所有子游戏,给每个子游戏添加热更新组件不仅麻烦,效率也不高。更新时只有传入以下参数就可以实现,主包和所有子游戏更新

        

export type HotupdateParam = {

    subgame?: string,      // bundle名 用于子游戏热更时,则一定要传

    onProgress?: (event: native.EventAssetsManager) => void,   // 热更过程

    onComplete: (findNew: boolean) => void,     // 热更完成

    onFail: (event: native.EventAssetsManager) => void,    // 热更失败

    onStart?: () => void  // 发现新版本,则开始热更新

}

3. 热更新代码生效

          主包只要热重启即可,因为刚启动游戏就检查主包,有更新的话再启动也是合理的,

        主包更新完成进入主界面后,检查子游戏更新,子游戏要删除所有包内的脚本和包内cclass,再把bundler所有资源释放干净后,然后就可以直接进入子游戏了。

   /** bundle 脚本表 */

        const bundleScriptTab: Record<string, any> = {};

        /** js 系统 */

        const systemJs = window["System"];

        /** 脚本缓存表 */

        const scriptCacheTab: Record<string, any> = systemJs[Reflect.ownKeys(systemJs).find((v) => typeof v === "symbol")!];

        // 初始化 bundle 脚本表

        Object.keys(scriptCacheTab).forEach((vs) => {

            const current = scriptCacheTab[vs] as { d: any[]; id: string };

            const parent = scriptCacheTab[vs].p;

            if (!parent?.d || current.id !== parent.id) {

                return;

            }

            const names = parent.id.slice((parent.id as string).lastIndexOf("/") + 1);

            bundleScriptTab[names] = parent;

        });

        // 清理脚本缓存

        {

            const bundleRoot = bundleScriptTab[bundleName]?.d[0];

            if (bundleRoot) {

                bundleRoot.d.forEach((v: { id: string }) => {

                    systemJs.delete(v.id);

                });

                systemJs.delete(bundleRoot.id);

                systemJs.delete(bundleRoot.p.id);

            }

        }

        // 清理 ccclass

        {

            const reg = new RegExp(`${bundleName}-`);

            Object.keys(js._nameToClass)

                .filter((vs) => vs.match(reg) !== null)

                .forEach((vs2) => {

                    js.unregisterClass(js.getClassByName(vs2));

                });

        }

        // 清理 bundle 资源

        {

            const bundle = assetManager.getBundle(bundleName)

            if (bundle) {

                bundle.releaseAll();

                assetManager.removeBundle(bundle);

            }

        }

        cclass要 以/区分,这样也支持编辑器启动,其它标志也可以,但是编辑器启动会出问题

        

  • 8
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值