Cocos Creator 中使用 protobufjs

原帖:https://blog.csdn.net/NRatel/article/details/84251138

为了在CocosCreator 中使用 protobuf

protobufjs开源地址

一、安装

前置条件,安装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的经验。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,311评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,339评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,671评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,252评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,253评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,031评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,340评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,973评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,466评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,937评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,039评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,701评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,254评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,259评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,485评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,497评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,786评论 2 345

推荐阅读更多精彩内容