为了在CocosCreator 中使用 protobuf,NRatel走了不少弯路。
一、安装
前置条件,安装Node.js 、npm。
查看候选版本:
npm view protobufjs versions
新建一个项目目录,用来转换.proto为.js。执行 npm init -y 初始化项目。
选择需要的版本安装,(这里用的是6.8.8版):
npm install --save-dev protobufjs@6.8.8
执行后,将出现node_modules目录,
要执行的转换命令文件为:node_modules\.bin\pbjs.cmd, 内容如下:
-
::当前目录是否存在node.exe
-
@IF EXIST
"%~dp0\node.exe" (
-
::使用node执行pbjs进行文件转换
-
"%~dp0\node.exe"
"%~dp0\..\protobufjs\bin\pbjs" %*
-
) ELSE (
-
@SETLOCAL
-
::将环境变量PATHEXT中的JS删除
-
@SET PATHEXT=%PATHEXT:;.JS;=;%
-
::使用node执行pbjs进行文件转换
-
node
"%~dp0\..\protobufjs\bin\pbjs" %*
-
)
实际上它最终是,用 node执行了 node_modules\protobufjs\bin\pbjs文件。
二、使用
protobufjs 提供了多种使用方式,但通常主要还是采用 生成静态.js使用和动态加载.proto文件使用这两种方式。
注意, protobufjs 依赖了 long.js, bytebuffer.js。放入工程即可。
1.静态方式(推荐!)
1). 执行命令获取帮助,确认参数含义和用法。避免版本改变导致用法改变导致的错误:
node_modules\.bin\pbjs -h
2). 执行命令进行 .proto文件到.js的转换操作:
注意,要执行的文件从上层目录开始执行时,在windows下为反斜杠间隔。
这里的版本中的 -t 指定目标格式;-w 指定模块引用规范;-o指定输入输出文件。具体以pbjs -h中的说明为准。
node_modules\.bin\pbjs -t static-module -w commonjs -o protores.js *.proto
3). 示例:
.proto源文件。
-
// 日志
-
package log;
-
-
// 提交评论
-
message comment_C {
-
optional
string msg =
1;
// 评论内容
-
}
可能需要修改protores.js文件顶部 对protibuf.js的引用(修改引用路径,改为自己使用的版本)。
-
//var $protobuf = require("protobufjs/minimal");
-
var $protobuf =
require(
"protobuf");
将使用方法封装为更通用、更易用的方式。
-
let protores =
require(
"protores");
-
-
let Pbjs6 =
class Pbjs6 {
-
//packageName: package名
-
//msgTypeName: 消息类型名
-
static Encode(packageName, msgTypeName, data) {
-
var msgType = protores[packageName][msgTypeName];
-
var msg = msgType.create(data);
-
var bytes = msgType.encode(msg).finish();
-
return bytes;
-
}
-
-
static Decode(packageName, msgTypeName, bytes) {
-
var msgType = protores[packageName][msgTypeName];
-
var msg = msgType.decode(bytes);
-
var data = msgType.toObject(msg, {
-
longs:
Number,
//long默认转换为Number类型
-
enums:
String,
-
bytes:
String,
-
// see ConversionOptions
-
});
-
return data;
-
}
-
}
-
-
module.exports = Pbjs6;
测试:
-
let Pbjs6 =
require(
"Pbjs6");
-
let bytes = Pbjs6.Encode(
"log",
"comment_C", {
msg:
"NRatel" });
-
cc.log(
"bytes: ", bytes);
//Uint8Array(14) [10, 12, 110, 105, 101, 104, 111, 110, 103, 113, 105, 97, 110, 103]
-
-
let data = Pbjs6.Decode(
"log",
"comment_C", bytes);
-
cc.log(
"data: ", data);
//{ msg: "NRatel" }
2.动态方式(不推荐!)
为什么要动态加载?,为了包体越小越好(微信小游戏中包体要求4M的限制)。
为什么不推荐?,因为 protobufjs6.x 在微信小游戏中不可用(其内部使用Es6的Function。而微信禁止了动态生成代码的行为); protobufjs5.x 虽然可用,但无法处理import了其他proto文件的proto文件。
protobufjs6.x的动态用法:
-
let Assets =
require(
"Assets");
-
let protobuf6 =
require(
"protobuf");
//6.x的protobufjs
-
-
let Pbjs6 =
class Pbjs6 {
-
static s_ProtoRootMap =
new
Map();
-
-
static LoadAll(protoDir) {
-
return
new
Promise(
(resolve, reject) => {
-
//二次封装的ccc加载目录的方法
-
Assets.LoadDir_ReturnWithUrls(protoDir).then(
(object) => {
-
let { resArray, urls } = object;
-
for (
let index
in resArray) {
-
let path = urls[index];
-
let key = path.substr(path.lastIndexOf(
'/') +
1, path.length);
-
//生成protoRoot并放入Map, 每个proto文件对应一个protoRoot
-
let protoRoot = protobuf6.parse(resArray[index]).root;
-
this.s_ProtoRootMap.set(key, protoRoot);
-
}
-
return resolve();
-
});
-
});
-
}
-
-
static Encode(packageName, msgTypeName, data) {
-
//根据packageName找到对应的protoRoot
-
let root =
this.s_ProtoRootMap.get(packageName);
-
cc.assert(
typeof (root) !=
"undefined" && root !=
null,
"未找到该protoRoot, 请确保已提前加载, packageName: ", packageName);
-
//根据protoRoot和msgTypeName找到消息类型。
-
let msgType = root.lookupType(msgTypeName);
-
//根据消息类型检查数据
-
let error = msgType.verify(data);
-
cc.assert(error ==
null,
"data数据类型检查失败!", error);
-
//根据实际数据创建消息提,并encode为bytes
-
let msg = msgType.create(data);
-
let bytes = msgType.encode(msg).finish();
-
-
return bytes;
-
}
-
-
static Decode(packageName, msgTypeName, bytes) {
-
//根据packageName找到对应的protoRoot
-
let root =
this.s_ProtoRootMap.get(packageName);
-
cc.assert(
typeof (root) !=
"undefined" && root !=
null,
"未找到该protoRoot,请确保已提前加载, packageName: ", packageName);
-
//根据protoRoot和msgTypeName找到消息类型。
-
var msgType = root.lookupType(msgTypeName);
-
//根据实际bytes解析出原始数据
-
var msg = msgType.decode(bytes);
-
var data = msgType.toObject(msg, {
-
longs:
Number,
-
enums:
String,
-
bytes:
String,
-
// see ConversionOptions
-
});
-
return data;
-
}
-
};
-
-
module.exports = Pbjs6;
protobuf5.x的动态用法:
-
let Assets =
require(
"Assets");
-
let protobuf5 =
require(
"protobuf");
//5.x的protobufjs
-
-
let Pbjs5 =
class Pbjs5 {
-
static s_ProtoRootMap =
new
Map();
-
-
static LoadAll(protoDir) {
-
return
new
Promise(
(resolve, reject) => {
-
//二次封装的ccc加载目录的方法
-
Assets.LoadDir_ReturnWithUrls(protoDir)
-
.then(
(object) => {
-
let { resArray, urls } = object;
-
for (
let index
in resArray) {
-
let path = urls[index];
-
let key = path.substr(path.lastIndexOf(
'/') +
1, path.length);
-
let root = protobuf5.loadProto(resArray[index]).build(key);
-
this.s_ProtoRootMap.set(key, root);
-
}
-
return resolve();
-
});
-
});
-
}
-
-
// 快捷式Encode
-
// 传入msg对应的data
-
static Encode(packageName, msgTypeName, data) {
-
let root =
this.s_ProtoRootMap.get(packageName);
-
cc.assert(
typeof (root) ===
"object" && root !=
null,
"未找到protoPackage:" + packageName);
-
let Message = root[msgTypeName];
-
cc.assert(
typeof (Message) ===
"function" && Message.$type.className ===
"Message",
"未找到Message定义, packageName: " + packageName +
", msgTypeName: " + msgTypeName);
-
let msg =
new Message();
-
for (
const p
in data) {
-
if (data.hasOwnProperty(p)) {
-
msg.set(p, data[p],
false);
-
}
-
}
-
let bytes =
new
Uint8Array(msg.encode().toBuffer());
-
return bytes;
-
}
-
-
// 面向对象式Encode。
-
// 在callback中 对msg 的字段逐个 set 进行Encode。
-
// 可以调用set(key, value), 也可以直接调用set_字段名(value), 字段命名规则为:同时支持下划线格式和驼峰格式。
-
static EncodeOO(packageName, msgTypeName, callback) {
-
let root =
this.s_ProtoRootMap.get(packageName);
-
cc.assert(
typeof (root) ===
"object" && root !=
null,
"未找到protoPackage:" + packageName);
-
let Message = root[msgTypeName];
-
cc.assert(
typeof (Message) ===
"function" && Message.$type.className ===
"Message",
"未找到Message定义, packageName: " + packageName +
", msgTypeName: " + msgTypeName);
-
let msg =
new Message();
-
msg = callback(msg);
-
let bytes =
new
Uint8Array(msg.encode().toBuffer());
-
return bytes;
-
}
-
-
static Decode(packageName, msgTypeName, bytes) {
-
let root =
this.s_ProtoRootMap.get(packageName);
-
cc.assert(
typeof (root) ===
"object" && root !=
null,
"未找到protoPackage:" + packageName);
-
let Message = root[msgTypeName];
-
cc.assert(
typeof (Message) ===
"function" && Message.$type.className ===
"Message",
"未找到Message定义, packageName: " + packageName +
", msgTypeName: " + msgTypeName);
-
-
let msg = Message.decode(bytes);
-
return msg;
-
}
-
};
-
-
module.exports = Pbjs5;
(https://blog.csdn.net/NRatel/article/details/84251138).