Beginning with Flash MX, Macromedia has provided the ability for coders to programmatically create their own movieclips within a Flash movie at runtime, and give them a defined shape using the Drawing API (Application Programmer Interface). This consists of a set of commands (MovieClip methods) to create movieclips (and movieclips within movieclips) that contain shapes made of strokes and fills. Those commands are:
Above are four movieclips drawn with the drawing API. Because they are the same as any other instance of the MovieClip class, any of the methods of the MovieClip class may be applied to them, and they have all the properties of that class. The code to make them is below. You can try it yourself by creating a new blank 500x200 Flash document with #666666 backround and pasting the code below into frame 1.
// make a 100-pixel grid movieclip at depth 1
createEmptyMovieClip("grid", 1);
grid.lineStyle(1, 0xcccccc, 100);
grid.moveTo(0, 0);
grid.lineTo(500, 0);
grid.lineTo(500, 200);
grid.lineTo(0, 200);
grid.lineTo(0, 0);
grid.moveTo(100, 0);
grid.lineTo(100, 200);
grid.moveTo(200, 0);
grid.lineTo(200, 200);
grid.moveTo(300, 0);
grid.lineTo(300, 200);
grid.moveTo(400, 0);
grid.lineTo(400, 200);
grid.moveTo(0, 100);
grid.lineTo(500, 100);
// create a movieclip of two rectangles at depth 2
// create a variable r to reference this movieclip
// (which we can do because createEmptyMovieClip returns a
// reference to itself)
var r:MovieClip = createEmptyMovieClip("rectangles", 2);
r.beginFill(0xcc00cc, 100);
r.moveTo(20, 20);
r.lineTo(140, 20);
r.lineTo(140, 140);
r.lineTo(20, 140);
// notice that the shape will be filled automatically
// without having to draw the last line
r.endFill();
// start a new rectangle of a different color in the same clip
r.beginFill(0x0000cc, 50);
r.lineStyle(1, 0x000033, 100);
r.moveTo(80, 80);
r.lineTo(180, 80);
r.lineTo(180, 180);
r.lineTo(80, 180);
r.endFill();
// create a triangle movieclip at centered on 0,0. then move it so
// it's centered at 300, 100 by referencing its _x and _y properties.
// then make it rotate every frame using the onEnterFrame method.
var t:MovieClip = createEmptyMovieClip("triangle", 3);
t.beginFill(0x000099);
t.lineStyle(2, 0x000033, 100);
t.moveTo(-50, 50);
t.lineTo(0, -50);
t.lineTo(50, 50);
t.endFill();
t._x = 300;
t._y = 100;
t.onEnterFrame = function() {
this._rotation += 5;
}
// make a circle movieclip c, centered at 400, 100 with radius=80
// apply a radial gradient fill to the circle
createEmptyMovieClip("c", 4);
// center point and radius of circle
var r:Number = 80;
var x:Number = 400;
var y:Number = 100;
// constant used in calculation
var A:Number = Math.tan(22.5 * Math.PI/180);
// variables for each of 8 segments
var endx:Number;
var endy:Number;
var cx:Number;
var cy:Number;
c.beginGradientFill("radial", [0x0000cc, 0xcc00cc], [80, 100], [128, 255],
{matrixType:"box", x:0, y:0, w:430, h:150, r:0});
c.moveTo(x+r, y);
for (var angle:Number = 45; angle<=360; angle += 45) {
// endpoint
endx = r*Math.cos(angle*Math.PI/180);
endy = r*Math.sin(angle*Math.PI/180);
// control:
// (angle-90 is used to give the correct sign)
cx =endx + r* A *Math.cos((angle-90)*Math.PI/180);
cy =endy + r* A *Math.sin((angle-90)*Math.PI/180);
c.curveTo(cx+x, cy+y, endx+x, endy+y);
}
c.endFill();
As you can see in the example above, drawing can be done either with lines (using lineTo with 2 parameters, the x and y point to draw the line to) or with curves. To draw a curve with the Flash drawing API, you use the curveTo method, which requires a control point and an anchor (endpoint). The control point is the "handle" in a quadratic bezier curve which bends the curve to the desired shape. Because there is only one handle per curve instead of two (as in a cubic bezier), some manipulating must be done to correctly draw arcs that are greater than about 45 degrees.
For angles of 45 degrees (an arbitrary point based on experimentation and the fact that it's a multiple of 360) and less, the control point which provides the best arc for a given angle theta is x=r, y=r * tan(theta/2). Using that control point for a 45-degree arc, we can connect eight such arcs to form a circle (45 * 8 = 360), as was done in the code above.
An easy mistake to make when using the drawing API is to assume that the movieclip's registration point (the point around which it will rotate, if you want to rotate it, eg) is at the point where you issue the first moveTo to, but it isn't. It's actually always at (0,0). So if you want the registration point to be at, say, (50,100), you need to draw the clip at or around (0,0) and then move it so that the old (0,0) point is now sitting at (50,100), using the clip's _x and _y properties. In the example above, I wanted the triangle to rotate around the (300,100) point, so I drew it first around (0,0) and then changed the _x and _y of the triangle so it would be in the position I wanted it to be for rotating.
As of Flash MX, the prototype property of the MovieClip class can be used to assign additional functions to the class, which makes them available to all movieclips. Here's an example of creating and using a drawCircle method:
// r = radius of circle
// x, y = center of circle
MovieClip.prototype.drawCircle = function (r, x, y) {
var TO_RADIANS:Number = Math.PI/180;
// begin circle at 0, 0 (its registration point) -- move it when done
this.moveTo(0, 0);
this.lineTo(r, 0);
// draw 12 30-degree segments
// (could do more efficiently with 8 45-degree segments)
var a:Number = 0.268; // tan(15)
for (var i=0; i < 12; i++) {
var endx = r*Math.cos((i+1)*30*TO_RADIANS);
var endy = r*Math.sin((i+1)*30*TO_RADIANS);
var ax = endx+r*a*Math.cos(((i+1)*30-90)*TO_RADIANS);
var ay = endy+r*a*Math.sin(((i+1)*30-90)*TO_RADIANS);
this.curveTo(ax, ay, endx, endy);
}
this._x = x;
this._y = y;
}
// use the method to draw a circle in movieclip c
// at x=100, y=100 with a 70-pixel radius
createEmptyMovieClip("c", 1);
c.beginFill(0x000055, 60);
c.drawCircle(70, 100, 100);
c.endFill();
To draw a shape with a part of it cut out, like a donut eg, simply put all the commands to draw both the initial object (big circle) and the cutout (smaller circle) between the beginFill and endFill statements. Here's how the donut on the left (ok, more like a bagel but still delicious) was created:
// r1 = radius of outer circle
// r2 = radius of inner circle (cutout)
// x, y = center of donut
// This creates a donut shape (circle with a cutout circle)
MovieClip.prototype.drawDonut1 = function (r1, r2, x, y) {
var TO_RADIANS:Number = Math.PI/180;
this.moveTo(0, 0);
this.lineTo(r1, 0);
// draw the 30-degree segments
var a:Number = 0.268; // tan(15)
for (var i=0; i < 12; i++) {
var endx = r1*Math.cos((i+1)*30*TO_RADIANS);
var endy = r1*Math.sin((i+1)*30*TO_RADIANS);
var ax = endx+r1*a*Math.cos(((i+1)*30-90)*TO_RADIANS);
var ay = endy+r1*a*Math.sin(((i+1)*30-90)*TO_RADIANS);
this.curveTo(ax, ay, endx, endy);
}
// cut out middle (draw another circle before endFill applied)
this.moveTo(0, 0);
this.lineTo(r2, 0);
for (var i=0; i < 12; i++) {
var endx = r2*Math.cos((i+1)*30*TO_RADIANS);
var endy = r2*Math.sin((i+1)*30*TO_RADIANS);
var ax = endx+r2*a*Math.cos(((i+1)*30-90)*TO_RADIANS);
var ay = endy+r2*a*Math.sin(((i+1)*30-90)*TO_RADIANS);
this.curveTo(ax, ay, endx, endy);
}
this._x = x;
this._y = y;
}
createEmptyMovieClip("d1", 1);
var colors:Array = [0, 0xCB9454, 0x8D6433];
var alphas:Array = [100, 100, 100];
var ratios:Array = [0, 110, 255];
var matrix:Object = {a:300, b:0, c:50, d:0, e:300, f:0, g:-3, h:3, i:1};
d1.beginGradientFill("radial", colors, alphas, ratios, matrix);
d1.drawDonut1(86, 36, 100, 94);
d1.endFill();
If a shape containing a cutout is to be used as a mask, as the donut on the right above, care must be taken to draw the cutout in the opposite direction from that in which the original shape was drawn. (If you don't, there will be no cutout in the mask). Here's the code for the donut mask on the right, in which a big circle is drawn in a clockwise direction and a smaller circle drawn in the opposite direction (all before the endFill is applied). pic is a movieclip containing the flower graphic.
// r1 = radius of outer circle
// r2 = radius of inner circle (cutout)
// x, y = center of donut
// This creates a donut shape that can be used as a mask
MovieClip.prototype.drawDonut2 = function (r1, r2, x, y) {
var TO_RADIANS:Number = Math.PI/180;
this.moveTo(0, 0);
this.lineTo(r1, 0);
// draw the 30-degree segments
var a:Number = 0.268; // tan(15)
for (var i=0; i < 12; i++) {
var endx = r1*Math.cos((i+1)*30*TO_RADIANS);
var endy = r1*Math.sin((i+1)*30*TO_RADIANS);
var ax = endx+r1*a*Math.cos(((i+1)*30-90)*TO_RADIANS);
var ay = endy+r1*a*Math.sin(((i+1)*30-90)*TO_RADIANS);
this.curveTo(ax, ay, endx, endy);
}
// cut out middle (go in reverse)
this.moveTo(0, 0);
this.lineTo(r2, 0);
for (var i=12; i > 0; i--) {
var endx = r2*Math.cos((i-1)*30*TO_RADIANS);
var endy = r2*Math.sin((i-1)*30*TO_RADIANS);
var ax = endx+r2*(0-a)*Math.cos(((i-1)*30-90)*TO_RADIANS);
var ay = endy+r2*(0-a)*Math.sin(((i-1)*30-90)*TO_RADIANS);
this.curveTo(ax, ay, endx, endy);
}
this._x = x;
this._y = y;
}
createEmptyMovieClip("d2", 2);
d2.beginFill(0xaa0000, 60);
d2.drawDonut2(86, 36, 300, 94);
d2.endFill();
pic.setMask(d2);
Discussed on this page:
drawing API, createEmptyMovieClip, beginFill, lineStyle, draw line, draw curve, draw circle, MovieClip.prototype, registration point, draw shape with cutout, reverse direction to make mask with cutout
Files:
Files? What files? It's all dynamic! But If you really need some, here's a list of all files currently available at the site.
Other Resources
When Flash MX came out, Ric Ewing developed these extra movieclip methods that make use of the drawing API. Each one is defined as a method of (ie, added to) the MovieClip.prototype property, which makes them available to any instance of the MovieClip class.