當(dāng)creator遇上protobufjs—起步

一. 環(huán)境準(zhǔn)備

我一直在探索cocos H5正確的開發(fā)姿勢咆蒿,目前做javascript項(xiàng)目已經(jīng)離不開 nodejs、npm或grunt等腳手架工具了。

1.初始化package.json文件

npm init

當(dāng)新建好cocos-js或creator項(xiàng)目,在項(xiàng)目根目錄使用npm init命令迟杂,一路回車,將在當(dāng)前目錄創(chuàng)建package.json文件用于nodejs三方模塊的管理本慕。關(guān)于npm的使用細(xì)節(jié)網(wǎng)絡(luò)上有很多教程排拷,在此不用細(xì)說。

2. protobufjs模塊


本人最早在cocos2dx 2.x時(shí)代就開始用protobufjs模塊來操縱protobuf一直到現(xiàn)在锅尘。所以下面所有內(nèi)容都是關(guān)于protobufjs在cocos creator中的使用监氢,包括原生平臺(tái)(cocos2d-js也是大同小異)。

安裝protobufjs到項(xiàng)目

npm install protobufjs@5 --save

使用npm install命令安裝模塊鉴象,注意我們這里使用的是protobufjs 5.x版本忙菠。 雖然protobufjs目前最新的 6.x版本,提供了ts纺弊、rpc等功能的支持牛欢,但接口變化太大,目前還不太會(huì)使用淆游。

安裝protobufjs到全局

npm install -g protobufjs@5

使用npm install -g 參數(shù)將模塊安裝到全局傍睹,目的主要是方便使用protobufjs提供的pbjs命令行工具隔盛。pbjs可以將proto原文件轉(zhuǎn)換成json、js等拾稳,以提供不同的加載proto的方式吮炕,我們可以根據(jù)自己的實(shí)際情況選擇使用。

二. protobufjs用法

下面是demo中定義的Player.proto文件的內(nèi)容

syntax = "proto3";
package grace.proto.msg;

message Player {
    uint32  id = 1;         //唯一ID  首次登錄時(shí)設(shè)置為0访得,由服務(wù)器分配
    string  name = 2;       //顯示名字
    uint64  enterTime = 3;  //登錄時(shí)間
}

關(guān)于proto具體語法細(xì)節(jié)這里就不多說了龙亲,我們重點(diǎn)如何將Player.proto文件中定義的Player對象在js中實(shí)例化、屬性賦值悍抑、序列化鳄炉、反序列化操作。

1. 靜態(tài)語言中使用proto文件

在c++/java這類靜態(tài)語言中使用protobuf通常是使用官方提供的protoc命令將proto文件編譯成c++/java代碼搜骡,像下面這樣:

protoc --cpp_out=輸出路徑 xxx.proto
protoc --java_out=輸出路徑 xxx.proto

將輸出路徑的文件導(dǎo)入對應(yīng)語言的工程中使用拂盯。

2. 在creator項(xiàng)目中使用proto文件

javascript是動(dòng)態(tài)語言,可以在運(yùn)行時(shí)產(chǎn)生對象记靡,因此protobufjs提供了更為便捷的動(dòng)態(tài)編譯谈竿,將proto文件中的對象生成js對象,下面簡要講解一下在creator中具體的使用步驟:

1.加載proto文件并編譯生成proto對象

//導(dǎo)入protobufjs模塊
let protobuf = require("protobufjs");
//獲取一個(gè)builder對象
let builder = protobuf.newBuilder();
//使用protobufjs加文件摸吠,并與一個(gè)builder對象關(guān)聯(lián)
protobuf.protoFromFile('xxx.proto', builder);
protobuf.protoFromFile('yyy.proto', builder);
...
let PB = builder.build('grace.proto.msg'); 

這步操作主要是使用protobufjs加載空凸、編譯proto文件。

2.實(shí)例化proto對象與屬性賦值

let PB = builder.build('grace.proto.msg')

build函數(shù)返回值PB對象中將包含的是在proto中定義所有message對象寸痢,現(xiàn)在已經(jīng)成為js對象劫恒,可以被實(shí)例化,代碼如下:

//實(shí)例化Player
let player = new PB.Player();  
//屬性賦值
player.name = '張三';             
player.enterTime = Date.now();

3.proto對象的序列化與反序列化

不說廢話轿腺,還是直接上代碼

...
//使用實(shí)例對象上的toArrayBuffer函數(shù)將對象序列化為二進(jìn)制數(shù)據(jù)
let data = player.toArrayBuffer();
//使用類型對象上的decode函數(shù)將二進(jìn)制數(shù)據(jù)反序列化為實(shí)例對象
let otherPlayer = PB.player.decode(data);

如果幸運(yùn)你可以在web上使用protobuf了, 為什么只是在web上呢丛楚,當(dāng)你把上面的代碼運(yùn)行在jsb環(huán)境下的時(shí)候族壳,你會(huì)體驗(yàn)到悲催的事情正在發(fā)生。

三. 拯救cocos-jsb上的protobufjs

為什么在原生上運(yùn)行就掛掉了呢趣些?要理解這個(gè)問題需要對nodejs\ 瀏覽器\cocos-jsb這三個(gè)javascript的運(yùn)行宿主環(huán)境有一定的了解仿荆。

我之前的文章提到過在選擇nodejs模塊時(shí),要注意是否同時(shí)支持nodejs和web坏平,只要是純js的模塊在cocos中一般都可以隨便用拢操,比如async、undersocre舶替、lodash等令境。
protobufjs這個(gè)模塊是可以很好的在瀏覽器和nodejs環(huán)境上運(yùn)行的。但運(yùn)行在cocos-jsb上就會(huì)出問題顾瞪,首先我們要定位到出問題的關(guān)鍵代碼:

protobuf.protoFromFile('xxx.proto', builder);

1. 問題分析

從protobuf.protoFromFile函數(shù)名上看就知道是要進(jìn)行文件的加載舔庶,一想到文件加載抛蚁,就涉及到文件操作的api,我們來整理一下不同平臺(tái)上的文件接口:

宿主平臺(tái) 文件接口 說明
瀏覽器 XMLHttpRequest 瀏覽器中動(dòng)態(tài)加載資源惕橙、文件等AJAX操作的基礎(chǔ)
nodejs fs.readFile / fs.readFileSync nodejs上的文件操作模塊瞧甩,底層由c/c++實(shí)現(xiàn)
cocos-jsb jsb.fileUtils.getStringFromFile cocos-js提供的讀取文件內(nèi)容接口,在不臺(tái)平臺(tái)(ios\android\windows)由不同底層api實(shí)現(xiàn)

看到這里相信很多人已經(jīng)明白為什么在cocos-jsb上會(huì)有問題了弥鹦,我們再來讀一下protobufjs源碼肚逸,證實(shí)下我們的分析。

2. 分析protobufjs源碼


找到protobufjs加載文件的主要代碼彬坏,下面我為源碼加上了注釋朦促,請認(rèn)真讀一下注釋內(nèi)容:

Util.fetch = function(path, callback) {
    //檢查callback參數(shù),callback參數(shù)決定是否為異步加載
    if (callback && typeof callback != 'function')
        callback = null;

    //運(yùn)行環(huán)境是否為nodejs
    if (Util.IS_NODE) {
        //加載nodejs的文件系統(tǒng)模塊
        var fs = require("fs");  
        //檢查是否有callback苍鲜,存在使用fs.readFile異步函數(shù)讀取文件內(nèi)容
        if (callback) {
            fs.readFile(path, function(err, data) {
                if (err)
                    callback(null);
                else
                    callback(""+data);
            });
        } else
            //使用fs.readFileSync同步函數(shù)讀取文件內(nèi)容 
            try {
                return fs.readFileSync(path);
            } catch (e) {
                return null;
            }
    } else {
        //當(dāng)不為nodejs運(yùn)行環(huán)境使用XmlHttpRequest加載文件
        var xhr = Util.XHR();
        //根據(jù)callbcak參數(shù)是否存在思灰,使用異步還是同步方式
        xhr.open('GET', path, callback ? true : false);
        // xhr.setRequestHeader('User-Agent', 'XMLHTTP/1.0');
        xhr.setRequestHeader('Accept', 'text/plain');
        if (typeof xhr.overrideMimeType === 'function') xhr.overrideMimeType('text/plain');
        //通過XmlHttpRequest.onreadystatechange事件函數(shù)異步獲取文件數(shù)據(jù)
        if (callback) {
            xhr.onreadystatechange = function() {
                if (xhr.readyState != 4) return;
                if (/* remote */ xhr.status == 200 || /* local */ (xhr.status == 0 && typeof xhr.responseText === 'string'))
                    callback(xhr.responseText);
                else
                    callback(null);
            };
            if (xhr.readyState == 4)
                return;
            //調(diào)用send方法發(fā)起AJAX請求
            xhr.send(null);
        } else {
            ////調(diào)用send方法發(fā)起AJAX請求,同步獲取文件數(shù)據(jù)
            xhr.send(null);
            if (/* remote */ xhr.status == 200 || /* local */ (xhr.status == 0 && typeof xhr.responseText === 'string'))
                return xhr.responseText;
            return null;
        }
    }
};

從上面的代碼可以看出protobufjs庫是為瀏覽器和nodejs準(zhǔn)備的混滔,根本就沒考慮過cocos-jsb的存在(吐槽:建議cocos官方提供的接口能模仿nodejs這樣能少很多事)洒疚,所以要在cocos-jsb中使用protobufjs其中的一個(gè)辦法就是修改protobufjs的源碼,如下:

Util.fetch = function(path, callback) {
    if (callback && typeof callback != 'function')
        callback = null;
    //將平臺(tái)檢查代碼改為cocos提供的接口
    if (cc.sys.isNative) {
        //文件讀取使用cocos-jsb提供的函數(shù)
        try {
            let data = jsb.fileUtils.getStringFromFile(path);
            cc.log(`proto文件內(nèi)容: {data}`);
            return data;
        } catch (e) {
            return null;
        }
    } else {
        //web端無需修改坯屿,略
        ...
};

我們用cocos的接口將代碼修改一下油湖,加載問題就被化解了,問題真的被解決了嗎领跛?
不好意思乏德,除了上面要代碼外還有一處代碼需要修改,源碼如下:

BuilderPrototype["import"] = function(json, filename) {
    var delim = '/';

    // Make sure to skip duplicate imports

    if (typeof filename === 'string') {
        //這里又出現(xiàn)了平臺(tái)檢查
        if (ProtoBuf.Util.IS_NODE)
            // require("path")是加載nodejs的path模塊吠昭,resolve
            filename = require("path")['resolve'](filename);
        if (this.files[filename] === true)
            return this.reset();
        this.files[filename] = true;

    } else if (typeof filename === 'object') { // Object with root, file.

        var root = filename.root;
        //這里還要修改
        if (ProtoBuf.Util.IS_NODE)
            root = require("path")['resolve'](root);
        if (root.indexOf("\\") >= 0 || filename.file.indexOf("\\") >= 0)
            delim = '\\';
        var fname;
         //這里還要修改
        if (ProtoBuf.Util.IS_NODE)
            fname = require("path")['join'](root, filename.file);
        else
            fname = root + delim + filename.file;
        if (this.files[fname] === true)
            return this.reset();
        this.files[fname] = true;
    }
    ...
}

這里我就不再貼修改代碼了喊括,大家自行解決。

四 為protobuf繼續(xù)填坑

本來寫到這里矢棚,問題大多已經(jīng)解決了郑什, 但此時(shí),如果你滿懷信心地使用改造后的protobufjs源碼蒲肋,將你的代碼運(yùn)行起來那一刻蘑拯,我相信絕大多數(shù)人會(huì)一臉蒙逼。



媽的根本就不行6嫡场申窘!看了好多字,好不容易讀到這里孔轴,不僅在模擬器上跑不起來剃法,在web上同樣也跑不起來。

怎么辦路鹰,為了徹底解決問題玄窝,我還得繼續(xù)寫下去牵寺。

1. 了解creator動(dòng)態(tài)加載資源的方法

請大家思考一個(gè)問題,creator項(xiàng)目中的一張圖片恩脂,在web與cocos-jsb上他們的文件路徑會(huì)一樣嗎帽氓?直接使用protobuf.protoFromFile('xxx.proto')去加載一個(gè)proto文件會(huì)成功嗎?
cocos文檔中說過要?jiǎng)討B(tài)加載一個(gè)圖片資源需要將文件存放在assets/resources目錄下俩块,使用如下方法加載:

cc.loader.loadRes('resources/xxx')

嘗試將proto文件存放在resources/pb/目錄下黎休,用使用以下代碼:

protobuf.protoFromFile('resources/pb/xxx.proto')

同樣會(huì)得到失敗的提示,該如何辦呢玉凯?怎么才能獲得正確的資源路徑势腮?
算了,不買關(guān)子了漫仆,寫累了直接出答案吧捎拯!

protobuf.protoFromFile(cc.url.raw('resources/pb/xxx.proto'));

cc.url.raw這個(gè)函數(shù)在瀏覽器、模擬器盲厌、手機(jī)上會(huì)返回不同的資源路徑署照,這才是真正的資源路徑,這下代碼應(yīng)該可以正常運(yùn)行起來了吗浩。

2. 更好的解決法辦

我一直在探索cocos H5正確的開發(fā)方式建芙,雖然通過修改protobufjs源碼的方法可以來解決在cocos-jsb上運(yùn)行的問題,但這并不是唯一的解決方案懂扼。

我這里編寫了一個(gè)creator + protobufjs的demo沒有使用上述方案禁荸,地址如下:

https://github.com/ShawnZhang2015/grace

如何在不修改protobufjs源碼的情況下讓代碼運(yùn)行起來,以及使用pbjs工具預(yù)編譯proto文件為JSON和js文件的用法阀湿,請繼續(xù)觀注我的系列文章《探索cocosH5正確的開發(fā)姿勢》赶熟!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市陷嘴,隨后出現(xiàn)的幾起案子钧大,更是在濱河造成了極大的恐慌,老刑警劉巖罩旋,帶你破解...
    沈念sama閱讀 207,248評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異眶诈,居然都是意外死亡涨醋,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,681評論 2 381
  • 文/潘曉璐 我一進(jìn)店門逝撬,熙熙樓的掌柜王于貴愁眉苦臉地迎上來浴骂,“玉大人,你說我怎么就攤上這事宪潮∷菥” “怎么了趣苏?”我有些...
    開封第一講書人閱讀 153,443評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長梯轻。 經(jīng)常有香客問我食磕,道長,這世上最難降的妖魔是什么喳挑? 我笑而不...
    開封第一講書人閱讀 55,475評論 1 279
  • 正文 為了忘掉前任彬伦,我火速辦了婚禮,結(jié)果婚禮上伊诵,老公的妹妹穿的比我還像新娘单绑。我一直安慰自己,他們只是感情好曹宴,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,458評論 5 374
  • 文/花漫 我一把揭開白布搂橙。 她就那樣靜靜地躺著,像睡著了一般笛坦。 火紅的嫁衣襯著肌膚如雪区转。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,185評論 1 284
  • 那天弯屈,我揣著相機(jī)與錄音蜗帜,去河邊找鬼。 笑死资厉,一個(gè)胖子當(dāng)著我的面吹牛厅缺,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播宴偿,決...
    沈念sama閱讀 38,451評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼湘捎,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了窄刘?” 一聲冷哼從身側(cè)響起窥妇,我...
    開封第一講書人閱讀 37,112評論 0 261
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎娩践,沒想到半個(gè)月后活翩,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,609評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡翻伺,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,083評論 2 325
  • 正文 我和宋清朗相戀三年材泄,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片吨岭。...
    茶點(diǎn)故事閱讀 38,163評論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡拉宗,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情旦事,我是刑警寧澤魁巩,帶...
    沈念sama閱讀 33,803評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站姐浮,受9級(jí)特大地震影響谷遂,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜单料,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,357評論 3 307
  • 文/蒙蒙 一埋凯、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧扫尖,春花似錦白对、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,357評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至沉颂,卻和暖如春条摸,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背铸屉。 一陣腳步聲響...
    開封第一講書人閱讀 31,590評論 1 261
  • 我被黑心中介騙來泰國打工钉蒲, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人彻坛。 一個(gè)月前我還...
    沈念sama閱讀 45,636評論 2 355
  • 正文 我出身青樓顷啼,卻偏偏與公主長得像,于是被迫代替她去往敵國和親昌屉。 傳聞我的和親對象是個(gè)殘疾皇子钙蒙,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,925評論 2 344

推薦閱讀更多精彩內(nèi)容