CocosCreator 中使用 protobufjs

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/Blog2015/article/details/82423350

  为了在CocosCreator 中使用 protobuf,当真走了不少弯路。

  protobufjs开源地址

  一、安装 

  前置条件,安装Node.js 、npm。

  查看候选版本:

npm view protobufjs versions

  新建一个项目目录,用来转换.proto为.js。执行 npm init -y 初始化项目。

  选择需要的版本安装,(这里用的是6.8.8版):

npm install --save-dev [email protected]

  执行后,将出现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.jsbytebuffer.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;

 本文原文地址:https://blog.csdn.net/Blog2015/article/details/82423350

猜你喜欢

转载自blog.csdn.net/Blog2015/article/details/82423350