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

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/984413.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

open webui-二次开发-源码启动前后端工程-【超简洁步骤】

参考资料 openwebui docs 获取源码 git clone https://github.com/open-webui/open-webui && cd open-webui启动后端服务 cd backend conda create --name open-webui python3.11 conda activate open-webui pip install -r requirements.txt -U sh dev.sh没有cond…

软件工程笔记下

从程序到软件☆ 章节 知识点 概论☆ 软件的定义&#xff0c;特点&#xff0c;生存周期。软件工程的概论。软件危机。 1.☆软件&#xff1a;软件程序数据文档 &#xff08;1&#xff09;软件&#xff1a;是指在计算机系统的支持下&#xff0c;能够完成特定功能与性能的包括…

Manus AI Agent 技术解读:架构、机制与竞品对比

目录 1. Manus 是什么&#xff1f; 1.1 研发背景 1.2 技术特点 1.3 工具调用能力 1.4 主要应用场景 2. Manus 一夜爆火的原因何在&#xff1f; 2.1 技术突破带来的震撼 2.2 完整交付的产品体验 2.3 生态与开源策略 3. Manus 与其他 AI Agent 的对比分析 3.1 技术架构…

深入探讨 Docker 层次结构及其备份策略20250309

深入探讨 Docker 层次结构及其备份策略 本文将深入探讨 Docker 层次结构 以及在 不同场景下应选择哪种备份方式。通过本文的介绍&#xff0c;您将对如何高效地管理和迁移 Docker 容器有更深的理解。 &#x1f4cc; 什么是 Docker 层次结构&#xff1f; Docker 镜像采用了 分…

Rust语言:开启高效编程之旅

目录 一、Rust 语言初相识 二、Rust 语言的独特魅力​ 2.1 内存安全:消除隐患的护盾​ 2.2 高性能:与 C/C++ 并肩的实力​ 2.3 强大的并发性:多线程编程的利器​ 2.4 跨平台性:适配多环境的优势​ 三、快速上手 Rust​ 3.1 环境搭建:为开发做准备​ 3.2 第一个 R…

邮件发送器:使用 Python 构建带 GUI 的邮件自动发送工具

在本篇博客中&#xff0c;我们将深入解析一个使用 wxPython 构建的邮件发送器 GUI 程序。这个工具能够自动查找指定目录中的文件作为附件&#xff0c;并提供邮件发送功能。本文将从功能、代码结构、关键技术等方面进行详细分析。 C:\pythoncode\new\ATemplateFromWeekReportByM…

JavaWeb-HttpServletRequest请求域接口

文章目录 HttpServletRequest请求域接口HttpServletRequest请求域接口简介关于请求域和应用域的区别 请求域接口中的相关方法获取前端请求参数(getParameter系列方法)存储请求域名参数(Attribute系列方法)获取客户端的相关地址信息获取项目的根路径 关于转发和重定向的细致剖析…

IO多路复用实现并发服务器

一.select函数 select 的调用注意事项 在使用 select 函数时&#xff0c;需要注意以下几个关键点&#xff1a; 1. 参数的修改与拷贝 readfds 等参数是结果参数 &#xff1a; select 函数会直接修改传入的 fd_set&#xff08;如 readfds、writefds 和 exceptfds&#xf…

实现静态网络爬虫(入门篇)

一、了解基本概念以及信息 1.什么是爬虫 爬虫是一段自动抓取互联网信息的程序&#xff0c;可以从一个URL出发&#xff0c;访问它所关联的URL&#xff0c;提取我们所需要的数据。也就是说爬虫是自动访问互联网并提取数据的程序。 它可以将互联网上的数据为我所用&#xff0c;…

计算机网络——交换机

一、什么是交换机&#xff1f; 交换机&#xff08;Switch&#xff09;是局域网&#xff08;LAN&#xff09;中的核心设备&#xff0c;负责在 数据链路层&#xff08;OSI第二层&#xff09;高效转发数据帧。它像一位“智能交通警察”&#xff0c;根据设备的 MAC地址 精准引导数…

【SpringBoot】深入解析 Maven 的操作与配置

Maven 1.什么是Maven? Maven是一个项目管理工具&#xff0c;通过pom.xml文件的配置获取jar包&#xff0c;而不用手动去添加jar包&#xff1b; 2. 创建一个Maven项目 IDEA本身已经集成了Maven&#xff0c;我们可以直接使用&#xff0c;无需安装 以下截图的idea版本为&#xff…

MySQL的安装以及数据库的基本配置

MySQL的安装及配置 MySQL的下载 选择想要安装的版本&#xff0c;点击Download下载 Mysql官网下载地址&#xff1a;​ ​https://downloads.mysql.com/archives/installer/​​ MySQL的安装 选择是自定义安装&#xff0c;所以直接选择“Custom”&#xff0c;点击“Next”​ …

Manus AI : Agent 元年开启.pdf

Manus AI : Agent 元年开启.pdf 是由华泰证券出品的一份调研报告&#xff0c;共计23页。报告详细介绍了Manus AI 及 Agent&#xff0c;主要包括Manus AI 的功能、优势、技术能力&#xff0c;Agent 的概念、架构、应用场景&#xff0c;以及 AI Agent 的类型和相关案例&#xff0…

2.数据结构-栈和队列

数据结构-栈和队列 2.1栈2.1.1栈的表示和实现2.1.2栈的应用举例数制转换括号匹配检验迷宫给求解表达式求值 2.1.3链栈的表示和实现2.1.4栈与递归的实现遍历输出链表中各个结点的递归算法*Hanoi塔问题的递归算法 2.2队列2.2.1循环队列——队列的顺序表示和实现2.2.2链队——队列…

(十七) Nginx解析:架构设计、负载均衡实战与常见面试问题

什么是Nginx? Nginx 是一款高性能的 HTTP 服务器和反向代理服务器&#xff0c;同时支持 IMAP/POP3/SMTP 协议。其设计以高并发、低资源消耗为核心优势&#xff0c;广泛应用于负载均衡、静态资源服务和反向代理等场景。 一、Nginx 的核心优势 高并发处理能力采用异步非阻塞的…

Cpu100%问题(包括-线上docker服务以及Arthas方式进行处理)

&#x1f353; 简介&#xff1a;java系列技术分享(&#x1f449;持续更新中…&#x1f525;) &#x1f353; 初衷:一起学习、一起进步、坚持不懈 &#x1f353; 如果文章内容有误与您的想法不一致,欢迎大家在评论区指正&#x1f64f; &#x1f353; 希望这篇文章对你有所帮助,欢…

【大模型】WPS 接入 DeepSeek-R1详解,打造全能AI办公助手

目录 一、前言 二、WPS接入AI工具优势​​​​​​​ 三、WPS接入AI工具两种方式 3.1 手动配置的方式 3.2 Office AI助手 四、WPS手动配置方式接入AI大模型 4.1 安装VBA插件 4.1.1 下载VBA插件并安装 4.2 配置WPS 4.3 WPS集成VB 4.4 AI助手效果测试 4.5 配置模板文…

架构思维:高性能架构_01基础概念

文章目录 概述基础概念性能指标利特尔法则&#xff08;O T L&#xff09;系统优化策略1. 降低耗时&#xff08;L↓&#xff09;2. 增加容量&#xff08;O↑&#xff09;3. 增加时延&#xff08;L↑&#xff09; 场景化指标选择响应时间优先吞吐量/容量优先平衡策略 概述 一个…

解决stylelint对deep报错

报错如图 在.stylelintrc.json的rules中配置 "selector-pseudo-class-no-unknown": [true,{"ignorePseudoClasses": ["deep"]} ]

VScode 中文符号出现黄色方框的解决方法

VScode 中文符号出现黄色方框的解决方法 我的vscode的python多行注释中会将中文字符用黄色方框框处&#xff1a; 只需要打开设置搜索unicode&#xff0c;然后将这一项的勾选取消掉就可以了&#xff1a; 取消之后的效果如下&#xff1a; 另一种情况&#xff1a;中文显示出现黄色…