在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 protobufjs@5 --save

使用npm install命令安装模块,注意我们这里使用的是protobufjs 5.x版本。 虽然protobufjs目前最新的 6.x版本,提供了ts、rpc等功能的支持,但接口变化太大,目前还不太会使用。

####安装protobufjs到全局

npm install -g protobufjs@5

使用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环境下的时候,你会体验到悲催的事情正在发生。

#完整内容点击以下连接
http://www.jianshu.com/p/c4b8a8e3077f

8赞

还是预编译 proto 为 js 比较靠谱

同意楼上的。推荐谷歌官方版的 js protobuf

是动态加载,还是预编译js,各有优缺点。
动态加载,可以把proto当配置用,不需要参与creator的编译,修改起来方便
预编译js,少一步加载过程,但修改一次proto文件需要编译一次

可以把 js 设置成插件脚本

虽然你推荐的方法效率高, 但是没找到比较完整的教程.
会不会ccc官方给个示例工程?或者在Example里加一项

抱歉目前没有,不过论坛里不少相关帖子你可以找找

1赞

看到公众号有推送了,赞

非常感谢

#继续之前的probufjs的探索

#一、 不修改源码让protobufjs适应多平台
我们上一篇《在cocos creator中使用protobufjs(一)》讲解了通过修改源码的方案,让protobufjs能正常运行在jsb环境上。这个方案适合将protobufjs源码直接放到项目中,而我们使用npm来管理三方库的方式,这种方案就显得不太优雅。
##1. 解决IS_NODE的检查
之前源码中已经看到Util.IS_NODE是用来区分代码是运行在nodejs上还是浏览器上。我们可以模拟cocos-jsb为nodejs环境,我们看protobufjs是怎么来检查环境的。

Util.IS_NODE = !!(
    typeof process === 'object' && process+'' === '[object process]' && !process['browser']
);

上面这段代码我们注意两个地方:

  • !!:在一个变量或表达示前面使用“!!”的意思是将其值转换为boolean值即true或false,这是js中常用的技术,第一次见这种写法的人可会犯晕。
  • process:process对象是nodejs的内置进程对象,在cocos-jsb上肯定是没这货,那怎么办呢?

#####方案一:伪装者
伪装者---适配器模式
在require(‘protobufjs’)之前我们自己定义一个process对象

if(cc.sys.isNative) {
    global.process = { 
        toString: () => '[object process]'
    }
}
...
require('protobufjs');

这种方案相当于欺骗protobufjs我们是nodejs,这段代码也解释两句:

  • global: global对象是js中很特殊的对象,全局的方法、属性都集中在一个对象中。我们这里将process对象放到global上相当于定义了全局变量。
  • toString方法:js中所有对象上都具有toString方法(除null\undefined外),当你在对象上使用字符串连接“+”操作时,其实是调用的对象的toString方法。

这种方法可将coco-jsb化身为nodejs,但感觉有点文绉绉的,我们再看看更直接的方法。

#####方案二:霸王硬上弓
霸王硬上弓---直接修改内存
在require(‘protobufjs’)之后强制修改Util.IS_NODE的值

protobufjs.Util.IS_NODE = cc.sys.isNative;

这个方法简单直接,而且不怕他修改检查方案,我觉得这个方法更好。

##2. 解决fs.readFile/fs.readFileSync

...
if (Util.IS_NODE) {
    //cocos中那来的fs模块呀?
    var fs = require("fs");
    if (callback) {
        fs.readFile(path, function(err, data) {
            if (err)
                callback(null);
            else
                callback(""+data);
        });
    } else
        try {
            return fs.readFileSync(path);
        } catch (e) {
            return null;
        }
}
...

这里不能硬来了,硬来只能改源码,使用伪装的方法,我们去编写一个fs模块

//fs.js
module.exports = {
    //同步读取文件
    readFileSync(path) {
        //cocos-jsb提供有相同功能的函数,就借用下它
        return jsb.fileUtils.getStringFromFile(path);
    }

    //异步读取文件
    readFile(path, cb) {
        //cocos-jsb没提供异步读取文件的函数,这里只能简单执行下回调传回读取内容
        let str = jsb.fileUtils.getStringFromFile(path);
        cb(null, str); 
    },
}

我们这里是偷梁换柱,实现了一个fs模块,这关算是过了。这里需要注意的是jsb.fileUtils对象,上面封装有不少原生上的文件操作。

大多数方法一看名字就知道用法了,这里就不再一一说明。
#完整内容点击以下连接
http://www.jianshu.com/p/e04ee7d4007d

赞,期待下文!

下文来了
http://www.jianshu.com/p/2b9791ae3eea

在cocos creator中使用protobufjs(一)
在cocos creator中使用protobufjs(二)
通过前面两篇我们探索了如何在creator中使用protobuf,并且让其能正常工作在浏览器、JSB上,最后聊到protobuf在js项目中使用上的一些痛点。这篇博文我要把这些痛点一条一条地扳开,分析为什么它让我痛,以及我的治疗方案。

#一、proto文件的加载问题
我遇到的第一个痛点就是proto文件的加载问题。有人可能会问,前面不是讲了怎么加载方法很简单的:

...
let builder = new protobuf.Builder();
protobuf.loadProtoFile('aaa.proto', builder);
protobuf.loadProtoFile('bbb.proto', builder);
...

protobufjs是一个很优秀的库,他提供的loadProtoFile接口简单直接,但是在真实的项目开发中会像是上面这样的吗?proto文件是一开始就设计好了,固定不变的吗?文件名会修改吗?文件会新增、删除吗?

##痛点分析
我只有第一天在cocos-js项目中使用proto时是将一个一个的proto文件名写死在loadProtoFile的参数中的,因为那是我中途参与的项目,当时我就发现了问题:

  1. 路径名、文件较长容易写错字。
  2. 项目开发中协议会不断新增,会写漏,少加载了proto文件。
  3. 某些原因会修改proto文件名,原来加载的没及时修改,加载时会出错。
  4. 人工手写这个加载文件会很累,效率低下,容易出错,在文件众多的情况下极度消耗脑细胞。

##解决办法

编写代码来生成代码

我的解决办法是编写一个程序,扫描proto文件目录,生成一个文件列表的数组,从而完全解放人工操作。

//protoFiles.js 用脚本自动生成的文件
module.exports = [
  res/proto/aaa.proto,
  res/proto/bbb.proto,
  res/proto/zzz.proto,
  res/proto/login/xxx.proto
  ...
]

//pbhelper.js 编写一个加载器
let protoFiles = require('protoFiles'); //导入自动生成的proto文件列表
...
loadProtoFile() {
    let builder = new protobuf.Builder();
    //遍历文件名,逐一加载
    protoFiles.forEach((protoFile) => {
        protobuf.loadProtoFile(protoFile, builder);
    })
    ...
}

从此再也不用担心proto文件加载方面的问题了。

##解放更多人工操作
在编写proto扫描脚本的同时,还可以将proto文件同步到自己的工程目录中,以解决proto文件的手工复制粘贴问题,如果你还要更进一步,还可以将svn/git的拉取给做了。

#完整内容点击以下连接
http://www.jianshu.com/p/2b9791ae3eea

伪装者那个解决方案 如果涉及到了 一个proto文件 里面有 import 多个proto文件貌似 会出现引导不到!!这个问题可以解决吗?

我试了一下没有你说的问题呢?
你可以把你的proto文件发上来,我可以帮你试试。

教程再哪里呀