CreatorPrimer|编写一个版本号组件插图

在集合类游戏中,不论是大厅还是子游戏都会涉及到版本的更新,在开发调试阶段,检查更新是否生效的一个直观的方法就是观察版本号的变化,因此版本号的显示是游戏中不可缺少的细节,特别是集合类游戏。

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”,先看一下组件提供的属性接口:

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的可视化属性面板暴露接口,可以让非程序员也能轻松使用组件搭建出游戏内容,不知道大家获取版本号是如何实现的呢,也欢迎分享你的实现方案。


奎特尔星球

最后修改日期:2019年10月31日

作者