21
2020
12

从flash中导出资源供pixijs使用

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相互之间可复用的相对多一些。



« 上一篇下一篇 »

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。