在cocos creator中使用protobufjs(一)

在cocos creator中使用protobufjs(一),第1张

概述一. 环境准备 我一直在探索cocos H5正确的开发姿势,目前做javascript项目已经离不开 nodejs、npm或grunt等脚手架工具了。 1.初始化package.json文件 npm init 当新建好cocos-js或creator项目,在项目根目录使用npm init命令,一路回车,将在当前目录创建package.json文件用于nodejs三方模块的管理。关于npm的使用细节 一. 环境准备

我一直在探索cocos H5正确的开发姿势,目前做JavaScript项目已经离不开 nodeJs、npm或grunt等脚手架工具了。

1.初始化package.Json文件

npm init

当新建好cocos-Js或creator项目,在项目根目录使用npm init命令,一路回车,将在当前目录创建package.Json文件用于nodeJs三方模块的管理。关于npm的使用细节网络上有很多教程,在此不用细说。

2. protobufJs模块


本人最早在cocos2dx 2.x时代就开始用protobufjs模块来 *** 纵protobuf一直到现在。所以下面所有内容都是关于protobufJs在cocos creator中的使用,包括原生平台(cocos2d-Js也是大同小异)。

安装protobufJs到项目

npm install protobufJs@5 –save

使用npm install命令安装模块,注意我们这里使用的是protobufJs 5.x版本。 虽然protobufJs目前最新的 6.x版本,提供了ts、rpc等功能的支持,但接口变化太大,目前还不太会使用。

安装protobufJs到全局

npm install -g protobufJs@5

使用npm install -g 参数将模块安装到全局,目的主要是方便使用protobufJs提供的pbJs命令行工具。pbJs可以将proto原文件转换成Json、Js等,以提供不同的加载proto的方式,我们可以根据自己的实际情况选择使用。

二. protobufJs用法

下面是demo中定义的Player.proto文件的内容

Syntax = "proto3";package grace.proto.msg;message Player {    uint32  ID = 1;         //唯一ID 首次登录时设置为0,由服务器分配    string  name = 2;       //显示名字    uint64  enterTime = 3;  //登录时间}

关于proto具体语法细节这里就不多说了,我们重点如何将Player.proto文件中定义的Player对象在Js中实例化、属性赋值、序列化、反序列化 *** 作。

1. 静态语言中使用proto文件

在c++/java这类静态语言中使用protobuf通常是使用官方提供的protoc命令将proto文件编译成c++/java代码,像下面这样:

protoc –cpp_out=输出路径 xxx.proto
protoc –java_out=输出路径 xxx.proto

将输出路径的文件导入对应语言的工程中使用。

2. 在creator项目中使用proto文件

JavaScript是动态语言,可以在运行时产生对象,因此protobufJs提供了更为便捷的动态编译,将proto文件中的对象生成Js对象,下面简要讲解一下在creator中具体的使用步骤:

1.加载proto文件并编译生成proto对象
//导入protobufJs模块let protobuf = require("protobufJs");//获取一个builder对象let builder = protobuf.newBuilder();//使用protobufJs加文件,并与一个builder对象关联protobuf.protoFromfile('xxx.proto',builder);protobuf.protoFromfile('yyy.proto',builder);...let PB = builder.build('grace.proto.msg');

这步 *** 作主要是使用protobufJs加载、编译proto文件。

2.实例化proto对象与属性赋值
let PB = builder.build('grace.proto.msg')

build函数返回值PB对象中将包含的是在proto中定义所有message对象,现在已经成为Js对象,可以被实例化,代码如下:

//实例化Playerlet player = new PB.Player();  //属性赋值player.name = '张三';             player.enterTime = Date.Now();
3.proto对象的序列化与反序列化

不说废话,还是直接上代码

...//使用实例对象上的toArrayBuffer函数将对象序列化为二进制数据let data = player.toArrayBuffer();//使用类型对象上的decode函数将二进制数据反序列化为实例对象let otherPlayer = PB.player.decode(data);

如果幸运你可以在web上使用protobuf了, 为什么只是在web上呢,当你把上面的代码运行在Jsb环境下的时候,你会体验到悲催的事情正在发生。

三. 拯救cocos-Jsb上的protobufJs

为什么在原生上运行就挂掉了呢?要理解这个问题需要对nodeJs\ 浏览器\cocos-Jsb这三个JavaScript的运行宿主环境有一定的了解。

我之前的文章提到过在选择nodeJs模块时,要注意是否同时支持nodeJs和web,只要是纯Js的模块在cocos中一般都可以随便用,比如async、undersocre、lodash等。
protobufJs这个模块是可以很好的在浏览器和nodeJs环境上运行的。但运行在cocos-Jsb上就会出问题,首先我们要定位到出问题的关键代码:

protobuf.protoFromfile('xxx.proto',builder);
1. 问题分析

从protobuf.protoFromfile函数名上看就知道是要进行文件的加载,一想到文件加载,就涉及到文件 *** 作的API,我们来整理一下不同平台上的文件接口:

宿主平台 文件接口 说明
浏览器 XMLhttpRequest 浏览器中动态加载资源、文件等AJAX *** 作的基础
nodeJs fs.readfile / fs.readfileSync nodeJs上的文件 *** 作模块,底层由c/c++实现
cocos-Jsb Jsb.fileUtils.getStringFromfile cocos-Js提供的读取文件内容接口,在不台平台(ios\androID\windows)由不同底层API实现

看到这里相信很多人已经明白为什么在cocos-Jsb上会有问题了,我们再来读一下protobufJs源码,证实下我们的分析。

2. 分析protobufJs源码


找到protobufJs加载文件的主要代码,下面我为源码加上了注释,请认真读一下注释内容:

Util.fetch = function(path,callback) {    //检查callback参数,callback参数决定是否为异步加载    if (callback && typeof callback != 'function')        callback = null;    //运行环境是否为nodeJs    if (Util.IS_NODE) {        //加载nodeJs的文件系统模块        var fs = require("fs");          //检查是否有callback,存在使用fs.readfile异步函数读取文件内容        if (callback) {            fs.readfile(path,function(err,data) {                if (err)                    callback(null);                else                    callback(""+data);            });        } else            //使用fs.readfileSync同步函数读取文件内容             try {                return fs.readfileSync(path);            } catch (e) {                return null;            }    } else {        //当不为nodeJs运行环境使用XmlhttpRequest加载文件        var xhr = Util.XHR();        //根据callbcak参数是否存在,使用异步还是同步方式        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事件函数异步获取文件数据        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;            //调用send方法发起AJAX请求            xhr.send(null);        } else {            ////调用send方法发起AJAX请求,同步获取文件数据            xhr.send(null);            if (/* remote */ xhr.status == 200 || /* local */ (xhr.status == 0 && typeof xhr.responseText === 'string'))                return xhr.responseText;            return null;        }    }};

从上面的代码可以看出protobufJs库是为浏览器和nodeJs准备的,根本就没考虑过cocos-Jsb的存在(吐槽:建议cocos官方提供的接口能模仿nodeJs这样能少很多事),所以要在cocos-Jsb中使用protobufJs***其中的一个办法*就是修改protobufJs的源码,如下:

Util.fetch = function(path,callback) {    if (callback && typeof callback != 'function')        callback = null;    //将平台检查代码改为cocos提供的接口    if (cc.sys.isNative) {        //文件读取使用cocos-Jsb提供的函数        try {            let data = Jsb.fileUtils.getStringFromfile(path);            cc.log(`proto文件内容: {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') {        //这里又出现了平台检查        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继续填坑

本来写到这里,问题大多已经解决了, 但此时,如果你满怀信心地使用改造后的protobufJs源码,将你的代码运行起来那一刻,我相信绝大多数人会一脸蒙逼。

妈的根本就不行!!看了好多字,好不容易读到这里,不仅在模拟器上跑不起来,在web上同样也跑不起来。

怎么办,为了彻底解决问题,我还得继续写下去。

1. 了解creator动态加载资源的方法

请大家思考一个问题,creator项目中的一张图片,在web与cocos-Jsb上他们的文件路径会一样吗?直接使用protobuf.protoFromfile(‘xxx.proto’)去加载一个proto文件会成功吗?
cocos文档中说过要动态加载一个图片资源需要将文件存放在assets/resources目录下,使用如下方法加载:

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

尝试将proto文件存放在resources/pb/目录下,用使用以下代码:

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

同样会得到失败的提示,该如何办呢?怎么才能获得正确的资源路径?
算了,不买关子了,写累了直接出答案吧!

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

cc.url.raw这个函数在浏览器、模拟器、手机上会返回不同的资源路径,这才是真正的资源路径,这下代码应该可以正常运行起来了。

2. 更好的解决法办

我一直在探索cocos H5正确的开发方式,虽然通过修改protobufJs源码的方法可以来解决在cocos-Jsb上运行的问题,但这并不是唯一的解决方案。

我这里编写了一个creator + protobufJs的demo没有使用上述方案,地址如下:

https://github.com/ShawnZhang2015/grace

如何在不修改protobufJs源码的情况下让代码运行起来,以及使用pbJs工具预编译proto文件为JsON和Js文件的用法,请继续观注我的系列文章《探索cocosH5正确的开发姿势》!

总结

以上是内存溢出为你收集整理的在cocos creator中使用protobufjs(一)全部内容,希望文章能够帮你解决在cocos creator中使用protobufjs(一)所遇到的程序开发问题。

如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/web/1084418.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-27
下一篇 2022-05-27

发表评论

登录后才能评论

评论列表(0条)

保存