如果想要直接看微信小程序的解决方案-角标任意门
[1]
首先要说的是,我所使用的creator版本是:
creator 版本
在写这篇博客之前,我也曾做了很多搜索,也搜出一些有价值的东西,只不过很多东西都已经过时了,,或者说我们现在有更好的方式去实现我们的功能。
废话不多说,让我们直接开始吧。
1
新建一个Hello World项目,命名为CreatorTest。
先放一边。
2
前往protobufjs开源项目库,我们去下一个最新的release版本。为什么用它?自然是有人一直在更新维护呀,而且用起来还不错!下载地址
下载完成后,解压,打开它,我们找到这个文件protobuf.js:
protobuf.js
3
打开creator,拖动protobuf.js到项目中,
也可以在导入的时候选择是否导入为插件,选是
就可以了。
这里说下为什么要导入为插件:creator在构建时候会将我们编辑器里所有的js脚本都打包到一个project.js的文件中,原生(native)的话就是project.jsc,如果我们的protobuf.js打包进去就会报错了,,所以这里需要导入为插件,这样做是为了避免错误
creator 构建的结果导入为插件后,我们就直接能使用protobuf了。
4
我们把protobufjs的官方例子抄过来,顺便做些改动。
创建我们的test1.proto
文件:
package PbLobby;
syntax = "proto3";
enum Cmd {
CMD_BEGIN = 0;
CMD_KEEPALIVED_C2S = 10000; //心跳包测试
CMD_LOGIN_C2S2C = 10001; // 登录
}
message Test1{
int32 id = 1;//活动ID
string name = 2;//名字
}
把它放到resources
目录下,resources
目录如果没有就自己创建一个,如下图
修改我们的HelloWorld.js
,增加一个函数:
testProtobuf: function () {
if (cc.sys.isNative) {//在native上加载失败,是因为没有找到目录,我们在testProtobuf函数里面添加一个搜索目录:
cc.log("jsb.fileUtils=" + jsb.fileUtils);
//下面这段代码在PC window平台运行没问题,但是在android下面就出问题了
//jsb.fileUtils.addSearchPath("res\\raw-assets\\resources", true);
//需要改成这样:
jsb.fileUtils.addSearchPath("res/raw-assets/resources", true);//坑太多了。。没办法
}
var filename1 = "test1.proto";
// cc.loader.loadRes(filename1, cc.TextAsset, function (error, result) {//指定加载文本资源
// cc.log("loadRes error=" + error + ",result = " + result + ",type=" + typeof result);
// // callback(null, result);
// });
var protobufHere = protobuf;//require("protobuf");//导入为插件,直接使用
protobufHere.load(filename1, function (err, root) {//Data/PbLobby.proto
if (err)
throw err;
cc.log("root=" + root);
for (var i in root) {
cc.log("root." + i + "=" + root[i]);
}
//return;
cc.log("加载protobuf完毕,开始测试protobuf...")
var cmd = root.lookupEnum("PbLobby.Cmd");
cc.log(`cmd = ${JSON.stringify(cmd)}`);
cc.log("CMD_KEEPALIVED_C2S = "+cmd.values.CMD_KEEPALIVED_C2S);
//lookup 等价于 lookupTypeOrEnum
//不同的是 lookup找不到返回null,lookupTypeOrEnum找不到则是抛出异常
var type1 = root.lookup("PbLobby.Cmd1");
cc.log("type1 = "+type1);
var type2 = root.lookup("PbLobby.Test1");
cc.log("type2 = "+type2);
// Obtain a message type
var Test1Message = root.lookupType("PbLobby.Test1");
cc.log("Test1Message = "+Test1Message);
// Exemplary payload
var payload = { id: 1,name:"hello protobuf" };
//var payload = { ids: 1,name:"hello protobuf" };
cc.log(`payload = ${JSON.stringify(payload)}`);
//检查数据格式,测试了下发现没什么卵用
// Verify the payload if necessary (i.e. when possibly incomplete or invalid)
// var errMsg = Test1Message.verify(payload);
// if (errMsg){
// cc.log("errMsg = "+errMsg);
// throw Error(errMsg);
// }
//过滤掉一些message中的不存在的字段
// Create a new message
var message = Test1Message.create(payload); // or use .fromObject if conversion is necessary
cc.log(`message = ${JSON.stringify(message)}`);
// Encode a message to an Uint8Array (browser) or Buffer (node)
var buffer = Test1Message.encode(message).finish();
cc.log("buffer1 = "+buffer);
cc.log(`buffer2 = ${Array.prototype.toString.call(buffer)}`);
// ... do something with buffer
// Decode an Uint8Array (browser) or Buffer (node) to a message
var decoded = Test1Message.decode(buffer);
cc.log("decoded1 = "+decoded);
cc.log(`decoded2 = ${JSON.stringify(decoded)}`);
// ... do something with message
// If the application uses length-delimited buffers, there is also encodeDelimited and decodeDelimited.
//一般情况下,也不需要下面的转换
// Maybe convert the message back to a plain object
var object = Test1Message.toObject(decoded, {
longs: String,
enums: String,
bytes: String,
// see ConversionOptions
});
cc.log("object = "+JSON.stringify(object));
});
},
然后在onLoad
函数添加代码this.testProtobuf();
运行看一下,发现报错了,在这行代码
return callback(Error("status " + xhr.status));
protobuf默认是用XMLHttpRequest去请求文件的,所以我们接下去修改一下源码,这是必须的步骤!
5
修改一下protobuf.js的代码,搜索 function fetch(filename, options, callback),把函数改成这样:
function fetch(filename, options, callback) {
if (typeof options === "function") {
callback = options;
options = {};
} else if (!options)
options = {};
if (!callback)
return asPromise(fetch, this, filename, options); // eslint-disable-line no-invalid-this
if (typeof cc !== "undefined") {//判断是否是cocos项目
if (cc.sys.isNative) {//native
var content = jsb.fileUtils.getStringFromFile(filename);
//对于一些新版的creator(作者creator2.3.2)来说,他会把资源混淆在不同的目录下,所以这里是没办法找到该文件的,直接使用cc.loader的loadRes方法尝试加载一次。
if(content === ""){
cc.loader.loadRes(filename, cc.TextAsset, function (error, result) {
cc.log("error1=" + error + ",result = " + result + ",type=" + typeof result);
if (error) {
callback(Error("status " + error))
} else {
//callback(null, result);//creator1.9及以下版本使用此行
callback(null, result.text);//新版creator可放心运行
}
});
} else {
callback(content === "" ? Error(filename + " not exits") : null, content);
}
} else {
//cc.log("cc.loader load 1 filename=" + filename);
//这里可以加载一个url图片 : "Host"+filename
// cc.loader.load(filename, function (error, result) {
// cc.log("error1=" + error + ",result = " + result + ",type=" + typeof result);
// // callback(null, result);
// });
//cc.log("cc.loader load 2");
// 这里h5会去加载resources目录下的文件 : "resources/"+ filename
// 这里filename一般不用指定扩展名,当然你也可以强制指定
cc.loader.loadRes(filename, cc.TextAsset, function (error, result) {
//cc.log("error2=" + error + ",result = " + result + ",type=" + typeof result);
if (error) {
callback(Error("status " + error))
} else {
//callback(null, result);//creator1.9及以下版本使用此行
callback(null, result.text);//新版creator可放心运行
}
});
//cc.log("cc.loader load 3");
}
return;
}
// if a node-like filesystem is present, try it first but fall back to XHR if nothing is found.
if (!options.xhr && fs && fs.readFile)
return fs.readFile(filename, function fetchReadFileCallback(err, contents) {
return err && typeof XMLHttpRequest !== "undefined"
? fetch.xhr(filename, options, callback)
: err
? callback(err)
: callback(null, options.binary ? contents : contents.toString("utf8"));
});
// use the XHR version otherwise.
return fetch.xhr(filename, options, callback);
}
@注意:
对于使用creator1.9及以下的同学注意改一下代码中注释的地方。为了兼容后续的creator版本,我已经把loadRes部分代码改成支持最新版。
6
点击下图按钮,运行看看
运行
我用的是默认chrome浏览器,然后打开Chrome开发者工具
看调试信息,下面是调试信息的截图
上图中,我们可以看到,我们已经成功动态加载了test1.proto
文件。
buffer1和buffer2用了不同的方式打印,打印的结果完全一样,而这个buffer就是我们需要传递给服务器的数据。
后面的decoded数据,建议用第二种方式打印,这样可以直观的看到具体的数据信息。对比一下,decode出来的数据跟我们一开始创建的数据一致——都是
{"id":1,"name":"hello protobuf"}
7
到这里,我们已经成功的走完了使用proto的整个流程:
- 加载proto文件
- 创建message
- encode
- decode
2018-09-16
更新一下native的加载代码
callback(null, content);
改成下面
callback(content === "" ? Error(filename + " not exits") : null, content);
2020-03-10
鉴于微信小程序不支持'Function'的使用,我特地又去研究了pb5.0.3的版本。
里面的README
有这样的提示
如果要使用pb5.0.3,需3个js文件,并且引入顺序如图所示。简单一点,我就整合成一个文件。并相对应的修改了代码。
闲话不表,开始新的实验
新的test1.proto:
syntax = "proto3";
package PbLobby;
enum Cmd {
CMD_BEGIN = 0;
CMD_KEEPALIVED_C2S = 10000; //心跳包测试
CMD_LOGIN_C2S2C = 10001; // 登录
}
message Token {
int64 utc = 1;//时间戳
string token = 2;//令牌
}
message Test1 {
int32 id = 1; //ID
string name = 2; //名字
bytes content = 3; //其他内容
}
新的测试代码片段:
var protobufHere = typeof window === "object" && window["ProtoBuf"] || typeof ProtoBuf === "object" && ProtoBuf;
protobufHere.load("test1.proto", null, function (err, root) {
if (err)
throw err;
that.mProtolBufRoot = root;
cc.log("root=" + root);
for (let i in root)
cc.log("root." + i + "=" + root[i]);
cc.log("加载protobuf完毕,开始测试protobuf...");
var usingPb6 = true;//ture使用pb6.8.6,false使用pb5.0.3
var cmd = root.lookupEnum("PbLobby.Cmd");
if (usingPb6) {
cc.log(`cmd = ${JSON.stringify(cmd)}`);
cc.log("CMD_KEEPALIVED_C2S = " + cmd.values.CMD_KEEPALIVED_C2S);
}
//lookup 等价于 lookupTypeOrEnum : 不同的是 lookup找不到返回null,lookupTypeOrEnum找不到则是抛出异常
var type1 = root.lookup("PbLobby.Cmd");
cc.log("type1 = " + type1);
var type2 = root.lookup("PbLobby.Test1");
cc.log("type2 = " + type2);
// Obtain a message type
var Test1Message = root.lookupType("PbLobby.Test1");
cc.log("Test1Message = " + Test1Message);
// Exemplary payload
var bytes = new Uint8Array(3);
bytes[0] = 10;
bytes[1] = 0x10;
bytes[2] = 0xFF;
var payload;
if (usingPb6) {
payload = {id: 1, name: "hello protobuf", content: bytes};
} else {
payload = {id: 1, name: "hello protobuf", content: new ByteBuffer(bytes.length).append(bytes, 0)};
}
cc.log(`payload = ${JSON.stringify(payload)}`);
var buffer;
// Create a new message - protobuf6.8.6
if (usingPb6) {
var message = Test1Message.create(payload); // or use .fromObject if conversion is necessary
cc.log(`message = ${JSON.stringify(message)}`);
buffer = Test1Message.encode(message).finish(); //pb6.8.6
} else {
//protobuf 5.0.3
// for (var k in Test1Message) {
// cc.log("Test1Message[" + k + "]=" + Test1Message[k]);
// }
var ClsTest1 = root.build("PbLobby.Test1");
message = new ClsTest1(payload);
buffer = message.encode().finish();//pb5.0.3
}
cc.log("buffer1 = " + buffer);
cc.log(`buffer2 = ${Array.prototype.toString.call(buffer)}`);
//把buffer传递给服务器 or other
var decoded;
if (usingPb6) {
decoded = Test1Message.decode(buffer);
} else {
var byteBuffer = new ByteBuffer(buffer.length);
byteBuffer.append(buffer, 0);
decoded = Test1Message.decode(byteBuffer).toRaw();
}
cc.log("decoded1 = " + decoded);
cc.log(`decoded2 = ${JSON.stringify(decoded)}`);
cc.log("decoded3 decoded.content = " + decoded.content);
cc.log("decoded3 decoded.content = " + new Uint8Array(decoded.content));
});
usingPb6
变量用于切换使用的是哪种pb
下面是实际运行后的结果:
Pb5
Pb6
观察对比两个截图中的payload
,buffer1
,decode2
,decode3
。
其中decode2
中的content的打印不一致,但是第二个decode3
打印结果可以表面其实际内容是一致的。
ok,到这里就完美的可以切换Pb6和Pb5了。
附上修改过的Pb5(一家之言,仅供参考):
我擦,,文章内容过长无法发布。。。
这样。。要的评论区留一下邮箱,我看到的时候给你们发一份过去(因为是自己在用的版本,稳定度可以)。
2020-05-06更新
//在native加载失败的时候尝试一次cc.loader.loadRes,也可以直接移除cc.sys.isNative的判断,直接用H5的逻辑亦可,作者已经用creator2.3.2-win32项目实验过了。
if (cc.sys.isNative) {//native
var content = jsb.fileUtils.getStringFromFile(filename);
//对于一些新版的creator(作者creator2.3.2)来说,他会把资源混淆在不同的目录下,所以这里是没办法找到该文件的,直接使用cc.loader的loadRes方法尝试加载一次。
if(content === ""){
cc.loader.loadRes(filename, cc.TextAsset, function (error, result) {
cc.log("error1=" + error + ",result = " + result + ",type=" + typeof result);
if (error) {
callback(Error("status " + error))
} else {
//callback(null, result);//creator1.9及以下版本使用此行
callback(null, result.text);//新版creator可放心运行
}
});
} else {
callback(content === "" ? Error(filename + " not exits") : null, content);
}
}
欢迎评论区留言
-
微信小程序pb5往上翻一点 ↩
网友评论