cocos creator protobuf實踐

當creator遇上protobufjs—起步 ----------- https://www.jianshu.com/p/c4b8a8e3077f
使用pomelo例子 https://github.com/eddy2015/ccc-pomelo-chat-client

https://blog.csdn.net/6346289/article/details/76474537 – 不錯文檔

首先要說的是,我所使用的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);
                    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);
                        }
                    });
                    //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);
        }

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);

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章