我已经将Adobe Illustrator文档中的许多路径导入到Flash文件中.路径在场景中作为绘图对象存在.使用纯动作脚本,如何在不使用预定义动作参考线的情况下在每条线后移动符号.
编辑:我附加了Flash文件,充满了绘图对象.
http://rapidshare.com/files/406497264/pather.fla.html
问题是:这些绘图对象是否可以通过AS3访问,或者我应该将它们转换为符号/任何必要的格式.请举几个AS示例.
谢谢!
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:
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.
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.
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.*;\rimport com.greensock.plugins.*;\r\rTweenPlugin.activate([BezierPlugin]);\r\rvar shapeTimeline:TimelineLite = new TimelineLite()\r"; } override public function beginFills():void { //_actionScript += "// Fills:\rgraphics.lineStyle();\r"; } override public function beginLines():void { //_actionScript += "// Lines:\r"; } override public function beginFill(color:uint, alpha:Number = 1.0):void { if (alpha != 1.0) { _actionScript += StringUtils.printf("graphics.beginFill(0x%06x, %f);\r", color, alpha); } else { _actionScript += StringUtils.printf("graphics.beginFill(0x%06x);\r", 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);\r", 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'\r);", 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');\r", type, asColors, alphas.join(","), ratios.join(","), asMatrix, spreadMethod); } else if (matrix != null) { _actionScript += StringUtils.printf("graphics.beginGradientFill('%s', [%s], [%s], [%s], %s);\r", type, asColors, alphas.join(","), ratios.join(","), asMatrix); } else { _actionScript += StringUtils.printf("graphics.beginGradientFill('%s', [%s], [%s], [%s]);\r", 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);\r", bitmapId, asMatrix, repeat, smooth); } else if (!repeat) { _actionScript += StringUtils.printf("// graphics.beginBitmapFill(%d, %s, %s, %s);\r", bitmapId, asMatrix, repeat); } else { _actionScript += StringUtils.printf("// graphics.beginBitmapFill(%d, %s, %s, %s);\r", bitmapId, asMatrix); } } override public function endFill():void { _actionScript += "graphics.endFill();\r"; } 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);\r", 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);\r", 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);\r", 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);\r", thickness, color, alpha, pixelHinting.toString(), (scaleMode == null ? "null" : "'" + scaleMode + "'")); } else if(pixelHinting) { _actionScript += StringUtils.printf("graphics.lineStyle(%f, 0x%06x, %f, %s);\r", thickness, color, alpha, pixelHinting.toString()); } else if(alpha != 1.0) { _actionScript += StringUtils.printf("graphics.lineStyle(%f, 0x%06x, %f);\r", thickness, color, alpha); } else if(color != 0) { _actionScript += StringUtils.printf("graphics.lineStyle(%f, 0x%06x);\r", thickness, color); } else if(!isNaN(thickness)) { _actionScript += StringUtils.printf("graphics.lineStyle(%f);\r", thickness); } else { _actionScript += "graphics.lineStyle();\r"; } */ } override public function moveTo(x:Number, y:Number):void { //_actionScript += StringUtils.printf("graphics.moveTo(%f, %f);\r", x, y); //_actionScript += StringUtils.printf(_target+".x = %f;\r"+_target+".y = %f;\r", x, y); _actionScript += StringUtils.printf("shapeTimeline.append(new TweenLite("+_target+",0.001,{x:%f,y: %f}));\r", x, y); } override public function lineTo(x:Number, y:Number):void { //_actionScript += StringUtils.printf("graphics.lineTo(%f, %f);\r", x, y); _actionScript += StringUtils.printf("shapeTimeline.append(new TweenLite("+_target+","+_time+",{x:%f,y: %f}));\r", x, y); } override public function curveTo(controlX:Number, controlY:Number, anchorX:Number, anchorY:Number):void { //_actionScript += StringUtils.printf("graphics.curveTo(%f, %f, %f, %f);\r", controlX, controlY, anchorX, anchorY); _actionScript += StringUtils.printf("shapeTimeline.append(new TweenLite("+_target+","+_time+",{bezier:[{x:%f,y: %f},{x:%f,y: %f}]}));\r", 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
我有时间再修补一下.我稍微更改了导出器,现在它还期望目标对象的最大距离.这将是选择的宽度或高度(所有线条),具体取决于哪一个更大(宽度与高度).存储先前的x和y值并用于计算距离,然后将该距离除以行进的最大距离.这又用于缩放每个补间的时间.此外,我已将缓动设置为线性,因为默认(Quad.easeOut)已添加到抖动中.时机不是很准确,但看起来好一点.在这里和这里更新了fla
更新的时间线代码:
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); }
在更新的出口:
再次,随时修补.
更新2:
好的,这是另一种方法......
使用AS3解析直接从Illustrator导出的FXG:
从Illustrator CS4开始,您可以通过文件>保存副本>选择FXG文件类型将图形保存为FXG 这是我使用的fxg文件.
日本再做一次:)惊人的Lib Spark包含一个FXG Parser.还有一个SVGParser,但是现在我只玩了fxg.
所以第一步是下载库:
svn export http://www.libspark.org/svn/as3/FxgParser
您可以使用该示例,因为您使用的是Flash CS5.解析器使用TLF进行文本.我没有费心下载整个flex4 sdk以获得swc和设置.我刚刚注释了Text解析器,因为我们关注路径.注释掉的课程位于底部.
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)]\r'; 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();\r'+(target+i)+'.graphics.beginFill(0x00ff00);\r'+(target+i)+'.graphics.drawCircle(-2,-2,4);\r'+(target+i)+'.graphics.endFill();\raddChild('+(target+i)+');\r'; 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