How to Animate a Line Drawing With Flash Using Actionscript 3: Part 1

Have you ever wanted to create an animation of a line drawing? I was given this challenge recently to animate a picture being drawn. At first I thought that using masks was going to be the only way to go, but I quickly realized that it is a pain to mask complex lines in a drawing. I also was thinking that reduction animation could work where segments are erased frame-by-frame, but this quickly becomes monotonous pain-staking process. Then a colleague and I began to think of a way to do this programatically through Actionscript. As it turns out, it really isn’t overly complex to program.

Here are the steps:

  1. Create a Simple Line Drawing Interface
  2. Generate and Record Points with Mouse Clicks
  3. Calculate the Angle and Length of a Line
  4. Place the Line within a Sprite
  5. Animate the Segment
  6. Use the Recorded Animation

Test the Demo by clicking around the stage to the right of the buttons below

Get Adobe Flash player

Remember that this uses line segments, so in order to get curved lines, you must use more points with short lines to create a curved look.

Create a Simple Line Drawing Interface

There are a great deal of tutorials out there about creating a line drawing interface, so I’m not going to go into that here. For this tutorial I created a rewind, pause, and play button to show that the animation is drawn on a timeline using TimelineLite. I also added a color picker as well as a simple line-weigh option in the demo interface.

Generate and Record Points with Mouse Clicks

For simplicity, this interface creates line segments to generate the animation. Each click of the mouse will generate a new point, so a listener will need to be added to the stage on mouse down. Each click will record the points needed for calculation for each line. Note that there must be two points to generate a line, so the first click of the user will appear to do nothing until the user places a second click on the stage.

stage.addEventListener(MouseEvent.MOUSE_DOWN, drawPoint);
function drawPoint(evt:MouseEvent){
 	if (!started){  // if stage has not yet been clicked, record the firstpoint and make it be the lastPoint
		vstarted = true;
		lastPoint = new Point(evt.currentTarget.mouseX, evt.currentTarget.mouseY);
 	} else {
 		var dx:Number = evt.currentTarget.mouseX-lastPoint.x;
 		var dy:Number = evt.currentTarget.mouseY-lastPoint.y;
 ...
 }

Calculate the Angle and Length of a Line

Once you have the points you will now need to know the angle of the line. This is done, first, by finding the radians bewteen the two points dy and dx. Then, that calculation must be changed to an angle. The length of the line is calculated using the good-ole Pythagorean Theorem (remember that from your old math class?).

...
 var radians:Number = Math.atan2(dy ,dx);
 var angle:Number = (radians/Math.PI)*180;
 var leng:Number = Math.sqrt(dx * dx + dy * dy); // (a^2 + b^2 = c^2)  // Pythagorean Theorem calculation

Place the Line within a Sprite

Once the line is drawn, it will be placed inside a Sprite. The Sprite is what gets controlled for the animation. Styling to the line is added through the Sprite.graphics. As mentioned before, my demo interface will record the line thickness and color to the respective variables lineThickness and curColor. Also, be sure to set the scaleMode attribute of the line to “none”, since we will be scaling the line to animate it. This will ensure the thickness of the line will not be scaled as the length of the line does get scaled. Once the lineStyle is create, the lineTo gets generated as the previously calculated length. The x and y values for the sprite are set on the last point the user clicks on. Once you set the x and y you can reset the lastPoint to the current point that the mouse was clicked on. The previously calculate angle then gets applied to the current Sprite and the Sprite is added to the stage.

...
 var line_mc:Sprite = new Sprite();
 line_mc.graphics.lineStyle(lineThickness, curColor, 1, true, "none", CapsStyle.ROUND); // be sure that scaleMode (fifth item) is "none"
 line_mc.graphics.lineTo(leng, 0);
 line_mc.x = lastPoint.x;
 line_mc.y = lastPoint.y;
 lastPoint = new Point(evt.currentTarget.mouseX, evt.currentTarget.mouseY);
 line_mc.rotation = angle;
 lines_mc.addChild(line_mc);
 ...

Animate the Segment

We have now successfully created the line and we are ready to animate it. This is simply done with Greensock’s TweenLite and TimelineLite classes. The time for the animated line is calculated in relation to the length of the line. These numbers can be played with to get the results you desire. Once you have the desired time, simply append your TweenLite to your TimelineLite. Be sure that you set your easing to “Linear.easeNone” in order for your timeline to be seamless. You will also need to play the timeline after each one is set.

...
 var time:Number = .05 * (leng/40); // time is different according to the length of the line
 lineTimeline.append(TweenMax.from(line_mc, time, {scaleX: 0, ease:Linear.easeNone}));
 lineTimeline.play()
 ...

Use the Recorded Animation

Once you have set your point animation it needs recorded. This is done at the end of the same drawPoint function:

...
 pointsArray.push({point:lastPoint, color:controller_mc.pickColor_mc.hexValue, thickness:lineThickness});

Now, every time the user creates a new point, the points and the current line attributes are added to the array that can be used for playback within a separate method. I have added the “Get Points” button in my demo interface to get the output of the array in a usable object format that can then be hard-coded for future use. Essentially, it loops through each element in the array and concatenates the elements as a string of objects.

for each(var obj:Object in pointsArray){
	 //trace("{pointX:"+obj.point.x+ ",pointY:"+obj.point.y + ",color:0x"+ obj.color + ",lineThickness:"+obj.thickness+"},");
	 tf.appendText("{pointX:"+obj.point.x+ ",pointY:"+obj.point.y + ",color:0x"+ obj.color + ",lineThickness:"+obj.thickness+"},");
 }

Its output can either be a trace or a textfield as you see when you click the “Get Points” button. It just needs to be output somewhere you can grab and then paste into your application for further use. To use this string, it is pasted as an array of objects in your actionscript as a hard-coded array named “presetArray.” One nice thing about now having the attributes as a string, you can manipulate the code attributes as desired to fine-tune your output. For example, if you decide to change the thickness or color of your line, it is as easy as replacing the respective elements in that string:

function drawPreset(evt:MouseEvent){
 presetArray = [
		{pointX:301,pointY:100,color:0x990000,lineThickness:3},
		{pointX:293,pointY:98,color:0x990000,lineThickness:3},
		{pointX:287,pointY:99,color:0x990000,lineThickness:3},
		{pointX:284,pointY:106,color:0x990000,lineThickness:3},
		{pointX:286,pointY:112,color:0x990000,lineThickness:3},
		{pointX:290,pointY:119,color:0x990000,lineThickness:3},
		{pointX:293,pointY:121,color:0x990000,lineThickness:3},
		{pointX:296,pointY:121,color:0x990000,lineThickness:3},
		{pointX:299,pointY:118,color:0x990000,lineThickness:3},
		{pointX:300,pointY:114,color:0x990000,lineThickness:3},
		{pointX:302,pointY:106,color:0x990000,lineThickness:3},
		{pointX:302,pointY:104,color:0x990000,lineThickness:3},
		{pointX:305,pointY:118,color:0x990000,lineThickness:3},
		{pointX:307,pointY:120,color:0x990000,lineThickness:3},
		{pointX:311,pointY:117,color:0x990000,lineThickness:3},
		{pointX:313,pointY:111,color:0x990000,lineThickness:3},
		{pointX:314,pointY:100,color:0x990000,lineThickness:3},
		{pointX:305,pointY:93,color:0x990000,lineThickness:3},
		{pointX:299,pointY:90,color:0x990000,lineThickness:3},
		{pointX:287,pointY:88,color:0x990000,lineThickness:3},
		{pointX:280,pointY:92,color:0x990000,lineThickness:3},
		{pointX:276,pointY:102,color:0x990000,lineThickness:3},
		{pointX:277,pointY:115,color:0x990000,lineThickness:3},
		{pointX:284,pointY:124,color:0x990000,lineThickness:3},
		{pointX:293,pointY:129,color:0x990000,lineThickness:3},
		{pointX:302,pointY:130,color:0x990000,lineThickness:3},
		{pointX:311,pointY:129,color:0x990000,lineThickness:3},
		{pointX:317,pointY:125,color:0x990000,lineThickness:3},
		{pointX:337,pointY:99,color:0xffffff,lineThickness:1},
		{pointX:340,pointY:105,color:0x990000,lineThickness:3},
		{pointX:334,pointY:103,color:0x990000,lineThickness:3},
		{pointX:326,pointY:105,color:0x990000,lineThickness:3},
		{pointX:322,pointY:114,color:0x990000,lineThickness:3},
		{pointX:323,pointY:124,color:0x990000,lineThickness:3},
		{pointX:329,pointY:129,color:0x990000,lineThickness:3},
		{pointX:337,pointY:129,color:0x990000,lineThickness:3},
		{pointX:340,pointY:126,color:0x990000,lineThickness:3},
		{pointX:342,pointY:121,color:0x990000,lineThickness:3},
		{pointX:343,pointY:111,color:0x990000,lineThickness:3},
		{pointX:343,pointY:104,color:0x990000,lineThickness:3},
		{pointX:344,pointY:102,color:0x990000,lineThickness:3},
		{pointX:343,pointY:108,color:0x990000,lineThickness:3},
		{pointX:344,pointY:119,color:0x990000,lineThickness:3},
		{pointX:344,pointY:128,color:0x990000,lineThickness:3},
		{pointX:343,pointY:136,color:0x990000,lineThickness:3},
		{pointX:341,pointY:149,color:0x990000,lineThickness:3},
		{pointX:337,pointY:158,color:0x990000,lineThickness:3},
		{pointX:331,pointY:162,color:0x990000,lineThickness:3},
		{pointX:321,pointY:160,color:0x990000,lineThickness:3},
		{pointX:315,pointY:154,color:0x990000,lineThickness:3},
		{pointX:316,pointY:150,color:0x990000,lineThickness:3},
		{pointX:322,pointY:146,color:0x990000,lineThickness:3},
		{pointX:327,pointY:144,color:0x990000,lineThickness:3},
		{pointX:331,pointY:142,color:0x990000,lineThickness:3},
		{pointX:336,pointY:138,color:0x990000,lineThickness:3},
		{pointX:338,pointY:135,color:0x990000,lineThickness:3},
		{pointX:342,pointY:130,color:0x990000,lineThickness:3},
		{pointX:345,pointY:126,color:0x990000,lineThickness:3},
		{pointX:349,pointY:118,color:0x990000,lineThickness:3},
		{pointX:353,pointY:109,color:0x990000,lineThickness:3},
		{pointX:356,pointY:104,color:0x990000,lineThickness:3},
		{pointX:358,pointY:102,color:0x990000,lineThickness:3},
		{pointX:359,pointY:101,color:0x990000,lineThickness:3},
		{pointX:358,pointY:100,color:0x990000,lineThickness:3},
		{pointX:362,pointY:98,color:0x990000,lineThickness:3},
		{pointX:365,pointY:99,color:0x990000,lineThickness:3},
		{pointX:367,pointY:99,color:0x990000,lineThickness:3},
		{pointX:360,pointY:99,color:0x990000,lineThickness:3},
		{pointX:359,pointY:107,color:0x990000,lineThickness:3},
		{pointX:359,pointY:114,color:0x990000,lineThickness:3},
		{pointX:359,pointY:122,color:0x990000,lineThickness:3},
		{pointX:359,pointY:126,color:0x990000,lineThickness:3},
		{pointX:384,pointY:97,color:0xffffff,lineThickness:1},
		{pointX:385,pointY:98,color:0x990000,lineThickness:3},
		{pointX:377,pointY:98,color:0x990000,lineThickness:3},
		{pointX:373,pointY:104,color:0x990000,lineThickness:3},
		{pointX:372,pointY:111,color:0x990000,lineThickness:3},
		{pointX:375,pointY:121,color:0x990000,lineThickness:3},
		{pointX:379,pointY:127,color:0x990000,lineThickness:3},
		{pointX:384,pointY:127,color:0x990000,lineThickness:3},
		{pointX:386,pointY:125,color:0x990000,lineThickness:3},
		{pointX:387,pointY:121,color:0x990000,lineThickness:3},
		{pointX:387,pointY:116,color:0x990000,lineThickness:3},
		{pointX:388,pointY:109,color:0x990000,lineThickness:3},
		{pointX:389,pointY:102,color:0x990000,lineThickness:3},
		{pointX:389,pointY:99,color:0x990000,lineThickness:3},
		{pointX:389,pointY:107,color:0x990000,lineThickness:3},
		{pointX:390,pointY:118,color:0x990000,lineThickness:3},
		{pointX:391,pointY:124,color:0x990000,lineThickness:3},
		{pointX:395,pointY:127,color:0x990000,lineThickness:3},
		{pointX:399,pointY:128,color:0x990000,lineThickness:3},
		{pointX:402,pointY:126,color:0x990000,lineThickness:3},
		{pointX:404,pointY:120,color:0x990000,lineThickness:3},
		{pointX:406,pointY:110,color:0x990000,lineThickness:3},
		{pointX:408,pointY:100,color:0x990000,lineThickness:3},
		{pointX:407,pointY:95,color:0x990000,lineThickness:3},
		{pointX:405,pointY:101,color:0x990000,lineThickness:3},
		{pointX:405,pointY:113,color:0x990000,lineThickness:3},
		{pointX:403,pointY:127,color:0x990000,lineThickness:3},
		{pointX:405,pointY:143,color:0x990000,lineThickness:3},
		{pointX:404,pointY:149,color:0x990000,lineThickness:3},
		{pointX:405,pointY:146,color:0x990000,lineThickness:3},
		{pointX:405,pointY:124,color:0x990000,lineThickness:3},
		{pointX:407,pointY:111,color:0x990000,lineThickness:3},
		{pointX:410,pointY:98,color:0x990000,lineThickness:3},
		{pointX:414,pointY:95,color:0x990000,lineThickness:3},
		{pointX:417,pointY:95,color:0x990000,lineThickness:3},
		{pointX:424,pointY:98,color:0x990000,lineThickness:3},
		{pointX:423,pointY:104,color:0x990000,lineThickness:3},
		{pointX:424,pointY:112,color:0x990000,lineThickness:3},
		{pointX:420,pointY:117,color:0x990000,lineThickness:3},
		{pointX:418,pointY:118,color:0x990000,lineThickness:3},
		{pointX:416,pointY:119,color:0x990000,lineThickness:3},
		{pointX:410,pointY:118,color:0x990000,lineThickness:3},
		{pointX:409,pointY:117,color:0x990000,lineThickness:3},
		{pointX:418,pointY:117,color:0x990000,lineThickness:3},
		{pointX:425,pointY:115,color:0x990000,lineThickness:3},
		{pointX:437,pointY:56,color:0xffffff,lineThickness:1},
		{pointX:434,pointY:114,color:0x990000,lineThickness:3},
		{pointX:435,pointY:121,color:0x990000,lineThickness:3},
		{pointX:437,pointY:89,color:0x990000,lineThickness:3},
		{pointX:441,pointY:86,color:0x990000,lineThickness:3},
		{pointX:445,pointY:86,color:0x990000,lineThickness:3},
		{pointX:449,pointY:86,color:0x990000,lineThickness:3},
		{pointX:451,pointY:89,color:0x990000,lineThickness:3},
		{pointX:452,pointY:94,color:0x990000,lineThickness:3},
		{pointX:453,pointY:101,color:0x990000,lineThickness:3},
		{pointX:453,pointY:106,color:0x990000,lineThickness:3},
		{pointX:452,pointY:115,color:0x990000,lineThickness:3},
		{pointX:452,pointY:120,color:0x990000,lineThickness:3},
		{pointX:457,pointY:123,color:0x990000,lineThickness:3},
		{pointX:458,pointY:126,color:0x990000,lineThickness:3},
		{pointX:462,pointY:120,color:0x990000,lineThickness:3},
		{pointX:469,pointY:87,color:0x990000,lineThickness:3},
		{pointX:467,pointY:122,color:0x990000,lineThickness:3},
		{pointX:472,pointY:91,color:0x990000,lineThickness:3},
		{pointX:475,pointY:88,color:0x990000,lineThickness:3},
		{pointX:483,pointY:87,color:0x990000,lineThickness:3},
		{pointX:487,pointY:93,color:0x990000,lineThickness:3},
		{pointX:488,pointY:101,color:0x990000,lineThickness:3},
		{pointX:485,pointY:107,color:0x990000,lineThickness:3},
		{pointX:485,pointY:115,color:0x990000,lineThickness:3},
		{pointX:485,pointY:122,color:0x990000,lineThickness:3},
		{pointX:487,pointY:124,color:0x990000,lineThickness:3},
		{pointX:492,pointY:125,color:0x990000,lineThickness:3},
		{pointX:497,pointY:127,color:0x990000,lineThickness:3},
		{pointX:500,pointY:118,color:0x990000,lineThickness:3},
		{pointX:505,pointY:97,color:0x990000,lineThickness:3},
		{pointX:504,pointY:87,color:0x990000,lineThickness:3},
		{pointX:504,pointY:115,color:0x990000,lineThickness:3},
		{pointX:506,pointY:123,color:0x990000,lineThickness:3},
		{pointX:510,pointY:126,color:0x990000,lineThickness:3},
		{pointX:540,pointY:95,color:0xffffff,lineThickness:1},
		{pointX:536,pointY:93,color:0x990000,lineThickness:3},
		{pointX:530,pointY:85,color:0x990000,lineThickness:3},
		{pointX:523,pointY:85,color:0x990000,lineThickness:3},
		{pointX:520,pointY:90,color:0x990000,lineThickness:3},
		{pointX:517,pointY:99,color:0x990000,lineThickness:3},
		{pointX:516,pointY:110,color:0x990000,lineThickness:3},
		{pointX:520,pointY:119,color:0x990000,lineThickness:3},
		{pointX:524,pointY:124,color:0x990000,lineThickness:3},
		{pointX:529,pointY:127,color:0x990000,lineThickness:3},
		{pointX:536,pointY:127,color:0x990000,lineThickness:3},
		{pointX:542,pointY:124,color:0x990000,lineThickness:3},
		{pointX:545,pointY:122,color:0x990000,lineThickness:3},
		{pointX:551,pointY:54,color:0xffffff,lineThickness:1},
		{pointX:550,pointY:47,color:0x990000,lineThickness:3},
		{pointX:551,pointY:133,color:0x990000,lineThickness:3},
		{pointX:574,pointY:62,color:0xffffff,lineThickness:1},
		{pointX:565,pointY:75,color:0x990000,lineThickness:3},
		{pointX:551,pointY:90,color:0x990000,lineThickness:3},
		{pointX:564,pointY:126,color:0x990000,lineThickness:3},
		{pointX:573,pointY:133,color:0x990000,lineThickness:3},
		{pointX:581,pointY:135,color:0x990000,lineThickness:3},
		{pointX:581,pointY:21,color:0xffffff,lineThickness:1},
		{pointX:521,pointY:24,color:0xffffff,lineThickness:1},
		{pointX:504,pointY:72,color:0xffffff,lineThickness:1},
		{pointX:505,pointY:73,color:0x990000,lineThickness:3},
		{pointX:502,pointY:76,color:0x990000,lineThickness:3}
	]
 ...

Once you have all your points you loop through them similar to how you created them in the first place with your mouse clicks, but using your array instead:

...
 var presetTimeline:TimelineLite = new TimelineLite();
 presetLines_mc = new Sprite();
 presetTimeline.pause();
 var lp:Point;
 var _idx:int=0;
 for each (var obj:Object in presetArray){
	if (_idx===0){
		lp = new Point(obj.pointX, obj.pointY);
	 }else{
		 var dx:Number = obj.pointX-lp.x;
		 var dy:Number = obj.pointY-lp.y;
		 var radians:Number = Math.atan2(dy ,dx);
		 var angle:Number = (radians/Math.PI)*180;
		 var leng:Number = Math.sqrt(dx * dx + dy * dy); // (a^2 + b^2 = c^2)
		 var line_mc:Sprite = new Sprite();
		 line_mc.graphics.lineStyle(obj.lineThickness, obj.color, 1, true, "none", CapsStyle.ROUND);
		 line_mc.graphics.lineTo(leng, 0);
		 line_mc.x = lp.x;
		 line_mc.y = lp.y;
		 line_mc.rotation = angle;
		 presetLines_mc.addChild(line_mc);
		 var time:Number = .05 * (leng/40);
		 presetTimeline.append(TweenLite.from(line_mc, time, {scaleX: 0, ease:Linear.easeNone}));
		 lp = new Point(obj.pointX, obj.pointY);
	}
		_idx++
	}
	presetTimeline.play();
	addChild(presetLines_mc)
}

View the Preset Animation Demo

Get Adobe Flash player

And there you have it, a simple drawing animation created with every click of the mouse. Now you try it for yourself, perhaps you can come up with some interesting things from this. Let me know what you come up with. Once you feel comfortable with it, go on to read Part 2.