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相互之间可复用的相对多一些。
发表评论:
◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。