I. Environment preparation
I’ve been exploring the proper development posture for Cocos H5, and javascript projects now rely on scaffolding tools like NodeJS, NPM, or Grunt.
1. Initialize the package.json file
npm init
When the CocoS-JS or Creator project is created, run the NPM init command in the root directory of the project and press Enter to create a package.json file in the current directory for nodeJS tripartite module management. There are plenty of tutorials on the web about how to use NPM, but I won’t go into them here.
2. Protobufjs module
I’ve been using protobufJS to manipulate Protobuf since cocos2dx 2.x. So everything below is about the use of ProtobufJS in Cocos Creator, including native platforms (cocos2D-JS is much the same).
Install ProtobufJS into the project
npm install protobufjs@5 –save
Use the NPM install command to install the module, note that we are using the Protobufjs 5.x version here. The latest 6.x version of ProtobufJS provides support for TS, RPC and other functions, but there is a problem that proto files cannot be dynamically loaded in wechat mini games.
Install Protobufjs globally
npm install -g protobufjs@5
Use the NPM install -g parameter to install the module globally, mainly for the convenience of using the PBJS command line tool provided by Protobufjs. PBJS can convert proTO original files into JSON, JS, etc., to provide different ways to load ProTO, we can choose to use according to their actual situation.
Protobufjs usage
Here is the contents of the player.proto file defined in demo
syntax = "proto3"; package grace.proto.msg; message Player { uint32 id = 1; // Unique ID Is set to 0 at first login and assigned by the server. String name = 2; // Display name uint64 enterTime = 3; // Login time}Copy the code
The syntax details of proto are not discussed here. We will focus on how to instantiate, assign, serialize, deserialize Player objects defined in player. proto file in JS.
1. Use proto files in static languages
Using protobuf in static languages such as c++/ Java usually involves compiling proto files into c++/ Java code using the officially supplied protoc commands, as follows:
Protoc –cpp_out= output path xxx.proto protoc –java_out= output path xxx.proto
Import the output path file into the corresponding language project for use.
2. Use the proto file in the Creator project
Javascript is a dynamic language, which can generate objects at runtime. Therefore, ProtobufJS provides a more convenient dynamic compilation, which generates JS objects from objects in proto files. The following is a brief description of the specific use steps in Creator:
1. Load the PROTO file and compile the ProTO object
// Import the protobufjs module
let protobuf = require("protobufjs");
// Get a Builder object
let builder = protobuf.newBuilder();
// Use protobufjs plus a file and associate it with a Builder object
protobuf.protoFromFile('xxx.proto', builder);
protobuf.protoFromFile('yyy.proto', builder); . let PB = builder.build('grace.proto.msg');
Copy the code
This step mainly uses protobufjs to load and compile proto files.
2. Instantiate the Proto object and attribute assignment
let PB = builder.build('grace.proto.msg')
Copy the code
The PB object will contain all message objects defined in proto, which are now js objects and can be instantiated as follows:
// instantiate Playerletplayer = new PB.Player(); // Assign player.name ='Joe';
player.enterTime = Date.now();
Copy the code
3. Serialization and deserialization of proto objects
Cut the crap. Let’s get right to the code
. // Use the toArrayBuffer function on the instance object to serialize the object to binary dataletdata = player.toArrayBuffer(); // Deserialize binary data into instance objects using decode functions on type objectslet otherPlayer = PB.player.decode(data);
Copy the code
If you are lucky enough to be able to use Protobuf on the Web, why only on the Web, when you run the above code in JSB environment, you will experience sad things happening.
Save the protobufjs on COcos – JSB
Why does running on native fail? To understand this problem, you need to know a little bit about the context in which nodejs, browser, cocos-jsb run javascript.
As MENTIONED in my previous article, when choosing a NodeJS module, be aware of whether it supports both NodeJS and Web. As long as the module is pure JS, it can be used freely in COcos, such as Async, Undersocre, Lodash, etc. The Protobufjs module runs well in browsers and nodeJS environments. Running on cocos-Jsb will cause a problem. First we need to locate the key code that is causing the problem:
protobuf.protoFromFile('xxx.proto', builder);
Copy the code
1. Problem analysis
The protobuf. ProtoFromFile function is used for file loading, and the API for file loading is used for file loading.
The host platform | File interface | instructions |
---|---|---|
The browser | XMLHttpRequest | The basis for AJAX operations such as dynamically loading resources and files in browsers |
nodejs | fs.readFile / fs.readFileSync | Nodejs file manipulation module, the bottom by C/C ++ implementation |
cocos-jsb | jsb.fileUtils.getStringFromFile | Cocos-js provides the interface to read file contents, which is implemented by different underlying apis on different platforms (ios/Android/Windows) |
Now that I’m sure many of you understand why there are problems with cocos-Jsb, let’s read the protobufJS source code again to verify our analysis.
2. Analyze protobufJS source code
Util.fetch = function(path, callback) {
// Check the callback argument, which determines whether the load is asynchronous
if (callback && typeofcallback ! ='function')
callback = null;
// Whether the running environment is nodejs
if (Util.IS_NODE) {
// Load the nodejs file system module
var fs = require("fs");
// check if there is a callback, use the fs.readFile asynchronous function to readFile contents
if (callback) {
fs.readFile(path, function(err, data) {
if (err)
callback(null);
else
callback(""+data);
});
} else
// Use the fs.readFileSync synchronization function to read the contents of the file
try {
return fs.readFileSync(path);
} catch (e) {
return null; }}else {
// When not loading the file with XmlHttpRequest for the nodeJS runtime environment
var xhr = Util.XHR();
// Use asynchronous or synchronous mode depending on whether the callbCAk parameter exists
xhr.open('GET', path, callback ? true : false);
/ / XHR. SetRequestHeader (' the user-agent ', 'XMLHTTP / 1.0);
xhr.setRequestHeader('Accept'.'text/plain');
if (typeof xhr.overrideMimeType === 'function') xhr.overrideMimeType('text/plain');
/ / by XmlHttpRequest. The onreadystatechange event functions asynchronous access file data
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;
// Call the send method to initiate an AJAX request
xhr.send(null);
} else {
//// calls the send method to initiate an AJAX request to obtain file data synchronously
xhr.send(null);
if (/* remote */ xhr.status == 200 || /* local */ (xhr.status == 0 && typeof xhr.responseText === 'string'))
return xhr.responseText;
return null; }}};Copy the code
The protobufJS library is intended for browsers and Nodejs, not cocos-jsb. *** One way to use Protobufjs in cocos-jsb *** is to modify the protobufjs source code as follows:
Util.fetch = function(path, callback) {
if(callback && typeof callback ! ='function') callback = null; // Change the platform checking code to the interface provided by Cocosif(cc.sys.isNative) {// The file is read using the function try {provided by cocos-jsbletdata = jsb.fileUtils.getStringFromFile(path); Cc.log (' proto file contents: {data} ');return data;
} catch (e) {
returnnull; }}else{// No modification is required on the web side. };Copy the code
We use the cocos interface to modify the code and the loading problem is solved. Is the problem really solved? Sorry, in addition to the above code, there is another code that needs to be modified, the source code is as follows:
BuilderPrototype["import"] = function(json, filename) {
var delim = '/';
// Make sure to skip duplicate imports
if (typeof filename === 'string') {// There is a platform check againif (ProtoBuf.Util.IS_NODE)
// require("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; // We need to change thisif (ProtoBuf.Util.IS_NODE)
root = require("path") ['resolve'](root);
if (root.indexOf("\ \") >= 0 || filename.file.indexOf("\ \") >= 0)
delim = '\ \'; var fname; // We need to change thisif (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; }... }Copy the code
I won’t post the modified code here, so you can figure it out.
Continue pit filling for protobuf
At this point, most of the problems have been fixed, but at this point, I’m sure most people will be baffled if you can confidently use the modified ProtobufJS source code to run your code.
Damn it!! After reading a lot of words and finally getting to this point, not only does it not run on the simulator, but it also doesn’t run on the Web.
What to do? I have to keep writing in order to solve the problem once and for all.
1. Understand the method of Creator to load resources dynamically
For an image from the Creator project, do they have the same file path on the Web as on COCOs-JSB? Would loading a proto file with protobuf.protoFromFile(‘xxx.proto’) be successful? The cocos documentation states that to dynamically load an image resource you need to store the file in assets/ Resources and use the following method to load it:
cc.loader.loadRes('resources/xxx')
Copy the code
Try to save the proto file in the resources/pb/ directory with the following code:
protobuf.protoFromFile('resources/pb/xxx.proto')
Copy the code
You’ll also get a failure prompt. How do you do that? How do I get the correct resource path? Calculate, do not buy guan zi, write tired direct answer!
protobuf.protoFromFile(cc.url.raw('resources/pb/xxx.proto'));
Copy the code
The cc.url.raw function returns different resource paths in the browser, emulator, and phone. This is the real resource path, so the code should work properly.
2. Better legal solutions
I’ve been exploring the right way to develop Cocos H5, and while modifying the ProtobufJS source code can solve the problem of running on Cocos-JSB, it’s not the only solution.
How to get protobufJS code to work without modifying the source code, and how to use PBJS tools to precompile proto files into JSON and JS files, stay tuned for my series on When Creator meets Protobufjs!