当creator遇上protobufjs—青春升级

pbkiller1.0已经上线Cocos商店,支持了微信小游戏环境,我录制了一段小视频,演示pbkiller的使用流程和方法。


pbkiller在微信小游戏中的使用

在「奎特尔星球」除了介绍插件、工具以外,更重要的是将这些插件、工具的实现原理和方法分享给大家,共同学习一起进步。

我曾在公众号上发过一篇《微信小游戏protobuf.js快速解决办法》,在这里给大家说声不好意思,这篇文章中的proto加载方案存在缺陷,具体问题如下图所示:

当a.proto文件中import了b.proto文件,在成功加载a.proto文件后protobufjs内部在解析a.proto时会自动加载b.proto,此时会触发XMLHttpRequest API的调用,导致在微信小游戏环境出现错误。

一、protobuf.js加载源码分析

还是从protobuf.js源码入手,我增加了一些注释,方便理解:

ProtoBuf.loadProtoFile = function(filename, callback, builder) {
    //参数解析,检查callback参数是否有效
    if (callback && typeof callback === 'object') 
        builder = callback,
        callback = null;
    else if (!callback || typeof callback !== 'function')
        callback = null;
    //callback存在,使用异步加载
    if (callback)
        //使用ProtoBuf.Util.fetch函数异步加载,
        //注意这里的写法很不爽,调用fetch函数后立即return了
        return ProtoBuf.Util.fetch(typeof filename === 'string' ? filename : filename["root"]+"/"+filename["file"], function(contents) {
            if (contents === null) {
                callback(Error("Failed to fetch file"));
                return;
            }
            try {
                //加载成功,调用ProtoBuf.loadProto函数解析contents变量,转换为proto对象,通过callback函数返回
                callback(null, ProtoBuf.loadProto(contents, builder, filename));
            } catch (e) {
                callback(e);
            }
        });

    //callbcak不存在,使用同步方式,
    //通过ProtoBuf.Util.fetch的返回值,获取文件数据
    var contents = ProtoBuf.Util.fetch(typeof filename === 'object' ? filename["root"]+"/"+filename["file"] : filename);
    //加载成功,调用ProtoBuf.loadProto函数解析contents变量,转换为proto对象,通过return返回
    return contents === null ? null : ProtoBuf.loadProto(contents, builder, filename);
};

从源码中可以看出,protobufjs有两种加载模式:同步与异步。

在《当creator遇上protobufjs|相遇》 一文中我们分析过ProtoBuf.Util.fetch函数,这里简单回顾一下:

浏览器:使用XMLHttpRequest实现的同步、异步的proto文件加载。

nodejs:异步使用fs.readFile,同步使用fs.readFileSync

Cocos-JSB:我们介绍了伪装fs模块的办法调用jsb.fileUtils.getStringFromFile来解决。

微信小游戏环境我的理解是:阉割+定制过的浏览器,它没有提供XMLHttpRequest API,这是导致protobuf.js失败的原因。

后来我又尝试了在protobufjs 6.x中使用的方案,在ProtoBuf.loadProtoFile函数,使用cc.loader.load代替ProtoBuf.Util.fetch,采用异步加载的方式,同样存在存问题。

在遇到问题时,以个人的能力不能很好的解决时,去逛一逛论坛是一个不错的想法。当我把问题一提出,第二天就有一位ID叫a1990091的热心朋友提供了一个思路:重写ProtoBuf.Util.fetch函数,在函数中检查当前是否为微信小游戏环境,然后可以利用微信提供的api去实现加载:

此方法做的非常的漂亮,分别检测了JSB\微信\Web环境,提供不同的加载实现。可对我来说,的遗憾是pbkiller库对外一直提供的是同步加载方法,改为异步加载,对已经使用pbkiller用户不太友好,同步、异步如取舍呢?

二、救命稻草cc.loader

发完帖从论坛回到问题上,不能解决估计是睡不着了,头脑中一阵自言自语言,忽然想到cc.loader.getRes同步获取资源的接口与ProtoBuf.Util.fetch的同步方式一样,能否从这里下手呢?

在这里先简单介绍一下cc.loader下的系列load函数。

1. cc.loader.load(url, callback)

cc.loader.load的url参数是从项目发布的根路径开始的完整路径,因此需要借助cc.url.raw函数来获取完整路径。

例如:加载文件assets/resources/a.json

cc.loader.load(cc.url.raw('resources/a.json'), (error, json) => {
    cc.load(json);
});

cc.loader.load除了可以加载当前项目资源,更重要的能力是加载其它远程服务器上的资源。只需要给出完整路径即可,但在浏览器上使用需要注意跨域问题。

加载当前项目下resources目录下的资源,使用cc.loader.loadRes更为简单。

更多用法请参考API文档:

http://docs.cocos.com/creator/api/zh/classes/loader.html#load

2. cc.loader.loadRes(url, callback)

cc.loader.loadRes的url参数路径是以resources为根路径。

例如:加载文件assets/resources/a.json

cc.loader.loadRes('a.json', (error, json) => {
    cc.load(json);
});

cc.loader.loadRes的用法比cc.loader.load简单很多,也有没那么多参数重载的用法,API文档链接:http://docs.cocos.com/creator/api/zh/classes/loader.html#loadres

3. cc.loader.loadResDir(url, callback)

cc.loader.loadResDir顾名思义它是加载一个目录(及子目录),url同样以assets/resources目录作为根路径。

例如:加载文件 assets/resources/json目录下有a.json、b.json两个文件

cc.loader.loadResDir('json', (error, array) => {
     //array中包含a.json和b.json的内容
    cc.log(array);
});

4. cc.loader.getRes(url)

cc.loader.getRes是cc.loader家族中唯一的同步资源获取函数。但是它有一个前提,需要被cc.loader.loadXXX加载成功过的资源才能使用,不然它会返回null。

例如:加载文件 assets/resources/json/a.json

//jsonA为null
let jsonA = cc.loader.getRes('json/a.json');
cc.loader.loadResDir('json', (error) => {
    //此时获取jsonB才有效    
    let jsonB = cc.loader.getRes('json/b.json');    
});

这里分享一个查看cc.loader缓存资源的一个方法,在浏览器中运行你的项目,在调试控制台上输入:cc.loader._catch,你会看到如下内容:

cc.loader._catch对象中的所有资源,都可以使用cc.loader.getRes获取。讲到此处,我猜你已经大概知道怎么使用cc.loader.getRes解决微信小游戏中proto的加载问题了。

三、cc.loader.getRes移花接木

从分析cc.loader的系列加载函数,cc.loader.getRes去代替ProtoBuf.Util.fetch,同样使用同步方式,这样pbkiller.loadAll/ pbkiller.loadFromFile的接口用法可以保持不变。

要想cc.loader.getRes的返回值有效,需要预先将资源加载到cc.loader的缓存中,因此提供了一个pbkiller.preload函数

let ProtoBuf = require('protobufjs');
preload(cb) {
    //运行时动态修改ProtoBuf.Util.fetch为cc.loader.getRes    
    ProtoBuf.Util.fetch = cc.loader.getRes.bind(cc.loader); 
    //使用cc.loader.loadResDir加载resources/pb目录所有文件    
    cc.loader.loadResDir('pb', (error, data) => { 
        //通知调用都,预加载完毕
        cb();
    }); 
}

简单几行代码解决了所有问题,而且没有修改protobuf.js任何一行源代码。再看下如何使用预加载函数:

/预先加载proto文件到引擎缓存
pbkiller.preload(() => { 
  //加载所有proto文件并动态生成proto对象
  let pb = pbkiller.loadAll();
  //实例化proto对象
  let player = new pb.grace.proto.msg.Player();
  ...
});

在实际项目中可以提前执行pbkiller.preload,以前所有的pbkiller的用法保持不变,利用javascript的动态属性赋值,特别是可以修改函数指针,基本上可以做到为所欲为,而且不需要修改源代码,有没有觉得特别爽呢?

四、结语

pbkiller的内核是protobuf.js,我所做的工作只是将protobuf.js适配到Cocos-JSB和微信小游戏环境,让其能正常工作。希望我的经验能对你有所帮助,愿pbkiller能为你节省时间,提高效率!

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

推荐阅读更多精彩内容