Skip to content

Cocos Creator 热更新

热更新是指在不重新发布 App 的情况下,通过网络下载新版本的脚本和资源来更新游戏内容。


2.x

2.x 使用 jsb.AssetsManager 管理热更新,依赖 project.manifestversion.manifest 两个版本描述文件。

版本文件格式

project.manifest(完整清单,含所有文件的 md5):

json
{
    "packageUrl": "http://your-server.com/res/",
    "remoteManifestUrl": "http://your-server.com/project.manifest",
    "remoteVersionUrl": "http://your-server.com/version.manifest",
    "version": "1.0.1",
    "assets": {
        "res/raw-assets/script.js": {
            "md5": "abc123",
            "size": 1024
        }
    }
}

version.manifest(轻量版,仅含版本号,用于快速检测是否需要更新):

json
{
    "remoteManifestUrl": "http://your-server.com/project.manifest",
    "remoteVersionUrl": "http://your-server.com/version.manifest",
    "version": "1.0.1"
}

版本文件可用 Cocos Creator 自带的 hot-update-tools 脚本自动生成。

热更新流程代码

ts
const { ccclass, property } = cc._decorator;

@ccclass
export default class HotUpdate extends cc.Component {
    private _am: jsb.AssetsManager = null;

    onLoad() {
        if (!cc.sys.isNative) {
            cc.log("热更新仅在原生平台生效");
            return;
        }

        const storagePath = jsb.fileUtils.getWritablePath() + "hot-update/";
        const manifestUrl = cc.url.raw("resources/version.manifest");

        this._am = new jsb.AssetsManager(manifestUrl, storagePath);
        this._am.setVersionCompareHandle((versionInApp, versionInServer) => {
            return versionInServer > versionInApp ? 1 : 0;
        });

        this.checkUpdate();
    }

    checkUpdate() {
        this._am.setEventCallback(this.onUpdateEvent.bind(this));
        this._am.checkUpdate();
    }

    onUpdateEvent(event: jsb.EventAssetsManager) {
        const { EventCode } = jsb.EventAssetsManager;

        switch (event.getEventCode()) {
            case EventCode.NEW_VERSION_FOUND:
                cc.log("发现新版本,开始下载");
                this._am.update();
                break;
            case EventCode.ALREADY_UP_TO_DATE:
                cc.log("已是最新版本");
                this._am.setEventCallback(null);
                break;
            case EventCode.UPDATE_PROGRESSION:
                const percent = (event.getPercent() * 100).toFixed(1);
                cc.log(`下载进度: ${percent}%`);
                break;
            case EventCode.UPDATE_FINISHED:
                cc.log("更新完成,重启游戏生效");
                this._am.setEventCallback(null);
                cc.game.restart();
                break;
            case EventCode.ERROR_NO_LOCAL_MANIFEST:
                cc.error("找不到本地 manifest 文件");
                break;
            case EventCode.ERROR_DOWNLOAD_MANIFEST:
                cc.error("下载 manifest 失败");
                break;
            case EventCode.ERROR_UPDATING:
                cc.error("更新出错:", event.getAssetId(), event.getMessage());
                break;
        }
    }

    onDestroy() {
        if (this._am) {
            this._am.setEventCallback(null);
        }
    }
}

3.x

3.x 推荐使用 AssetBundle 实现热更新,粒度更细,可以只更新某个子包。

基本思路

  1. 将可更新内容打包为 AssetBundle(在编辑器 Inspector 中勾选 "Is Remote Bundle")
  2. 构建时设置 Bundle 的远程 URL
  3. 运行时通过 assetManager.loadBundle 加载远程 Bundle

热更新流程代码

ts
import { _decorator, Component, assetManager, AssetManager } from 'cc';
const { ccclass } = _decorator;

@ccclass('HotUpdate')
export class HotUpdate extends Component {

    // 检查并更新指定 Bundle
    async updateBundle(bundleName: string, remoteUrl: string) {
        return new Promise<void>((resolve, reject) => {
            // 先加载远程的 Bundle 版本信息(实际通过 manifest 对比)
            assetManager.loadBundle(remoteUrl + "/" + bundleName, (err, bundle) => {
                if (err) {
                    console.error("Bundle 加载/更新失败:", err);
                    reject(err);
                    return;
                }
                console.log("Bundle 更新成功:", bundle.name);
                resolve();
            });
        });
    }

    // 使用示例
    async onLoad() {
        try {
            await this.updateBundle("gameplay", "http://your-server.com/bundles");
            console.log("热更新完成,加载新资源");

            // 加载 Bundle 内的资源
            const bundle = assetManager.getBundle("gameplay");
            bundle.load("prefabs/NewEnemy", (err, prefab) => {
                if (!err) {
                    console.log("新版敌人预制体加载成功");
                }
            });
        } catch (e) {
            console.error("热更新失败,使用本地版本");
        }
    }
}

常见错误排查

错误现象可能原因解决方案
ERROR_NO_LOCAL_MANIFESTmanifest 文件未打包进 App确认 manifest 放在 resources/ 目录下
下载进度卡在 0%服务器地址错误或跨域检查 packageUrl,服务器开启 CORS
更新后资源未生效未重启游戏调用 cc.game.restart() 或提示用户重启
版本号比较不生效版本号格式问题建议使用 "1.0.0" 格式的语义化版本
Android 写文件失败缺少存储权限AndroidManifest.xml 添加 WRITE_EXTERNAL_STORAGE