A Simple Node.js Commandline App flash2egret

Now I am developing a app which is a html5 based mmo game, called MillionsQuiz, and I pick Egret+SFS as the solution.
Because Egret uses Typescript as the logic language.
And SFS server side requires Java programming skill.
And if you need to persistence some data, you might need to write some SQL.
The last project need me to support as a iOS expert.
Also I am re-sharpening my Javascript and HTML skills.
When you need to deploy a serverside app like nginx or whatever, you need some basic shell command and linux knowledges.
I use to be a Actionscript develop. And I am writing this blog with markdown in English.
In conclusion, I probably can write in more the 10 kinds of languages so far. So what?
Yes I am flaunting!! And I guess if I talk to Master Bai about this. He is going to act like
“Damn!! You’re such a full stack developer!! But ~~~~ the majority of them are all subset of C, so Can I call you C-man”.

TheMeOnThreeHouse

No, call me X-man. And merry X-mans.

All right, let me get back to my track. Stop fucking with this.
As I mentioned, I need some animation can be ran in egret,like I did it in ActionScript with SWF files. Everytime I think about AS, I would miss the benifits from its prefect workflow tools.
Egret did support convert gif or swf files into sprite sheet, and they have a particular tool called TextureMerger (well the launch frame is totally Adobe style and it’s nice!). But I still can say it’s some kind of crude.
When I try to generate a sprite sheet from a gif file which was exported from photoshop. The result was running like a special effects from 50’s SI-FI movie. It lost some keyframes, and some frames were clipped by unreasonable rectangle. That’s shitty!
I asked the designer Lee to export it with varies methods and settings, it didn’t work. Sometimes it jumped like a fake UFO.
So I decided to make it work by myself. The pinpoint is to create animation in Flash, then export it there. Unfortunately, Flash doesn’t have a template to export a egret format file. But it has JSON-Array format.

SimilarFormats

They look very similar on structures. So I think I can parse it from Flash-JSON-Array to Egret-Movieclip.

I got three ways to figure it.

  • Write a jsfl export plugin for Flash.(well it sould work,and maybe the best choice)
  • Write a Mac OSX app to parse it like a standalone parser application did.(I did it before and I don’t wanna try it again)
  • Write a web app with html+javascript.(Man I still need to create a basic interface)
  • Call egret’s developer to give a solution.(That sounds like you are a health man and you ask a doctor to fuck your wife to heal the infertility disease)
  • Use a type of server-side script like Python,Ruby,Or Node.js,to write a commandline app.(That’s it!)

If I choose No.1 as the solution, I will lost a chance to learn a new server-side framework, so I just leave it as a ‘direct strike ball’.
And If I choose Python or Ruby,I need more time to learn the language, so the answer is Node.js.
Run the app in commandline, pass in the filepath as a parameter, then it will convert and save it to egret format. That’s it.

Why Node.js?

I do understand javascript for a while, even I don’t treat it as my main programming language. But I love it. Here is why:

  • Node.js was designed with destiny to solve blocking problems, so it’s totally non-blocking model framework.
  • If you got familiar with javascript, then it’s pretty easy to learn Coffeescript,ActionScript, Lua, Typescript etc.
  • Node.js is built up on Chrome V8 engine (that’s what they say on the offical site), I love google and chrome, so.
  • You can handle a lot of commandline things with javascript, when you need to justify some algorithm ideas, or you need to handle some daily tasks. You can write code with a modern popular script language. Isn’t that cool enough?!
  • npm, no matter what the joke that the offical site has made (now it says npm means ‘navel piercing madness’), I just love its crazy style! further more it’s holding 221,763(2016/1/2) packages on it.

TheCrazyNPM

Setup Node.js dev environment.

I installed node long time ago, and the version is too old. I upgraded it by homebrew (also I installed it by homebrew instead of downloading the installer), here’s why I do it like this.

  • If you get stuck on ‘brew update’ for several minutes. You might can reinstall all of your brew package like this, or you can upgrade your ruby to the lastest version by rvm

Get some basic knowledges about Node.js and npm

I did this by learning from my old friend treehouse too, the teachers from there are always such eager and exciting. Alternative tutorials are everywhere, here’s one of them.
Trouble shooting

Read arguments from command

If the command is like this:

1
node app.js flashSpriteSheet.json

then the nodejs code would be like this:

1
2
var filePath = process.argv.slice(2)[0];
console.log(filePath);//out put 'flashSpriteSheet.json'

Read file content from local file

1
2
3
4
5
6
7
8
var fs = require("fs");
fs.readFile(filePath,function(err,data){
if (err) {
throw err;
}else{
//do whatever you like with data
}
});

Parse string to JSON object

1
JSON.parse(dataString);

I got problem when parsing it from flash sprite sheet, that shows:
1
2
3
4
5
6
7
8
undefined:1
��{
^

SyntaxError: Unexpected token �
at Object.parse (native)
at /Users/Yourname/Documents/nodejs/flash2egret/encoding2utf8.js:9:20
at FSReqWrap.readFileAfterClose [as oncomplete] (fs.js:404:3)

I don’t know what the hell is that �� mean. But I intuitively know it’s something wrong with the encoding charset of the file.
A easy way to check file’s encoding charset

1
file -I yourfile

I found the encoding of Flash output sprite sheet is utf16-le. I can handle this by change Flash export script jsfl file. You can locate it from:
1
/Applications/Adobe\ Flash\ CC/Adobe\ Flash\ CC.app/Contents/Common/Configuration/Sprite\ Sheet\ Plugins/JSON-Array.plugin.jsfl

Then you can add a line of code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function getPluginInfo(lang)
{
// fl.trace("==== getPluginInfo");
// fl.trace(lang);
// fl.trace("---- getPluginInfo");

pluginInfo = new Object();
pluginInfo.id = "JSON-Array";
pluginInfo.name = "JSON-Array";
pluginInfo.ext = "json";
pluginInfo.encoding = "utf8";//--------------------here it is.
pluginInfo.capabilities = new Object();
pluginInfo.capabilities.canRotate = true;
pluginInfo.capabilities.canTrim = true;
pluginInfo.capabilities.canShapePad = true;
pluginInfo.capabilities.canBorderPad = true;
pluginInfo.capabilities.canStackDuplicateFrames = true;

return pluginInfo;
}

Yes, jsfl is actually ‘Javascript running in Flash’.
Don’t forget to restart Flash CC after you change the code above.
But the bad news is even you flow all the steps up there, and you’re absolutely sure that the file’s charset is ‘utf8’. You still got this shit error.
1
2
3
4
5
6
7
8
undefined:1
{"frames": [
^

SyntaxError: Unexpected token 
at Object.parse (native)
at /Users/wanghe/Documents/nodejs/flash2egret/encoding2utf8.js:9:20
at FSReqWrap.readFileAfterClose [as oncomplete] (fs.js:404:3)

A npm package iconv-lite do help

One thing that I noticed is if you open that original file by sublime, then ‘Save with Encoding’ utf-8, that will be no error when parsing. So I am a bit doubtful about Flash’s output encoding setting. So I undo the change on JSON-Array.plugin.jsfl, then try to decode it with Node.js package(Show me how powerful you are!! Node.js!!).
Then I found this,iconv-lite. I already know that the original output sprite sheet’s encoding is utf16-le.So the code I did is like this:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var fs = require("fs");
var iconvlite = require('iconv-lite');
function loadAndParse(filePath,callBack){
fs.readFile(filePath,function(err,data){
if (err) {
throw err;
}else{
var newContent = iconvlite.decode(data, 'utf16le');
var json = JSON.parse(newContent);
callBack(json);
}

});
}
module.exports.loadAndParse = loadAndParse;

Convert flash format to egret

Now I can handle all the array,and save them to a new file.Here’s the full version

app.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
var fs = require("fs");
var filePath = process.argv.slice(2)[0];
var encoding2utf8 = require("./encoding2utf8");
console.log("loading file from "+filePath);
encoding2utf8.loadAndParse(filePath,function(jsonObject){
var frames = jsonObject['frames'];
var res = {};
var newFrames = [];
frames.forEach(function(frame){
var filenameArr = frame.filename.split(" ");
var fileName = filenameArr.join("_");
var fo = frame["frame"];
res[fileName] = {x:fo.x,y:fo.y,w:fo.w,h:fo.h};
newFrames.push({res:fileName,x:0,y:0});
});
var outputObject = {
mc:{
loading:{
frameRate:30,
frames:newFrames,
}
},
res:res
};
var outputString = JSON.stringify(outputObject);
if (!fs.existsSync("./out/")) {
fs.mkdir("./out/");
}
var textureName = filePath.split(".")[0] + ".json";
fs.writeFile('./out/'+textureName, outputString, function (err) {
if (err) throw err;
var pngName = filePath.split(".")[0] + ".png";
fs.createReadStream(pngName).pipe(fs.createWriteStream('./out/'+pngName));
});
});

encoding2utf8.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var fs = require("fs");
var iconvlite = require('iconv-lite');
function loadAndParse(filePath,callBack){
fs.readFile(filePath,function(err,data){
if (err) {
throw err;
}else{
var newContent = iconvlite.decode(data, 'utf16le');
var json = JSON.parse(newContent);
callBack(json);
}

});
}
module.exports.loadAndParse = loadAndParse;