在绘图对象周围移动符号 [英] Moving symbol around drawing objects

查看:23
本文介绍了在绘图对象周围移动符号的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我已将 Adob​​e Illustrator 文档中的许多路径直接导入到 Flash 文件中.路径作为场景中的绘图对象存在.使用纯动作脚本,如何在不使用预定义的运动指南的情况下移动每行后面的符号.

我附上了 Flash 文件,里面装满了绘图对象.

注释掉TextGraphic.as

FXG 方法更适合这个问题('使用纯动作脚本,如何在不使用预定义的运动指南的情况下移动每行后面的符号?')

至于嵌套问题('这些绘图对象是否可以通过 AS3 访问,或者我应该将它们转换为符号/任何必要的格式?'):

正如@Casey 所提到的,一旦设置了图形,您就无法访问它们.使用更新的 Graphics API,您可以将图形从一个 Graphics 实例复制到另一个,但这不会公开命令.我记得 Tink某事 早于 Flash Player 10,但我不知道是什么在这方面取得进展.

HTH

I have imported a lot of paths from an Adobe Illustrator document right into a flash file. The paths exists as Drawing Objects inside the scene. Using pure actionscript, how can I move a symbol following each of the lines, without using predefined Motion Guides.

EDIT: I've attached the Flash file, full of drawing objects.

http://rapidshare.com/files/406497264/pather.fla.html

The question is: Are these drawing objects accessible through AS3 or I should convert them to symbols / whatever format necessary. And please give some AS examples.

Thanks!

解决方案

Nice question +1

I've seen the fla at work, don't have cs5 home, but I understand what you're trying to achieve.

My approaches were these:

  1. Motion Paths:

Since Flash CS4 you can copy a path, and paste it onto a Motion Tween. This will work similar to the Classic Tween's Motion Guide feature. There are quite a few problems with this:

  • You might need to manually do the cut/paste
  • Not all paths can be pasted onto a motion
  • You can use the AnimationFactory class and add a target, but the problem is, while the target is animating, you have no actionscript control over it. You can set a timer for the duration of the AnimationFactory's motion, but it gets to cumbersome.

Obviously this is a no-no.

  1. Using JSFL to traverse the paths inside the IDE:

    I stumbled upon this very handy jsfl script by ericlin which traverses all shapes selected on stage. If you select your paths and run the script(you can just double click the jsfl file), you will get the parsed coordinates.

I did a simple test using TweenLite:

import com.greensock.*;
import com.greensock.easing.*;
import com.greensock.plugins.*;

TweenPlugin.activate([BezierPlugin]);

graphics.lineStyle(0.05);
var index:int = 0;
var ball:Sprite = new Sprite();
ball.graphics.beginFill(0x009900,.75);ball.graphics.drawCircle(-2,-2,4);ball.graphics.endFill();
addChild(ball);

drawLines();

function drawLines():void{
    var t:Number = .01;
    var timeline:TimelineLite = new TimelineLite();
    var i:int = index;
    for(index; index <= ptArray.length; index += 12){
        timeline.append( new TweenLite(ball, t, {x:ptArray[i],y:ptArray[i+1]}) );
        timeline.append( new TweenLite(ball, t, {bezier:[{x:ptArray[i+2], y:ptArray[i+3]}, {x:ptArray[i+4], y:ptArray[i+5]}]}) );
        this.graphics.moveTo(ptArray[i], ptArray[i+1]);
        this.graphics.curveTo(ptArray[i+2], ptArray[i+3], ptArray[i+4], ptArray[i+5]);
        i += 6;
        timeline.append( new TweenLite(ball, t, {x:ptArray[i],y:ptArray[i+1]}) );
        timeline.append( new TweenLite(ball, t, {bezier:[{x:ptArray[i+2], y:ptArray[i+3]}, {x:ptArray[i+4], y:ptArray[i+5]}]}) );
        this.graphics.moveTo(ptArray[i], ptArray[i+1]);
        this.graphics.curveTo(ptArray[i+2], ptArray[i+3], ptArray[i+4], ptArray[i+5]);
    }
}

*Note:*The ptArray isn't shown here because it would waste too much space. The result isn't that great though. You can have a look at the fla to see what I mean. The jsfl script could be altered, but I saw you emphasised actionscript usage, so this is a no no as well.

  1. Using AS3SWF to decompile the swf at runtime and access the shapes:

Claus Wahlers developed an amazing as3 library called as3swf which allows flash/flex developers to decompile swfs at runtime. Here is an awesome article explaining the ins and outs of shapes inside swfs. There are quite a few exporters already written.

I just duplicated the AS3ShapeExporter and changed the as3 draw commands to TweenLite code. Basically I replaced moveTo with a fast tween to position, lineTo, to a regular tween and curveTo with a bezier tween. Tween Lite's BezierPlugin luckily used quadratic bezier, just like curveTo does.

Here is the code you will need to paste inside the fla that holds the shapes:

import com.codeazur.as3swf.*;
import com.codeazur.as3swf.tags.*;
import com.codeazur.as3swf.exporters.*;

this.loaderInfo.addEventListener(Event.COMPLETE, completeHandler);

function completeHandler(e:Event):void {
    var swf:SWF = new SWF(this.loaderInfo.bytes);//new SWF(URLLoader(e.target).data as ByteArray);
    var doc:AS3ShapeTweenLiteExporter = new AS3ShapeTweenLiteExporter(swf,"ball",.01);
    // Loop over all tags
    for (var i:uint = 0; i < swf.tags.length; i++) {
        var tag:ITag = swf.tags[i];
        // Check if tag is a DefineShape
        if (tag is TagDefineShape) {
          // Export shape tween
          TagDefineShape(tag).export(doc);

        }
    }
    trace(doc.actionScript);
}

Basically I load the swf, once it's ready, I pass it's bytes to as3swf and use the AS3ShapeTweenLiteExporter to parse shape tags and spit out actionscript. The 3 paramaters I pass to the constructor are : the swf instance, a name for the tween target and a time for each tween.

Here's how my hacked together class looks like:

package com.codeazur.as3swf.exporters
{
    import com.codeazur.as3swf.SWF;
    import com.codeazur.utils.StringUtils;

    import flash.display.CapsStyle;
    import flash.display.InterpolationMethod;
    import flash.display.JointStyle;
    import flash.display.LineScaleMode;
    import flash.display.SpreadMethod;
    import flash.geom.Matrix;
    import com.codeazur.as3swf.exporters.core.DefaultShapeExporter;

    public class AS3ShapeTweenLiteExporter extends DefaultShapeExporter
    {
        protected var _actionScript:String;
        protected var _target:String;
        protected var _time:Number;

        public function AS3ShapeTweenLiteExporter(swf:SWF,target:String,time:Number) {
            super(swf);
            _target = target;
            _time = time;
        }

        public function get actionScript():String { return _actionScript; }

        override public function beginShape():void {
            _actionScript = "import com.greensock.*;
import com.greensock.plugins.*;

TweenPlugin.activate([BezierPlugin]);

var shapeTimeline:TimelineLite = new TimelineLite()
";
        }

        override public function beginFills():void {
            //_actionScript += "// Fills:
graphics.lineStyle();
";
        }

        override public function beginLines():void {
            //_actionScript += "// Lines:
";
        }

        override public function beginFill(color:uint, alpha:Number = 1.0):void {
            if (alpha != 1.0) {
                _actionScript += StringUtils.printf("graphics.beginFill(0x%06x, %f);
", color, alpha);
            } else {
                _actionScript += StringUtils.printf("graphics.beginFill(0x%06x);
", color);
            }
        }

        override public function beginGradientFill(type:String, colors:Array, alphas:Array, ratios:Array, matrix:Matrix = null, spreadMethod:String = SpreadMethod.PAD, interpolationMethod:String = InterpolationMethod.RGB, focalPointRatio:Number = 0):void {
            var asMatrix:String = "null";
            if (matrix != null) {
                asMatrix = "new Matrix(" +
                    matrix.a + "," +
                    matrix.b + "," +
                    matrix.c + "," +
                    matrix.d + "," +
                    matrix.tx + "," +
                    matrix.ty + ")";
            }
            var asColors:String = "";
            for (var i:uint = 0; i < colors.length; i++) {
                asColors += StringUtils.printf("0x%06x", colors[i]);
                if (i < colors.length - 1) { asColors += ","; }
            }
            if (focalPointRatio != 0.0) {
                _actionScript += StringUtils.printf("graphics.beginGradientFill('%s', [%s], [%s], [%s], %s, '%s', '%s', %s);
",
                    type,
                    asColors,
                    alphas.join(","),
                    ratios.join(","),
                    asMatrix,
                    spreadMethod,
                    interpolationMethod,
                    focalPointRatio.toString());
            } else if (interpolationMethod != InterpolationMethod.RGB) {
                _actionScript += StringUtils.printf("graphics.beginGradientFill('%s', [%s], [%s], [%s], %s, '%s', '%s'
);",
                    type,
                    asColors,
                    alphas.join(","),
                    ratios.join(","),
                    asMatrix,
                    spreadMethod,
                    interpolationMethod);
            } else if (spreadMethod != SpreadMethod.PAD) {
                _actionScript += StringUtils.printf("graphics.beginGradientFill('%s', [%s], [%s], [%s], %s, '%s');
",
                    type,
                    asColors,
                    alphas.join(","),
                    ratios.join(","),
                    asMatrix,
                    spreadMethod);
            } else if (matrix != null) {
                _actionScript += StringUtils.printf("graphics.beginGradientFill('%s', [%s], [%s], [%s], %s);
",
                    type,
                    asColors,
                    alphas.join(","),
                    ratios.join(","),
                    asMatrix);
            } else {
                _actionScript += StringUtils.printf("graphics.beginGradientFill('%s', [%s], [%s], [%s]);
",
                    type,
                    asColors,
                    alphas.join(","),
                    ratios.join(","));
            }
        }

        override public function beginBitmapFill(bitmapId:uint, matrix:Matrix = null, repeat:Boolean = true, smooth:Boolean = false):void {
            var asMatrix:String = "null";
            if (matrix != null) {
                asMatrix = "new Matrix(" +
                    matrix.a + "," +
                    matrix.b + "," +
                    matrix.c + "," +
                    matrix.d + "," +
                    matrix.tx + "," +
                    matrix.ty + ")";
            }
            if (smooth) {
                _actionScript += StringUtils.printf("// graphics.beginBitmapFill(%d, %s, %s, %s);
", bitmapId, asMatrix, repeat, smooth);
            } else if (!repeat) {
                _actionScript += StringUtils.printf("// graphics.beginBitmapFill(%d, %s, %s, %s);
", bitmapId, asMatrix, repeat);
            } else {
                _actionScript += StringUtils.printf("// graphics.beginBitmapFill(%d, %s, %s, %s);
", bitmapId, asMatrix);
            }
        }

        override public function endFill():void {
            _actionScript += "graphics.endFill();
";
        }

        override public function lineStyle(thickness:Number = NaN, color:uint = 0, alpha:Number = 1.0, pixelHinting:Boolean = false, scaleMode:String = LineScaleMode.NORMAL, startCaps:String = null, endCaps:String = null, joints:String = null, miterLimit:Number = 3):void {
            /*
            if (miterLimit != 3) {
                _actionScript += StringUtils.printf("graphics.lineStyle(%f, 0x%06x, %f, %s, %s, %s, %s, %f);
",
                    thickness, color, alpha, pixelHinting.toString(),
                    (scaleMode == null ? "null" : "'" + scaleMode + "'"),
                    (startCaps == null ? "null" : "'" + startCaps + "'"),
                    (joints == null ? "null" : "'" + joints + "'"),
                    miterLimit);
            } else if (joints != null && joints != JointStyle.ROUND) {
                _actionScript += StringUtils.printf("graphics.lineStyle(%f, 0x%06x, %f, %s, %s, %s, %s);
",
                    thickness, color, alpha, pixelHinting.toString(),
                    (scaleMode == null ? "null" : "'" + scaleMode + "'"),
                    (startCaps == null ? "null" : "'" + startCaps + "'"),
                    "'" + joints + "'");
            } else if(startCaps != null && startCaps != CapsStyle.ROUND) {
                _actionScript += StringUtils.printf("graphics.lineStyle(%f, 0x%06x, %f, %s, %s, %s);
",
                    thickness, color, alpha, pixelHinting.toString(),
                    (scaleMode == null ? "null" : "'" + scaleMode + "'"),
                    "'" + startCaps + "'");
            } else if(scaleMode != LineScaleMode.NORMAL) {
                _actionScript += StringUtils.printf("graphics.lineStyle(%f, 0x%06x, %f, %s, %s);
",
                    thickness, color, alpha, pixelHinting.toString(),
                    (scaleMode == null ? "null" : "'" + scaleMode + "'"));
            } else if(pixelHinting) {
                _actionScript += StringUtils.printf("graphics.lineStyle(%f, 0x%06x, %f, %s);
",
                    thickness, color, alpha, pixelHinting.toString());
            } else if(alpha != 1.0) {
                _actionScript += StringUtils.printf("graphics.lineStyle(%f, 0x%06x, %f);
", thickness, color, alpha);
            } else if(color != 0) {
                _actionScript += StringUtils.printf("graphics.lineStyle(%f, 0x%06x);
", thickness, color);
            } else if(!isNaN(thickness)) {
                _actionScript += StringUtils.printf("graphics.lineStyle(%f);
", thickness);
            } else {
                _actionScript += "graphics.lineStyle();
";
            }
            */
        }

        override public function moveTo(x:Number, y:Number):void {
            //_actionScript += StringUtils.printf("graphics.moveTo(%f, %f);
", x, y);
            //_actionScript += StringUtils.printf(_target+".x = %f;
"+_target+".y = %f;
", x, y);
            _actionScript += StringUtils.printf("shapeTimeline.append(new TweenLite("+_target+",0.001,{x:%f,y: %f}));
", x, y);
        }

        override public function lineTo(x:Number, y:Number):void {
            //_actionScript += StringUtils.printf("graphics.lineTo(%f, %f);
", x, y);
            _actionScript += StringUtils.printf("shapeTimeline.append(new TweenLite("+_target+","+_time+",{x:%f,y: %f}));
", x, y);
        }

        override public function curveTo(controlX:Number, controlY:Number, anchorX:Number, anchorY:Number):void {
            //_actionScript += StringUtils.printf("graphics.curveTo(%f, %f, %f, %f);
", controlX, controlY, anchorX, anchorY);
            _actionScript += StringUtils.printf("shapeTimeline.append(new TweenLite("+_target+","+_time+",{bezier:[{x:%f,y: %f},{x:%f,y: %f}]}));
", controlX, controlY, anchorX, anchorY);
        }
    }
}

Once you download as3swf, you need to save this class in the exporter's package. Here is the result. You can download its fla and also the fla for that generated the code.

This is a pure actionscript version and has decent result.

The animation looks jerky because it has uses the same amount of time to tween for each of the line segments. some are shorter while others are longer. You could store the previous position and use it with the current position to calculate the distance, and based on that generate a decent for each TweenLite instance. Also feel free to modify that class any way you need(say you want to use a Timer instead, etc.)

Update

I had time to tinker with this a bit more. I changed the exporter a bit, now it also expects a maximum distance for your target object to travel. That will be either the width or height of the selection(all the lines), depending on which one is larger(width vs height). The previous x and y values are stored and used to calculate the distance, then that distance is divided by the maximum distance to travel. This in turn is used to scale the time on each tween. Also, I've set the easing to Linear, because the default(Quad.easeOut) added to the jitter. The timing isn't very accurate, but looks a bit better. Updated fla's here and here

The updated timeline code:

import com.codeazur.as3swf.*;
import com.codeazur.as3swf.tags.*;
import com.codeazur.as3swf.exporters.*;

this.loaderInfo.addEventListener(Event.COMPLETE, completeHandler);

function completeHandler(e:Event):void {
    var swf:SWF = new SWF(this.loaderInfo.bytes);//new SWF(URLLoader(e.target).data as ByteArray);
    var doc:AS3ShapeTweenLiteExporter = new AS3ShapeTweenLiteExporter(swf,"ball",1,300);
    // Loop over all tags
    for (var i:uint = 0; i < swf.tags.length; i++) {
        var tag:ITag = swf.tags[i];
        // Check if tag is a DefineShape
        if (tag is TagDefineShape) {
          // Export shape tween
          TagDefineShape(tag).export(doc);

        }
    }
    trace(doc.actionScript);
    System.setClipboard(doc.actionScript);
} 

the updated exporter:

Again, feel free to tinker.

UPDATE 2:

Ok, Here's yet another method...

  1. Using AS3 to parse FXG exported straight from Illustrator:

Since Illustrator CS4, you can save graphics as FXG via File > Save a Copy > select FXG filetype Here's the fxg file I used.

Japan's done it again :) The amazing Lib Spark contains an FXG Parser. There is also an SVGParser, but for now I've only played with fxg.

So the first step is to download the library:

svn export http://www.libspark.org/svn/as3/FxgParser

You might be fine using the sample, since you use Flash CS5. The parser uses TLF for text. I didn't bother to download the whole flex4 sdk to get the swc and setup. I just commented out the Text parser since we're concerned with paths. The commented out class is at the bottom.

The library contains a Path parser which is cloned and modified to get some animation code: PathTween.as You might recognize some of the variables from the as3swf classes. Here are some explanation for some of the variables I added:

  • ID - is static and is a counter for the number of Path parsed, this means we can animate each path individually
  • CODE - static, contains the import code + the timeline lite code for each path instance, this is THE CODE :)
  • MAX_DISTANCE - similar to the as3swf approach, this is used to alter the time of each tween, based on the distance travelled
  • TIME - a generic time for each tween, handy to be set from outside the class
  • TARGET - a name that will be incremented for each path and is used as the tween target.
  • _code,_distance,_x,_y,_timeScale - the same as in the as3swf approach
  • _timeRel - the relative time of each time(e.g. after it's been adjusted)

Also, I've done a quickfix, added a default winding, since sometimes, the winding attribute might be missing from the .fxg file and that breaks the parser.

In order to use you need to make a minor change to FxgFactory.as so it uses the PathTween parser instead of the default Path class.

private static const PARSERS:Array = [  Graphic , Group , Library, 
                                                Path , Ellipse, Rect, Line, 
                                                BitmapGraphic, BitmapImage, 
                                                TextGraphic, RichText ];

becomes:

private static const PARSERS:Array = [  Graphic , Group , Library, 
                                                PathTweenTracer , Ellipse, Rect, Line, 
                                                BitmapGraphic, BitmapImage, 
                                                TextGraphic, RichText ];

Finally, some basic timeline code that uses all these:

import fxgparser.*
import fxgparser.parser.*;

var fxgurl:String = "fingerprint.fxg";
var fxgSprite:FxgDisplay;

var loader:URLLoader = new URLLoader( new URLRequest( fxgurl ) );
    loader.addEventListener( Event.COMPLETE , displayData );

//some setup
PathTween.MAX_DISTANCE = 360;//change this to fit your shape's largest dimension(width || height)
PathTween.TIME = 2;//change this to your needs
PathTween.TARGET = "ball";//a name of a target clip that will be incremented for each move,line,curve


function displayData( e:Event ):void {
    var fxgxml:XML = XML( e.currentTarget.data );

    fxgSprite = new FxgDisplay( fxgxml );   //parse SVG
    System.setClipboard(PathTween.CODE);
    //make some clips for the tester
    trace(getClips());

    addChild( fxgSprite );  
}

function getClips():String {
    var result:String = 'this.filters = [new GlowFilter(0x00ff99)]
';
    var clipsNum:int = PathTween.ID;
    var target:String = PathTween.TARGET;
    for(var i:int = 0 ; i < clipsNum ; i++)
        result += 'var '+(target+i)+':Sprite = new Sprite();
'+(target+i)+'.graphics.beginFill(0x00ff00);
'+(target+i)+'.graphics.drawCircle(-2,-2,4);
'+(target+i)+'.graphics.endFill();
addChild('+(target+i)+');
';
    return result;
}

This is fairly simple:

  • setup the constants in PathTween
  • load the fxg
  • once it's loaded, draw it. While it's drawing, code is generated in the background
  • once it's drawn, put the code in the clipboard. For my path I had about 11K of generated lines, so tracing isn't a good idea here
  • too keep just the tweening code in PathTween, I generate some code(getClips()) to make target movieclips here. Feel free to add this kind of functionality in PathTween as you need it.

Then I opened up a fresh fla file and:

  • pasted the clip that was already in the clipboard (a few thousand lines of code)
  • copied the code from the Output Panel and pasted it after "TweenPlugin.activate([BezierPlugin]);"

You can see the result and get the fla.

So far, as3swf is cool once you've got the fla ready with pasted illustrator paths, could be faster since as3swf works with bytes. What I like about the FXG approach:

  • you skip the step where you paste the graphics into an fla, you just save a copy as FXG. You can use one fla to generate all the code you need, just change the path to the fxg file you want to animate.
  • each path is parsed individually, so this is a bit more flexible.
  • although it generates more code than the as3swf version, cubic beziers, arcs and other curves are broken into lineTo commands which even out the animation a bit.

This actually got fun, with individual timelines, so I made yet another copy that draws some cheesy trails into a bitmap data.

Here's the PathTweenTracer class, like the previous, place this in the parser package. Again, the PARSERS constant needs to be updated inside FxgFactory:

private static const PARSERS:Array = [  Graphic , Group , Library, 
                                                PathTweenTracer , Ellipse, Rect, Line, 
                                                BitmapGraphic, BitmapImage, 
                                                TextGraphic, RichText ];

The timeline code is pretty much the same. The result looks nice (source)

Here are some screenshots of the generated animation:

Commented out TextGraphic.as

The FXG approach would fit the question better('Using pure actionscript, how can I move a symbol following each of the lines, without using predefined Motion Guides ?')

As for the nested question('Are these drawing objects accessible through AS3 or I should convert them to symbols / whatever format necessary ?' ) :

As @Casey mentioned, you cannot access the graphics once they're set. Using the updated Graphics API you can copy graphics from one Graphics instance into another, but that doesn't expose the commands. I remember Tink had something way before Flash Player 10, but I don't know what's the progress on that.

HTH

这篇关于在绘图对象周围移动符号的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆