在集合类游戏中,不论是大厅还是子游戏都会涉及到版本的更新,在开发调试阶段,检查更新是否生效的一个直观的方法就是观察版本号的变化,因此版本号的显示是游戏中不可缺少的细节,特别是集合类游戏。
1. 熟悉manifest
这里我们使用 Cocos Creator 提供的 AssetsManager 热更新框架所要求的 project.manifest 它是一个JSON格式的配置文件:
{
"version": "0.0.1",
"packageUrl": "http://192.168.1.100/update",
"remoteManifestUrl": "http://192.168.1.100/update/hall-project.manifest",
"remoteVersionUrl": "http://192.168.1.100/update/hall-version.manifest",
"assets": {}
}
上面是一个hall-project.manifest,再看一个game-project.manifest内容如下:
{
"version": "0.1.1",
"packageUrl": "http://192.168.1.100/update",
"remoteManifestUrl": "http://192.168.1.100/update/game-project.manifest",
"remoteVersionUrl": "http://192.168.1.100/update/game-version.manifest",
"assets": {}
}
获取版本号,其实就是读取 manifest 中的 version 字段,显示到一个 cc.Label 组件上。这对大多数人来说都是小菜一碟,但是Shawn发现,利用组件方式实现一个相对通用的版本号组件做法却并不多见。
2. VersionLabel组件
了解过 manifest,我们就可以开始动手编写一个基于 Cocos Creator 引擎提供的 AssetsManager 热更新框架的版本号组件,这里将组件取名为“VersionLabel”,先看一下组件提供的属性接口:
对于组件的使者关心的是ModuleName属性,当你想显示不同游戏模块的版本时,只需要指定正确的ModuleName就可以了,下面是组件代码:
cc.Class({
extends: cc.Component,
editor: CC_EDITOR && {
requireComponent: cc.Label, //强制依赖cc.Label组件
},
properties: {
default: '0.0.0',
moduleName: {
default: '',
notify(oldValue) {
if (CC_EDITOR || oldValue === this.moduleName) {
return;
}
this._updateContent();
}
}
},
start () {
//获取Label组件
this.label = this.getComponent(cc.Label);
//更新版本内容
this._updateContent();
},
/**
* 更新内容
*/
_updateContent() {
//加载“resources/manifest/xxx-project.manifest”
let url = `manifest/${this.moduleName}-project`;
this._getManifestContent(url, (content) => {
this._setVersion(content);
});
},
/**
*
* @param {String} url resources以下路径
* @param {Function} cb 异步回调函数,返回manifest上下文
*/
_getManifestContent(url, cb) {
cc.loader.loadRes(url, cc.Asset, null, (error, asset) => {
if (error) {
cb(null);
return;
}
//通过nativeUrl读取文件内容
let content = cc.loader.getRes(asset.nativeUrl);
cb(content);
});
},
/**
* 设置Label文本
* @param {String} content
*/
_setVersion(content) {
let data;
try {
data = JSON.parse(content);
this.label.string = data.version;
} catch(e) {
cc.warn(e);
this.label.string = this.default;
}
}
});
简单说明一下上面的代码:
1. 该组件提供了一个moduleName的属性,这里注意不要使用name属性,因为是‘name’是Cocos Creator组件内置属性,还有定义manifest文件需要按照一定文件名命规范,我这里的名命模版是“xxx-project.manifest”
2. 我们是将版本号文本显示到Label组件上,因此requireComponent: cc.Label 是定义该组件强制依赖cc.Label组件。使用它的好处是,当直接将该脚本拖动到场景或层级管理器时,会自动挂载一个cc.Label组件,增强组件的使用体验。
3. manifest文件是放在resources目录下的,虽然manifest内部是json格式,但目前cc.loader还不能直接解析manifest这个扩展名的文件内容,当使用cc.loader.loadRes加载后,只能获取到文件的基本信息,通过使用cc.loader.getRes(asset.nativeUrl)获取文件内容。
3. 读取搜索路径下的manifest
上面的组件代码还存在一个Bug,我们是读取的安装包中的manifest文件,看下面代码:
let url = `manifest/${this.moduleName}-project`;
此文件存在于 resources 目录,当运行在原生设备上它是存在于安装路径中,当游戏通过热更新下载了新的 manifest 文件,此路径是否还正确呢?当然就不对了,我们需要读取热更新包中的 manifest 文件才能获得正确的版本号,看下面代码:
cc.Class({
extends: cc.Component,
...
/**
* 更新内容
*/
_updateContent() {
//如果为原生环境,尝试加载可写路径下的xxx-project.manifes文件
if (cc.sys.isNative) {
let remoteAssets = cc.path.join(jsb.fileUtils ? jsb.fileUtils.getWritablePath() : '/', 'remote-assets');
let url = cc.path.join(remoteAssets, this.moduleName, '-project.manifest');
//使用jsb函数读取文件内容
let content = jsb.fileUtils.getStringFromFile(url);
if (content) {
this._setVersion(content);
return;
}
}
let url = `manifest/${this.moduleName}-project`;
this._getManifestContent(url, (content) => {
this._setVersion(content);
});
},
...
});
上面代码在_updateContent函数中检查当前如果为原生环境,先尝试读取可写路径/remote-assets/xxx-project.manifest
文件,如果文件内容不存在才读取安装包resources目录下的manifest文件。
需要注意的是remote-assets
是使用AssetsManager下载热更新包时指定的,你需要根据自己的实际情况设置正确的路径。
源码地址:https://github.com/ShawnZhang2015/CreatorPrimer
4. 小结
读取版本号这个逻辑上下文只需要关心到那里去获取版本字符串,使用组件的方式,可以将很多逻辑细节代码封装到一段小代码中独立执行,以达到与其它代码老死不相往来,从而有效减少耦合。
同时借助Cocos Creator的可视化属性面板暴露接口,可以让非程序员也能轻松使用组件搭建出游戏内容,不知道大家获取版本号是如何实现的呢,也欢迎分享你的实现方案。