原帖:https://blog.csdn.net/NRatel/article/details/84251138
为了在CocosCreator 中使用 protobuf
一、安装
前置条件,安装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。而微信禁止了动态生成代码的行为。
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的动态用法:
注意:无法处理import了其他proto文件的proto文件。
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;
以上为creator中使用porotobuf的经验。