pixijs很强大,但是没有一款好用的界面编辑器。
纯代码进行布局还是效率太低,修改起来也麻烦。
flash本身是一款很优秀的工具,虽然现在swf好多平台都默认不支持了,但是flash pro (animatecc)作为工具使用还是很方便的。
flash支持jsfl脚本,可以自己写脚本实现想要的功能。
这是我自己写的一个脚本,导出spritesheet以及界面布局结构,把两个json文件合并到一起了,可以减少一次请求,有其他需求可以自己修改。
代码如下:(建议删掉MovieClip和SimpleButton)
var trace = fl.trace;
var JSFL_PATH = getDir(fl.scriptURI);
fl.runScript(JSFL_PATH + "json2.jsfl");
var flaDocument = fl.getDocumentDOM();
var flaName = flaDocument.name.replace(".fla", "");
;
var sheet = fl.spriteSheetExporter || new SpriteSheetExporter();
var data = {
instances: [],
libs: {}
};
flaDocument.editScene(0);
initSheet();
parseDada();
simplify();
save();
function parseDada() {
walkTimeline(flaDocument.getTimeline(), function (element, frameIndex) {
if (frameIndex === 0) {
if (element.instanceType === "symbol") {
var symbolItem = element.libraryItem;
if (symbolItem.timeline.frameCount === 1) {
if (!data.libs[symbolItem.name]) {
var o = parseSymbolItem(symbolItem);
if (o) {
data.instances.push(symbolItem.name);
data.libs[symbolItem.name] = o;
}
}
}
}
}
});
}
function initSheet() {
sheet.borderPadding = 2;
sheet.shapePadding = 2;
sheet.layoutFormat = "JSON";
sheet.canStackDuplicateFrames = false;
sheet.stackDuplicateFrames = false;
}
function save() {
var sheetStr = sheet.exportSpriteSheet(flaDocument.path.replace(".fla", ""), { format: "png", bitDepth: 32, backgroundColor: "#00000000" });
var sheetData = JSON.parse(sheetStr);
frameAddFileName(sheetData);
delete sheetData.meta.app;
delete sheetData.meta.version;
sheetData.conf = data;
var strData = JSON.stringify(sheetData);
var jsonPath = flaDocument.path.replace(".fla", ".json");
jsonPath = FLfile.platformPathToURI(jsonPath);
FLfile.write(jsonPath, strData);
trace("save complete:" + jsonPath);
}
function frameAddFileName(sheetData) {
var frames = {};
for (var key in sheetData.frames) {
frames[flaName + "/" + key] = sheetData.frames[key];
}
sheetData.frames = frames;
}
function parseSymbolItem(symbolItem) {
var arr = [];
walkTimeline(symbolItem.timeline, function (element, frameIndex) {
if (!arr[frameIndex]) {
arr[frameIndex] = [];
}
var eleData = parseElement(element);
if (eleData) {
arr[frameIndex].push(eleData);
}
});
if (arr.length === 1) {
return {
type: "Container",
children: arr[0]
};
}
return {
type: "MovieClip",
frames: arr
};
}
function parseElement(element) {
var data = {};
parseElementParam(data, element);
if (element.elementType === "text") {
return null;
}
else if (element.elementType === "shape") {
return null;
}
else if (element.elementType === "instance") {
data.libName = element.libraryItem.name;
parseLibItem(element.libraryItem);
}
else {
return null;
}
return data;
}
function parseLibItem(item) {
if (data.libs[item.name]) {
return;
}
var o;
if (item.itemType === "bitmap") {
o = parseItemBitmap(item);
}
else if (item.itemType === "graphic") {
o = parseSymbolItem(item);
}
else if (item.itemType === "movie clip") {
o = parseSymbolItem(item);
}
else if (item.itemType === "button") {
o = parseSymbolItem(item);
}
if (o) {
data.libs[item.name] = o;
}
}
function parseItemBitmap(item) {
sheet.addBitmap(item);
return {
type: "Bitmap",
texture: item.name
};
}
function toRad(ang) {
return ang * 3.1415926 / 180;
}
function parseElementParam(o, element) {
setParamIgnoreDefault(o, "x", toFixed(element.x, 2), 0);
setParamIgnoreDefault(o, "y", toFixed(element.y, 2), 0);
setParamIgnoreDefault(o, "rotation", toFixed(toRad(element.rotation || 0), 2), 0);
setParamIgnoreDefault(o, "sx", toFixed(element.scaleX, 3), 1);
setParamIgnoreDefault(o, "sy", toFixed(element.scaleY, 3), 1);
setParamIgnoreDefault(o, "skewX", toFixed(toRad(element.skewX || 0), 3), 0);
setParamIgnoreDefault(o, "skewY", toFixed(toRad(element.skewY || 0), 4), 0);
if (element.elementType === "instance") {
var instance = element;
if (instance.instanceType === "symbol") {
setParamIgnoreDefault(o, "alpha", toFixed((element.colorAlphaPercent / 100), 2), 1);
}
if (instance.name !== "") {
o.name = instance.name;
}
}
}
function setParamIgnoreDefault(o, param, value, defaultValue) {
if (value !== defaultValue) {
o[param] = value;
}
}
function walkTimeline(timeline, callback) {
forEach(timeline.layers, function (layer) {
forEach(layer.frames, function (frame, j) {
forEach(frame.elements, function (element) {
callback(element, j);
});
});
}, true);
}
function forEach2(arr, callback) {
forEach(arr, function (a, i) {
forEach(a, function (b, j) {
callback(b, i, j);
});
});
}
function forEach(arr, callback, reverse) {
if (reverse === void 0) { reverse = false; }
if (reverse) {
for (var i = arr.length - 1; i >= 0; i--) {
callback(arr[i], i);
}
}
else {
for (var i = 0; i < arr.length; i++) {
callback(arr[i], i);
}
}
}
function toFixed(num, n) {
var s = num + "";
if (s.indexOf(".") != -1 && s.indexOf(".") + n < s.length) {
return parseFloat(num.toFixed(n));
}
return num;
}
function getDir(path) {
return path.substr(0, path.lastIndexOf("/") + 1);
}
function simplify() {
simplifyBitmap();
simplifyContainer();
simplifyMovieClip();
}
function simplifyBitmap() {
for (var key in data.libs) {
var o = data.libs[key];
if (o.type === "Container") {
forEach(o.children, simplifyBitmapEle);
}
else if (o.type === "MovieClip") {
forEach2(o.frames, simplifyBitmapEle);
}
}
for (var key in data.libs) {
var o = data.libs[key];
if (o.type === "Bitmap") {
delete data.libs[key];
}
}
}
function simplifyBitmapEle(c) {
var libItem = data.libs[c.libName];
if (libItem.type === "Bitmap") {
c.texture = libItem.texture;
delete c.libName;
}
}
function simplifyContainer() {
for (var key in data.libs) {
var o = data.libs[key];
if (o.type === "Container" && data.instances.indexOf(key) === -1) {
if (o.children.length === 1) {
var c = o.children[0];
if (c.texture && !c.name && (!c.sx && !c.sy && !c.rotation && !c.alpha && !c.skewX && !c.skewY)) {
o.type = "Sprite";
o.texture = c.texture;
if (c.x || c.y) {
o.anchor = { x: -c.x, y: -c.y };
}
delete o.children;
}
}
}
}
}
function simplifyMovieClip() {
for (var key in data.libs) {
var o = data.libs[key];
if (o.type === "MovieClip") {
tryMovieClip2Animate(o);
}
}
}
function tryMovieClip2Animate(o) {
for (var i = 0; i < o.frames.length; i++) {
if (o.frames[i].length !== 1) {
return;
}
if (!isOri(o.frames[i][0])) {
return;
}
if (!o.frames[i][0].texture) {
return;
}
}
o.type = "AnimatedSprite";
o.textureAry = [];
for (var i = 0; i < o.frames.length; i++) {
o.textureAry.push(o.frames[i][0].texture);
}
delete o.frames;
}
function isOri(o) {
return !o.sx && !o.sy && !o.rotation && !o.alpha && !o.x && !o.y && !o.skewX && !o.skewY;
}json2.jsfl可以从这里下载,下载之后改一下后缀名,放到和上面的代码同一个目录就可以用了。
https://github.com/douglascrockford/JSON-js
读取json中的conf字段,就是布局的配置对象。
只导出主场景主时间轴第一帧的元件,支持导出多个元件。
相应的pixijs中的解析代码。
export class ParseSkinPlugin {
public static parse(con: BaseEquipment, skinName: string): void {
const confData: IEquipmentConfData = LoadSkinPlugin.getConfData(skinName) as IEquipmentConfData;
if (confData) {
const parser: Parser = new Parser(con, confData, skinName);
parser.destroy();
}
}
}
class Parser{
private className: string;
private libs: IKeyValueMap<IItemData>;
constructor(parent: Container, confData: IEquipmentConfData, className: string) {
this.className = className;
this.libs = confData.libs;
const instanceData: IItemData = this.getInstanceData(confData, className);
if (instanceData && instanceData.type === ItemType.Container) {
this.parseChildren(parent, instanceData.children);
}
}
public destroy(): void{
this.className = null;
this.libs = null;
}
private getInstanceData(confData: IEquipmentConfData, className: string): IItemData {
let instanceName: string = className;
if (confData.instances.indexOf(className) === -1) {
instanceName = confData.instances[0];
}
if (instanceName) {
return this.libs[instanceName];
}
return null;
}
private parseChildren(parent: Container, children: IElementData[]): void {
children.forEach((ele: IElementData) => {
const c: DisplayObject = this.parseEle(ele);
if (c) {
parent.addChild(c);
if (c.name && c.name !== "") {
parent[c.name] = c;
}
}
});
}
private parseEle(ele: IElementData): DisplayObject {
let dis: DisplayObject;
if (ele.texture) {
dis = Sprite.from(this.getTextureId(ele.texture));
} else {
dis = this.parseLibItem(ele.libName);
}
if (dis) {
this.setDisParam(dis, ele);
}
return dis;
}
private parseLibItem(libName: string): DisplayObject {
const itemData: IItemData = this.libs[libName];
if (!itemData) {
return null;
}
switch (itemData.type) {
case ItemType.Sprite:
return this.parseSprite(itemData);
case ItemType.AnimatedSprite:
return this.parseAnimatedSprite(itemData);
case ItemType.Container:
return this.parseContainer(itemData);
case ItemType.MovieClip:
return this.parseMovieClip(itemData);
case ItemType.Text:
return this.parseText(itemData);
case ItemType.Graphics:
return this.parseGraphics(itemData);
case ItemType.Button:
return this.parseButton(itemData);
default:
break;
}
return null;
}
/**
* 解析Sprite。
* @param itemData
* @return {Sprite}
*/
private parseSprite(itemData: IItemData): Sprite {
const sp: Sprite = Sprite.from(this.getTextureId(itemData.texture));
if (itemData.anchor) {
sp.anchor.x = itemData.anchor.x / sp.width;
sp.anchor.y = itemData.anchor.y / sp.height;
}
return sp;
}
/**
* 解析AnimatedSprite。
* @param itemData
* @return {AnimatedSprite}
*/
private parseAnimatedSprite(itemData: IItemData): AnimatedSprite {
const textureArr: Texture[] = [];
itemData.textureAry.forEach((textureStr: string) => {
textureArr.push(Texture.from(this.getTextureId(textureStr)));
});
return new AnimatedSprite(textureArr);
}
/**
* 解析Container。
* @param itemData
* @return {Conatiner}
*/
private parseContainer(itemData: IItemData): Container {
const container: Container = new Container();
this.parseChildren(container, itemData.children);
return container;
}
/**
* 解析MovieClip。
* @param itemData
* @return {MovieClip}
*/
private parseMovieClip(itemData: IItemData): MovieClip {
console.log(this.className + ": MovieClip:" + itemData.texture);
const movieClip: MovieClip = new MovieClip();
for(let i: number = 0; i < itemData.frames.length; i++) {
const container: Container = new Container();
this.parseChildren(container, itemData.frames[i]);
movieClip.insertDisplayObjectToFrame(container, i, null);
}
movieClip.currentFrame = 0;
return movieClip;
}
/**
* 解析Text。
* @param itemData
* @return {Text}
*/
private parseText(itemData: IItemData): Text {
return null;
}
/**
* 解析Graphics。
* @param itemData
* @return {Graphics}
*/
private parseGraphics(itemData: IItemData): Graphics {
return null;
}
/**
* 解析SimpleButton。
* @param itemData
* @return {SimpleButton}
*/
private parseButton(itemData: IItemData): SimpleButton {
return null;
}
/**
* 获取TextureId。
* 拼接上className。
* @param textureStr
* @return {string}
*/
private getTextureId(textureStr: string): string {
return this.className + "/" + textureStr;
}
/**
* 设置元件属性。
* @param dis 元件
* @param ele 元件数据
*/
private setDisParam(dis: DisplayObject, ele: IElementData): void {
dis.x = ele.x || 0;
dis.y = ele.y || 0;
dis.scale.x = ele.sx || 1;
dis.scale.y = ele.sy || 1;
dis.rotation = ele.rotation || 0;
dis.alpha = isNaN(ele.alpha) ? 1.0 : ele.alpha;
dis.name = ele.name || "";
if (dis.rotation === 0) {
dis.transform.skew.x = ele.skewX || 0;
dis.transform.skew.y = ele.skewY || 0;
}
}
}
//----------------------器材_conf.json数据结构------------------------
interface IEquipmentConfData {
instances: string[];
libs: IKeyValueMap<IItemData>;
}
interface IElementData {
x?: number;
y?: number;
rotation?: number;
sx?: number;
sy?: number;
skewX?: number;
skewY?: number;
alpha?: number;
libName?: string;
type?: string;
name?: string;
texture?: string;
}
interface IItemData {
type: string;
texture?: string;
anchor?: IPoint;
children?: IElementData[];
frames?: IElementData[][];
textureAry?: string[];
}
const enum ItemType {
Bitmap = "Bitmap",
Sprite = "Sprite",
Container = "Container",
MovieClip = "MovieClip",
AnimatedSprite = "AnimatedSprite",
Text = "Text",
Graphics = "Graphics",
Button = "Button"
}一些简单的小游戏,推荐使用animatecc自带的canvas项目,使用createjs开发,完全够用,支持的功能多,很方便。性能要求高一些的使用pixijs。
也可以考虑白鹭引擎或者cocos creator。白鹭的工具太散,工作流不明确,cocos好一些,cocos成本还是比较高的,而且从cocos转其它的成本会更高。pixijs和createjs以及egret相互之间可复用的相对多一些。
发表评论:
◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。