在 Cocos Creator 中使用 Protobufjs(一)

一. 环境准备

我一直在探索Cocos H5正确的开发姿势,目前做javascript项目已经离不开 nodejs、npm或grunt等脚手架工具了。

1.初始化package.json文件

npm init

当新建好cocos-js或creator项目,在项目根目录使用npm init命令,一路回车,将在当前目录创建package.json文件用于nodejs三方模块的管理。关于npm的使用细节网络上有很多教程,在此不用细说。

2. protobufjs模块

本人最早在cocos2dx 2.x时代就开始用protobufjs模块来操纵protobuf一直到现在。所以下面所有内容都是关于protobufjs在cocos creator中的使用,包括原生平台(cocos2d-js也是大同小异)。

安装protobufjs到项目

npm install [email protected] --save

使用npm install命令安装模块,注意我们这里使用的是protobufjs 5.x版本。 虽然protobufjs目前最新的 6.x版本,提供了ts、rpc等功能的支持,但有一个问题是在微信小游戏中不能动态加载proto文件。

安装protobufjs到全局

npm install -g [email protected]

使用npm install -g 参数将模块安装到全局,目的主要是方便使用protobufjs提供的pbjs命令行工具。pbjs可以将proto原文件转换成json、js等,以提供不同的加载proto的方式,我们可以根据自己的实际情况选择使用。

二. protobufjs用法

下面是demo中定义的Player.proto文件的内容

syntax = "proto3";
package grace.proto.msg;

message Player {
    uint32  id = 1;         //唯一ID  首次登录时设置为0,由服务器分配
    string  name = 2;       //显示名字
    uint64  enterTime = 3;  //登录时间
}

关于proto具体语法细节这里就不多说了,我们重点如何将Player.proto文件中定义的Player对象在js中实例化、属性赋值、序列化、反序列化操作。

1. 静态语言中使用proto文件

在c++/java这类静态语言中使用protobuf通常是使用官方提供的protoc命令将proto文件编译成c++/java代码,像下面这样:

protoc --cpp_out=输出路径 xxx.proto
protoc --java_out=输出路径 xxx.proto

将输出路径的文件导入对应语言的工程中使用。

2. 在creator项目中使用proto文件

javascript是动态语言,可以在运行时产生对象,因此protobufjs提供了更为便捷的动态编译,将proto文件中的对象生成js对象,下面简要讲解一下在creator中具体的使用步骤:

1.加载proto文件并编译生成proto对象

//导入protobufjs模块
let protobuf = require("protobufjs");
//获取一个builder对象
let builder = protobuf.newBuilder();
//使用protobufjs加文件,并与一个builder对象关联
protobuf.protoFromFile('xxx.proto', builder);
protobuf.protoFromFile('yyy.proto', builder);
...
let PB = builder.build('grace.proto.msg'); 

这步操作主要是使用protobufjs加载、编译proto文件。

2.实例化proto对象与属性赋值

let PB = builder.build('grace.proto.msg')

build函数返回值PB对象中将包含的是在proto中定义所有message对象,现在已经成为js对象,可以被实例化,代码如下:

//实例化Player
let player = new PB.Player();
//属性赋值
player.name = '张三';
player.enterTime = Date.now();

3.proto对象的序列化与反序列化

不说废话,还是直接上代码

...
//使用实例对象上的toArrayBuffer函数将对象序列化为二进制数据
let data = player.toArrayBuffer();
//使用类型对象上的decode函数将二进制数据反序列化为实例对象
let otherPlayer = PB.player.decode(data);

如果幸运你可以在web上使用protobuf了, 为什么只是在web上呢,当你把上面的代码运行在jsb环境下的时候,你会体验到悲催的事情正在发生。

三. 拯救cocos-jsb上的protobufjs

为什么在原生上运行就挂掉了呢?要理解这个问题需要对nodejs?浏览器\cocos-jsb这三个javascript的运行宿主环境有一定的了解。

我之前的文章提到过在选择nodejs模块时,要注意是否同时支持nodejs和web,只要是纯js的模块在cocos中一般都可以随便用,比如async、undersocre、lodash等。
protobufjs这个模块是可以很好的在浏览器和nodejs环境上运行的。但运行在cocos-jsb上就会出问题,首先我们要定位到出问题的关键代码:

protobuf.protoFromFile('xxx.proto', builder);

1. 问题分析

从protobuf.protoFromFile函数名上看就知道是要进行文件的加载,一想到文件加载,就涉及到文件操作的api,我们来整理一下不同平台上的文件接口:

宿主平台 文件接口 说明
浏览器 XMLHttpRequest 浏览器中动态加载资源、文件等AJAX操作的基础
nodejs fs.readFile / fs.readFileSync nodejs上的文件操作模块,底层由c/c++实现
cocos-jsb jsb.fileUtils.getStringFromFile cocos-js提供的读取文件内容接口,在不台平台(ios\android\windows)由不同底层api实现

看到这里相信很多人已经明白为什么在cocos-jsb上会有问题了,我们再来读一下protobufjs源码,证实下我们的分析。

2. 分析protobufjs源码


找到protobufjs加载文件的主要代码,下面我为源码加上了注释,请认真读一下注释内容:

Util.fetch = function(path, callback) {
    //检查callback参数,callback参数决定是否为异步加载
    if (callback && typeof callback != 'function')
        callback = null;

    //运行环境是否为nodejs
    if (Util.IS_NODE) {
        //加载nodejs的文件系统模块
        var fs = require("fs");
        //检查是否有callback,存在使用fs.readFile异步函数读取文件内容
        if (callback) {
            fs.readFile(path, function(err, data) {
                if (err)
                    callback(null);
                else
                    callback(""+data);
            });
        } else
            //使用fs.readFileSync同步函数读取文件内容
            try {
                return fs.readFileSync(path);
            } catch (e) {
                return null;
            }
    } else {
        //当不为nodejs运行环境使用XmlHttpRequest加载文件
        var xhr = Util.XHR();
        //根据callbcak参数是否存在,使用异步还是同步方式
        xhr.open('GET', path, callback ? true : false);
        // xhr.setRequestHeader('User-Agent', 'XMLHTTP/1.0');
        xhr.setRequestHeader('Accept', 'text/plain');
        if (typeof xhr.overrideMimeType === 'function') xhr.overrideMimeType('text/plain');
        //通过XmlHttpRequest.onreadystatechange事件函数异步获取文件数据
        if (callback) {
            xhr.onreadystatechange = function() {
                if (xhr.readyState != 4) return;
                if (/* remote */ xhr.status == 200 || /* local */ (xhr.status == 0 && typeof xhr.responseText === 'string'))
                    callback(xhr.responseText);
                else
                    callback(null);
            };
            if (xhr.readyState == 4)
                return;
            //调用send方法发起AJAX请求
            xhr.send(null);
        } else {
            ////调用send方法发起AJAX请求,同步获取文件数据
            xhr.send(null);
            if (/* remote */ xhr.status == 200 || /* local */ (xhr.status == 0 && typeof xhr.responseText === 'string'))
                return xhr.responseText;
            return null;
        }
    }
};

从上面的代码可以看出protobufjs库是为浏览器和nodejs准备的,根本就没考虑过cocos-jsb的存在(吐槽:建议cocos官方提供的接口能模仿nodejs这样能少很多事),所以要在cocos-jsb中使用protobufjs其中的一个办法就是修改protobufjs的源码,如下:

Util.fetch = function(path, callback) {
    if (callback && typeof callback != 'function')
        callback = null;
    //将平台检查代码改为cocos提供的接口
    if (cc.sys.isNative) {
        //文件读取使用cocos-jsb提供的函数
        try {
            let data = jsb.fileUtils.getStringFromFile(path);
            cc.log(`proto文件内容: {data}`);
            return data;
        } catch (e) {
            return null;
        }
    } else {
        //web端无需修改,略
        ...
};

我们用cocos的接口将代码修改一下,加载问题就被化解了,问题真的被解决了吗?
不好意思,除了上面要代码外还有一处代码需要修改,源码如下:

BuilderPrototype["import"] = function(json, filename) {
    var delim = '/';

    // Make sure to skip duplicate imports

    if (typeof filename === 'string') {
        //这里又出现了平台检查
        if (ProtoBuf.Util.IS_NODE)
            // require("path")是加载nodejs的path模块,resolve
            filename = require("path")['resolve'](filename);
        if (this.files[filename] === true)
            return this.reset();
        this.files[filename] = true;

    } else if (typeof filename === 'object') { // Object with root, file.

        var root = filename.root;
        //这里还要修改
        if (ProtoBuf.Util.IS_NODE)
            root = require("path")['resolve'](root);
        if (root.indexOf("\\") >= 0 || filename.file.indexOf("\\") >= 0)
            delim = '\\';
        var fname;
         //这里还要修改
        if (ProtoBuf.Util.IS_NODE)
            fname = require("path")['join'](root, filename.file);
        else
            fname = root + delim + filename.file;
        if (this.files[fname] === true)
            return this.reset();
        this.files[fname] = true;
    }
    ...
}

这里我就不再贴修改代码了,大家自行解决。

四 为protobuf继续填坑

本来写到这里,问题大多已经解决了, 但此时,如果你满怀信心地使用改造后的protobufjs源码,将你的代码运行起来那一刻,我相信绝大多数人会一脸蒙逼。

妈的根本就不行!!看了好多字,好不容易读到这里,不仅在模拟器上跑不起来,在web上同样也跑不起来。

怎么办,为了彻底解决问题,我还得继续写下去。

1. 了解creator动态加载资源的方法

请大家思考一个问题,creator项目中的一张图片,在web与cocos-jsb上他们的文件路径会一样吗?直接使用protobuf.protoFromFile(‘xxx.proto‘)去加载一个proto文件会成功吗?
cocos文档中说过要动态加载一个图片资源需要将文件存放在assets/resources目录下,使用如下方法加载:

cc.loader.loadRes('resources/xxx')

尝试将proto文件存放在resources/pb/目录下,用使用以下代码:

protobuf.protoFromFile('resources/pb/xxx.proto')

同样会得到失败的提示,该如何办呢?怎么才能获得正确的资源路径?
算了,不买关子了,写累了直接出答案吧!

protobuf.protoFromFile(cc.url.raw('resources/pb/xxx.proto'));

cc.url.raw这个函数在浏览器、模拟器、手机上会返回不同的资源路径,这才是真正的资源路径,这下代码应该可以正常运行起来了。

2. 更好的解决法办

我一直在探索Cocos H5正确的开发方式,虽然通过修改protobufjs源码的方法可以来解决在cocos-jsb上运行的问题,但这并不是唯一的解决方案。

如何在不修改protobufjs源码的情况下让代码运行起来,以及使用pbjs工具预编译proto文件为JSON和js文件的用法,请继续观注我的系列文章《当Creator遇上protobufjs》!

原文地址:https://www.cnblogs.com/creator-star/p/11584504.html

时间: 2024-08-02 17:36:23

在 Cocos Creator 中使用 Protobufjs(一)的相关文章

Cocos Creator中的动画支持技术

Cocos Creator主要亮点 官方的权威描述是:Cocos Creator是以内容创作为核心的一体化游戏开发工具,这个引擎基于Cocos2d-x,组件化,脚本化,数据驱动,跨平台发布.本人使用一段时间后的感觉是,这个工具并不仅仅实现了开发语言由C++向Javascript的转移(当然Cocos Creator是从Cocos2d-js发展而来的),其真正的亮点在于它实现了全面型游戏开发思想的转变.程序员与美工都要熟悉这个工具的整体使用流程,并重点了解二者如何交互.本人感觉在设计之初,通过类似

Cocos Creator 中 _worldMatrix 到底是什么(上)

Cocos Creator 中 _worldMatrix 到底是什么(上) 1. (矩阵)Matrix是什么,有什么用 (矩阵)Matrix一个神奇的存在?在开发过程中对里边各项值的含义是不是抓耳挠腮,百思不得其解?今天我们就来庖丁解牛,拨开它的神秘面纱.由于内容较多,关于Cocos Creator 中的_worldMatrix会分为三篇文章完成.最终形成一个完整的demo 首先我们先看看在Cocos Creator编辑器中,对应图形的变化都有那些属性,如下图 红框的地方分别是位移.旋转.缩放.

在cocos creator中使用代码给TileMap地图块添加碰撞体

cc.Class({ extends: cc.Component, properties: { tiledMap:cc.TiledMap }, // LIFE-CYCLE CALLBACKS: onLoad () { //开启物理效果 cc.director.getPhysicsManager().enabled = true; cc.director.getPhysicsManager().debugDrawFlags = true; }, start () { let tiledSize =

Cocos Creator 中根据uuid快速定位资源

var BASE64_KEYS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='; var BASE64_VALUES = new Array(123); // max char code in base64Keys for (let i = 0; i < 123; ++i) BASE64_VALUES[i] = 64; // fill with placeholder('=') index for (le

COCOS CREATOR(TS)之setTimeOut

一 : 前景 有很多前端可能觉得setTimeOut不值得一讲,But,在不同的平台setTimeOut的执行是有一定的规则需要遵守,有时也叫迫于无奈.比如,前几日的开发中发现一个非常奇怪的现象 , 就是setOutTime在Web / 微信IDE端都是可以起作用的 , 但是一旦到了手机微信中运行,好似就不那么灵光了.今日抽了点时间好好测试了一番 , 内容如下: 二 : 测试 Ⅰ,测试环境如下(使用TS代码)Ⅱ,代码1,测试思想分别给出有参数(setTimeOut自带传参和setTimeOut不

麒麟子Cocos Creator实用技巧一:如何正确地显示微信头像

不管是游戏App,还是H5,又或者是微信小游戏.但凡接入了微信登录的应用,都可能需要显示微信头像. 在Cocos Creator中,我们常见的显示方法像下面这样 var headimg = 'http://thirdwx.qlogo.cn/mmopen/vi_32/DYAIOgq83erD6MOUwRKV9NyBAqnoFDTnltzAe2zWOkKxyDOFibVBb1ZV5CaATJwYAuNqZ5sXMBC4c8iacaHDf8RA/132'; cc.loader.load({url:he

Cocos Creator Android打包 apk

这一篇讲的是用 Cocos Creator 编译器打包 Android APP 的时候遇到的一些问题,虽然说打包的过程不是很复杂,但是在其中还是会遇到各式各样的坑. 我们将项目用CCC(Cocos Creator)编译器打包的时候,肯定是要先配置一下本机的开发环境,我这里主要是打包成 Android APP 的时候需要依赖的开发环境. 下载 Java SDK(JDK) 在编译 Android 工程的时候需要在本地电脑上有完整的 Java SDK 工具,如果没有这个工具可以到下面的地址下载: Ja

Cocos Creator开发中的按钮节点的事件处理方法

Cocos Creator开发中的按钮节点的事件处理方法 Cocos Creator基于Cocos2d-x(C++方案),提出全新的编程理念.Cocos Creator是以内容创作为核心的游戏开发工具,在Cocos2d-x基础上实现了彻底脚本化(可以使用Javascript.Typescript或者Coffeescript).组件化和数据驱动等特点.但是,在事件处理机制上并不显得简单,至少是新手初学阶段如此.下面我们通过场景制作中按钮组件的事件处理机制来说明这个问题. 试验目的 通过为按钮组件设

Cocos Creator 使用protobufjs

Win7 + Creator 2.0.0 + protobufjs 6.8.8 1.下载安装protobufjs npm install -g protobufjs 可以看到protobufjs安装在C:\Users\Administrator\AppData\Roaming\npm\node_modules\protobufjs中 2.在protobufjs\dist中找到protobuf.js文件,并作为插件拖放到Creator中(注意,必须作为插件,并且是四个选项都必须选中,否则将报错!)