Cocos Creator 使用 protobuf

  很早之前就听说过protobuf,对此协议的评价也一直很高。但是之前接触到的项目一直没有使用这个协议,都是直接跟后端约定好数据包格式(协议号+数据包大小),然后将对应的数据结构体转换为数据字节流进行数据的传输。最近接触的一个新项目,使用到了protobuf协议,项目前端使用的是cocos2dx-lua,正好前段时间在用cocos creator进行项目开发,所以本着在开发中学习的想法,用cocos creator再简单的将项目实现一边,主要是加入protobuf协议。

  在搜索引擎中搜索 cocos creator + protobuf ,得到的资料都比较旧,而且我试着参考网上给到的资料,一直失败,所以还是要根据creator、protobuf的版本来做一定的调整才行。

  本文中使用到的环境是:cocos creator 2.0.10版本,protobuf 6.8.6版本,下载地址

  1、

  下载好protobuf之后,导入到creator中,在提示是否导入为插件的时候选择是,然后再将允许编辑器加载勾上,不然在编辑过程中一直会提示 protobuf 未定义之类的错误:

  2、

  将protobuf导入到编辑器之后,就可以参考官方的例子,来编写代码了,首先看一下官方给的proto文件:

// awesome.proto
package awesomepackage;
syntax = "proto3";

message AwesomeMessage {
    string awesome_field = 1; // becomes awesomeField
}

  具体的protobuf语法,可以回头再去了解一下。这里有几个关键信息后续会用到,包名(awesomepackage)、消息名称(AwesomeMessage)。将proto文件放到resource目录(这个目录是creator规定用于加载动态导入资源的目录),之前lua这边是将.proto文件转成了.pb文件(具体为什么要转,.pb和.proto有什么不同,还没了解清楚),所以一开始我也是直接去加载.pb文件,一直加载不了,后来才发现网上的教程都是加载的.proto文件。

  官方给的例子,加载方式是这样的:

protobuf.load("awesome.proto", function(err, root) {
    if (err)
        throw err;

    // Obtain a message type
    var AwesomeMessage = root.lookupType("awesomepackage.AwesomeMessage");
}

  直接运行的话,会报错,类似: return callback(Error("status " + xhr.status)); ,参考 这篇文章 ,修改protobuf.js,搜索 function fetch 修改一下:

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

    // 判断是否是cocos项目
    if (typeof cc !== "undefined"){
        if (cc.sys.isNative){
            var content = jsb.fileUtils.getStringFromFile(filename)
            callback(content === "" ? Error(filename + " not exits") : null, content)
        }
        else{
            cc.loader.loadRes(filename, cc.TextAsset, function (error, result) {
                if (error) {
                    callback(Error("status " + error))
                } else {
                    callback(null, result);
                }
            })
        }
        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)
    ...
}

  主要就是那个判断是否cocos环境的地方。

  我参考这个方式,试着加载了一下,root有值,但是按照这个例子,root.lookupType 返回的是null,后来参考论坛的教程,改了一下加载方式,直接使用cocos提供的加载资源的api加载:

cc.loader.loadRes(pbFiles, cc.TextAsset, function (err, protos) {
    if (err) {
        bg.Log.e("load proto error ==> ", err)
        return
    }
    let pr = protobuf.parse(tex)
    bg.Log.i(pr)
    let rs = pr.root.lookupType("awesomepackage.AwesomeMessage")
    bg.Log.i(rs)

    let payLoad = { awesome_field:"hello   sdfsefewg"}
    let msg = rs.create(payLoad)
    bg.Log.i("msg ", msg)
    let buf = rs.encode(msg).finish()
    bg.Log.i("buf ", buf)
    let decode = rs.decode(buf)
    bg.Log.i("decode ", JSON.stringify(decode))
})    

  使用这种方案,可以正确的加载到,打印出来的值都是正常的。

  如果有多个.proto文件,则可以用cc.loader.loadResArray批量加载,在加载结果里面,将protobuf.parse解析的结果,以文件名为key存储起来:

for (let proto of protos) {
    window.protoMessageMap[proto._name] = protobuf.parse(proto)
}

  在多.proto文件下,发、收消息的时候,使用哪个协议,我这里使用的是一个笨方法:

// 发送消息
let protocal = "包名.messageName"
for (let k in protoMessageMap) {
    let obj = protoMessageMap[k]
    let found = obj.root.lookup(protocal)
    if (null != found) {
        let bodyMsg = found.create(data)
        let body = found.encode(bodyMsg).finish()   
break } }
// 接收消息 let protocal = "包名.messageName" for (let k in protoMessageMap) { let obj = protoMessageMap[k] let found = obj.root.lookup(protocal) if (null != found) { let msg = found.decode(pbmsg.body) break } }

  遍历一边所有加载进来的 .proto,然后再做后续操作。这个地方有个问题就是,如果消息名重复了,就会有问题。后期再看看有没有什么解决方案吧。

  另外,creator用websocket进行消息通讯时,接收到的数据包用protobu解包,需要将数据包转一下格式:

found.decode(new Uint8Array(event.data))

  不然protobuf会报错。

posted @ 2019-06-18 15:53  Le Ciel  阅读(5113)  评论(0编辑  收藏  举报