美文网首页protobuf我爱编程
cocos creator protobuf实践(含微信小程序解

cocos creator protobuf实践(含微信小程序解

作者: 甚解_4703 | 来源:发表于2018-04-15 22:11 被阅读0次

    如果想要直接看微信小程序的解决方案-角标任意门[1]

    首先要说的是,我所使用的creator版本是:


    creator 版本

    在写这篇博客之前,我也曾做了很多搜索,也搜出一些有价值的东西,只不过很多东西都已经过时了,,或者说我们现在有更好的方式去实现我们的功能。

    废话不多说,让我们直接开始吧。

    1

    新建一个Hello World项目,命名为CreatorTest。
    先放一边。

    2

    前往protobufjs开源项目库,我们去下一个最新的release版本。为什么用它?自然是有人一直在更新维护呀,而且用起来还不错!下载地址

    下载protobufjs
    下载完成后,解压,打开它,我们找到这个文件protobuf.js
    protobuf.js

    3

    打开creator,拖动protobuf.js到项目中,

    拖动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目录如果没有就自己创建一个,如下图

    test1.proto文件位置

    修改我们的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的整个流程:

    1. 加载proto文件
    2. 创建message
    3. encode
    4. 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 README.md
    如果要使用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);
        }
    }
    

    欢迎评论区留言


    1. 微信小程序pb5往上翻一点

    相关文章

      网友评论

        本文标题:cocos creator protobuf实践(含微信小程序解

        本文链接:https://www.haomeiwen.com/subject/fbfjkftx.html