Cocos Creator 中使用 protobufjs
為了在CocosCreator 中使用 protobuf,NRatel走了不少彎路。
一、安裝
前置條件,安裝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.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;