当前位置: 首页 > article >正文

【cocos creator】热更新

一、介绍

试了官方的热更新功能,总结一下
主要用于安卓包热更新

参考:

Cocos Creator 2.2.2 热更新简易教程
基于cocos creator2.4.x的热更笔记

二、使用软件
1、cocos creator v2.4.10

2、creator热更新插件:热更新manifest生成工具(creator应用商店下载)
cocos提供的脚本也可以用,但这个集成好了,更便捷
在这里插入图片描述

3、静态资源服务器(用的win+tomcat)
tomcat下载安装

4、模拟器

5、Android打包环境

三、配置热更新

1、首先确保项目可以正常构建出apk,进行一次构建
2、商城下载插件,安装好
在这里插入图片描述
3、配置好地址,只支持一个固定的地址,对应服务器存放热更新文件的文件夹
在这里插入图片描述
在这里插入图片描述
4、点击生成热更新包,再点击导入manifest

2.4.10控制台会提示xx已弃用,建议使用xx,按照提示全局搜索替换即可
在这里插入图片描述

5、把下面的脚本放在加载界面

manifest url绑定上一步导入的version,默认在assets下面
在这里插入图片描述

在这里插入图片描述


const { ccclass, property } = cc._decorator;

@ccclass
export default class HotUpdate extends cc.Component {

    @property(cc.Node) progressNode: cc.Node = null;
    @property(cc.Label) alartLbl: cc.Label = null;
    @property(cc.Asset) manifestUrl: cc.Asset = null;  // 以资源的形式,绑定初始的manifest

    private _am: jsb.AssetsManager; // 热更的对象
    private _storagePath: string = '';
    private _hotPath: string = 'HotUpdateSearchPaths'
    private _progress: number = 0;
    private _downFailList: Map<string, number> = new Map();

    protected start(): void {
        this.alartLbl.string = ""
        this._showProgress(0);
        this.alartLbl.string = '';
        this._init();
    }

    private _init() {
        console.log("hotUpdate检测更新", cc.sys.os);
        if (cc.sys.os != cc.sys.OS_ANDROID && cc.sys.os != cc.sys.OS_IOS) {
            // 非原生平台不处理
            this._enterGame();
            return;
        }
        this.alartLbl.string = "检测更新..."
        this._storagePath = ((jsb.fileUtils ? jsb.fileUtils.getWritablePath() : '/') + this._hotPath);
        // 创建一个热更对象,提供一个本地资源路径,用来存放远程下载的资源。一个版本比较方法,用来判断是否需要更新
        this._am = new jsb.AssetsManager('', this._storagePath, this._versionCompareHandle);

        // 设置MD5 校验回调,这里一般用来比对文件是否正常
        // 如果这个方法返回true表示正常, 返回false的会触发文件下载失败,失败时会抛出错误事件ERROR_UPDATING,后面还会提到
        this._am.setVerifyCallback(function (filePath, asset) {
            // When asset is compressed, we don't need to check its md5, because zip file have been deleted.
            let compressed = asset.compressed;
            // Retrieve the correct md5 value.
            let expectedMD5 = asset.md5;
            // asset.path is relative path and path is absolute.
            let relativePath = asset.path;
            // The size of asset file, but this value could be absent.
            let size = asset.size;
            if (compressed) {
                return true;
            }
            else {
                // let expectedMD5 = asset.md5; // 远程project.manifest文件下资源对应的MD5
                // let resMD5: string = calculateMD5(filePath);  // filePath是文件下载到本地的路径,需要自行提供方法来计算文件的MD5
                // return resMD5 == expectedMD5;
                return true;
            }
        });

        if (cc.sys.os === cc.sys.OS_ANDROID) {
            // Some Android device may slow down the download process when concurrent tasks is too much.
            // The value may not be accurate, please do more test and find what's most suitable for your game.
            // this._am.setMaxConcurrentTask(2);
        }

        this._progress = 0;
        //检测更新
        this._checkUpdate();
    }

    /**
     * 版本对比,返回值小于0则需要更新
     * @param versionA 当前游戏内版本  本地
     * @param versionB 需要更新的版本  服务器
     * @returns 
     */
    _versionCompareHandle(versionA: string, versionB: string) {
        console.log("JS Custom Version Compare: version A is " + versionA + ', version B is ' + versionB);
        let vA = versionA.split('.');
        let vB = versionB.split('.');
        for (let i = 0; i < vA.length; ++i) {
            let a = parseInt(vA[i]);
            let b = parseInt(vB[i] || '0');
            if (a === b) {
                continue;
            } else {
                return a - b;
            }
        }
        if (vB.length > vA.length) {
            return -1;
        } else {
            return 0;
        }
    }

    private async _checkUpdate() {
        // 判断当前热更的状态,没有初始化才加载本地Manifest,加载完成后状态会改变
        if (this._am.getState() === jsb.AssetsManager.State.UNINITED) {
            let url = this.manifestUrl.nativeUrl;
            this._am.loadLocalManifest(url); // 加载本地manifest
        }

        if (!this._am.getLocalManifest() || !this._am.getLocalManifest().isLoaded()) {
            console.error('Failed to load local manifest ...');
            return;
        }

        this._am.setEventCallback(this._checkCb.bind(this));
        this._am.checkUpdate();
    }

    private _checkCb(event: jsb.EventAssetsManager) {
        switch (event.getEventCode()) {
            case jsb.EventAssetsManager.ERROR_NO_LOCAL_MANIFEST:
            case jsb.EventAssetsManager.ERROR_DOWNLOAD_MANIFEST:
            case jsb.EventAssetsManager.ERROR_PARSE_MANIFEST:
                // manifest文件相关的错误,这里可以去做一些错误相关的处理
                console.error('加载manifest文件失败', event.getEventCode())
                break;
            case jsb.EventAssetsManager.ALREADY_UP_TO_DATE:
                // 当前版本已经是最新版本
                cc.log('hotUpdate Already up to date with the latest remote version.');
                this._enterGame();
                break;
            case jsb.EventAssetsManager.NEW_VERSION_FOUND:
                // 找到新的版本
                console.log(`hotUpdate New version found, please try to update. (${this._am.getTotalBytes()})`);
                cc.log(`hotUpdate getDownloadedBytes${this._am.getDownloadedBytes()}`);
                cc.log(`hotUpdate getTotalBytes${this._am.getTotalBytes()}`);
                this._am.setEventCallback(null);
                this._downLoadAlart();
                break;
            default:
                return;
        }
    }

    private _downLoadAlart() {
        this.alartLbl.string = "开始更新..."
        // 检测到有新版本则直接更新,也可以改成点击按钮再更新,这一步可以根据自己的需求来处理
        this._hotUpdate();
    }

    // 开始更新
    private _hotUpdate() {
        console.log('hotUpdate开始更新')
        this._am.setEventCallback(this._updateCb.bind(this));
        this._am.update();
    }

    private _updateCb(event: jsb.EventAssetsManager) {
        var needRestart = false, file = null;
        console.log('hotUpdate _updateCb', event.getEventCode())
        this.alartLbl.string = "更新中..."
        switch (event.getEventCode()) {
            case jsb.EventAssetsManager.ERROR_NO_LOCAL_MANIFEST:
            case jsb.EventAssetsManager.ERROR_DOWNLOAD_MANIFEST:
            case jsb.EventAssetsManager.ERROR_PARSE_MANIFEST:
                // manifest文件相关的错误,这里可以去做一些错误相关的处理
                break;
            case jsb.EventAssetsManager.ERROR_UPDATING:
                file = event.getAssetId();
                // 文件更新出错,这里有可能是验证方法没有通过,也有可能是文件下载失败等等
                this._updateFailList(file, false);
                break;
            case jsb.EventAssetsManager.ERROR_DECOMPRESS:
                // 文件解压缩失败
                break;
            case jsb.EventAssetsManager.UPDATE_FAILED:
                cc.error(`Fail to Update -> ${event.getMessage()}`);
                let failed = false;
                this._downFailList.forEach((count) => {
                    if (count > 3) {
                        failed = true;
                    }
                });
                if (failed) {
                    // 超过3次失败,显示下载失败
                    // this._showUpdateFalid();
                } else {
                    cc.log(`HotUpdate failed...Restart Update`);
                    this._am.downloadFailedAssets();  // 重新下载失败的文件
                }
                break;
            case jsb.EventAssetsManager.UPDATE_PROGRESSION:
                // 这里处理正常下载进度显示
                if (event.getPercent()) {
                    let downloadBytes = event.getDownloadedBytes() || 0;
                    let totalBytes = event.getTotalBytes() || 0;
                    this._progress = Math.floor(downloadBytes / totalBytes * 100);
                    if (this._progress <= 0) return;
                    this._showProgress(this._progress);

                    let unit = 1048576;/* 1MB = 1,024 KB = 1,048,576 Bytes */
                    let downloadedMB = (downloadBytes / unit).toFixed(2) + 'MB';
                    let totalMB = (totalBytes / unit).toFixed(2) + 'MB';
                    this.alartLbl.string = `下载资源: ${this._progress}% (${downloadedMB}/${totalMB})`;
                    cc.log('hotUpdate downloadBytes=>', this._progress, downloadedMB, totalMB);
                }
                break;
            case jsb.EventAssetsManager.ALREADY_UP_TO_DATE:
                // 已经是最新版本
                cc.log('hotUpdate Already up to date with the latest remote version...');
                this._enterGame();
                break;
            case jsb.EventAssetsManager.UPDATE_FINISHED:
                cc.log(`hotUpdate Update finished. ${event.getMessage()}`);
                // 更新完成,这里去做重启
                needRestart = true;
                break;
            case jsb.EventAssetsManager.ASSET_UPDATED:
                // 每次资源下载成功会回调到这里,可以根据需求做一些处理
                cc.log('hotUpdate Updated file: ' + event.getAssetId() + ' ' + event.getMessage());
                file = event.getAssetId();
                this._updateFailList(file, true);
                break;
            default:
                break;
        }

        if (needRestart) {
            this._am.setEventCallback(null);
            var newPaths = this._storagePath;
            // 将当前路径写入到本地,持久化数据以便下次游戏启动的时候能拿到
            cc.sys.localStorage.setItem(this._hotPath, JSON.stringify([newPaths]));
            this.alartLbl.string = "更新完成"
            cc.game.restart();
        }
    }

    private _updateFailList(file: string, success: boolean) {
        if (success) {
            this._downFailList.delete(file);
            cc.log('hotUpdate 更新成功', file);
        } else {
            if (this._downFailList.get(file)) {
                let count = this._downFailList.get(file);
                this._downFailList.set(file, count + 1);
                cc.log(`hotUpdate ${file} download fail count ${count + 1}`);
            } else {
                this._downFailList.set(file, 1);
                cc.log(`hotUpdate ${file} download fail count 1`);
            }
        }
    }

    private _showProgress(percent: number) {
       percent > 100 && (percent = 100);
        let progress = cc.winSize.width * percent / 100;
        this.progressNode.width = progress;
  }

    private _enterGame() {
        console.log("hotUpdate 加载游戏");
        //TODO  加载游戏场景
        cc.director.loadScene("game");
    }
}

本来还要在build\jsb-default\main.js开头添加热更新代码,插件集成好了,省略这一步

6、点击编译导出apk,安装在手机上
原始含有热更新功能的包就打包好了

7、修改一部分内容,再次构建(只需要构建)
然后在热更新工具把版本号增加一位,点击生成热更包

将导出的压缩包放到服务器解压
在这里插入图片描述
手机上打开之前安装的包,就会自动更新成最新版本


http://www.kler.cn/a/578331.html

相关文章:

  • at_abc396_d题解
  • 八股打卡(七)
  • idea技巧
  • 系统架构设计师—数据库基础篇—数据库优化技术
  • 【GPT入门】第14课 openai调用高德地图案例实现多轮会话与多轮接口调用
  • 大白话html第十三章HTML学习全文总结
  • 【Hadoop】Hadoop是什么?
  • 【无人机路径规划】基于麻雀搜索算法(SSA)的无人机路径规划(Matlab)
  • 平时作业(偷懒)
  • zotero同步infiniCLOUD报错:webdav服务器不接受您输入的用户名及密码
  • 2025年 Apache SeaTunnel 2月份社区月报速递
  • 配置多区域OSPF,配置OSPF手动汇总,配置OSPF特殊区域
  • WinUI 3 支持的三种窗口 及 受限的窗口透明
  • Linux进程概念(二)
  • JavaWeb后端基础(8)spring原理
  • 炒菜的基本逻辑?
  • Codeforces Round 258 (Div. 2) E. Devu and Flowers 生成函数
  • Phi-4-multimodal:图、文、音频统一的多模态大模型架构、训练方法、数据细节
  • 智源开源多模态向量模型BGE-VL:多模态检索新突破
  • google s2部分浅讲