Appearance
Cocos Creator 热更新
热更新是指在不重新发布 App 的情况下,通过网络下载新版本的脚本和资源来更新游戏内容。
2.x
2.x 使用 jsb.AssetsManager 管理热更新,依赖 project.manifest 和 version.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 实现热更新,粒度更细,可以只更新某个子包。
基本思路
- 将可更新内容打包为 AssetBundle(在编辑器 Inspector 中勾选 "Is Remote Bundle")
- 构建时设置 Bundle 的远程 URL
- 运行时通过
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_MANIFEST | manifest 文件未打包进 App | 确认 manifest 放在 resources/ 目录下 |
| 下载进度卡在 0% | 服务器地址错误或跨域 | 检查 packageUrl,服务器开启 CORS |
| 更新后资源未生效 | 未重启游戏 | 调用 cc.game.restart() 或提示用户重启 |
| 版本号比较不生效 | 版本号格式问题 | 建议使用 "1.0.0" 格式的语义化版本 |
| Android 写文件失败 | 缺少存储权限 | AndroidManifest.xml 添加 WRITE_EXTERNAL_STORAGE |