In addition to importing assets, or creating them in the Flash authoring environment, you can include assets in your projects by drawing them dynamically with ActionScript at runtime. Much of the last half of this book will take advantage of this approach to minimize the number of custom assets required, and let you generate content exclusively with code. For that reason, this section starts off with a brief introduction to drawing with code.
A code-only approach doesn’t easily offer the artistic range afforded if you can use imported or hand-drawn assets, but significant tradeoffs include increased flexibility and reduced file size. Indeed, an entirely new creative horizon becomes available with code-generated art, and using ActionScript combined with previously created assets is, of course, the best of both worlds.
You have two primary methods of drawing with code: manipulating
vectors with the Graphics
class, and
manipulating pixels with the BitmapData
and/or related classes. This chapter will primarily focus on the former,
but will also discuss a few simple pixel-based techniques, such as bitmap
caching and basic filters.
To effectively demonstrate some of the concepts in this chapter, you need to use a display object or two. Part I and Part II of this book covered the creation and use of display objects throughout, and you’ll look at some of those concepts again in Chapter 13. For clarity, however, the first recipe in this chapter reviews the process of creating a display object into which you can draw, since you need a display object for the subsequent recipes.
Use the new
keyword to create
an instance of a class that is part of the DisplayObject
class hierarchy,
and then add it to the display list.
Display objects and the display list are covered in more detail in Chapter 13. However, in case you haven’t yet read the first half of this book, you’ll find it handy now if you know just enough about the display list to create an empty display object to serve as a canvas on which you can draw with code. The combination of creating a new display object and drawing on it with the techniques discussed here lets you create code-only movie clips, sprites, buttons, and more.
All display objects can be created using the new
keyword. The following two examples create
a movie clip and sprite, respectively:
var mc:MovieClip = new MovieClip(); var sp:Sprite = new Sprite();
Neither the movie clip nor the sprite, however, is ever visible to
the user unless you add them to the display list. To add a display
object to the display list, use the addChild()
method. The following, for example, adds the previously created
sprite to the display list:
addChild(sp);
In this scenario, the sprite would be visible because you added it to the display list, but the movie clip would not be visible. (So far, the sprite has no content but is still technically viewable as an empty canvas. The remainder of this chapter will show how to draw into the sprite.) The movie clip, however, cannot be seen even if it already has content, until you add it to the display list.
13.2 Creating a New Display Object for more information on creating a display object.
You want to efficiently reference the graphics
property of a display object, such as
a sprite or the main timeline, to use as the target for vector-based
drawing.
Store a reference to the graphics
property in a variable.
To draw with vectors, you must first reference the graphics
property of a display object. Then
you can access the methods and properties of the Graphics
class, which is responsible for
dynamic vector drawing. You can reduce code length and improve
performance by storing a reference to the property in a variable, and
using that variable thereafter where you would otherwise have referenced
the property directly.
If you want to draw into a display object, for example, such as
the sprite sp
you created in 12.1 Creating a Display Object Dynamically, you can reference
its graphics
property like so:
var g:Graphics = sp.graphics;
Thereafter, you need only refer to g
when using a method or property of the
Graphics
class. You can also
reference the graphics
property of
the main timeline with the following:
var g:Graphics = this.graphics;
This line of code assumes you are writing the script in the main
timeline, so the this
keyword refers
to the correct display object: the main timeline itself.
While it’s a matter of personal coding preference, drawing directly on the main timeline isn’t usually as useful as being able to contain your drawing in a display object you can manipulate later.
The last line of the following code block sets any lines
subsequently drawn into the display object sp
to be of 1-pixel thickness and blue in
color. The line settings for the Graphics
object g
remain in effect until changed or
reset.
var sp:Sprite = new Sprite(); addChild(sp); var g:Graphics = sp.graphics; g.lineStyle(1, 0x0000FF);
The first parameter is thickness
, a pixel value, with 0 being a
hairline. The second parameter is the color
of the line.
You also have additional optional parameters that closely mimic
the Property inspector settings. These parameters include alpha
, pixelHinting
(also called stroke hinting, a
Boolean to determine if the lines are drawn on the whole pixel),
scaleMode
(to determine if and how
lines are scaled when the parent container is scaled), caps
(specifying type of line end cap used),
joints
(specifying type of joint type
used), and miterLimit
(determining
how sharp corners appear).
If you want to clear everything previously drawn into the display
object, you can use the clear()
method of the Graphics
class.
However, because a line isn’t drawn when a pixel thickness isn’t
specified, you can also clear just the line attributes by passing no
parameters to the lineStyle()
method.
g.lineStyle();
12.6 Defining a Fill Style for a demonstration of
using multiple lineTo()
commands to
create a triangle.
Drawing a line works the same way it does with pen and paper.
First, you place your pen at the first point of the line. If you don’t
take this first step in Flash, the line begins at the origin (0, 0) of
the display object, such as the registration point of a parent sprite or
even the top-left corner of the Flash stage. Also like pen and paper,
combining moveTo()
and lineTo()
method calls in your scripts lets you
draw complex shapes without creating one continuous line.
To draw a line on the stage from point (100, 100), to point (300, 100), use the following code:
var g:Graphics = this.graphics; g.lineStyle(1, 0x0000FF); g.moveTo(100,100); g.lineTo(300,100);
In most cases, you have greater control if you draw into a container display object so you can easily manipulate your sprite or movie clip as a whole. When drawing onto the sprite or movie clip, the registration point of assets drawn into the display object is (0, 0), regardless of the display object’s x and y coordinates on the stage, because the drawn assets inside are relative to the display object’s coordinate space, not that of the stage. So, you’re better off achieving the previous goal of drawing a line on the stage from (100, 100) to (300, 100) by drawing a line from (0, 0) to (200, 0), and positioning the display object.
var sp:Sprite = new Sprite(); addChild(sp); sp.x = sp.y = 100; var g:Graphics = sp.graphics; g.lineStyle(1, 0x0000FF); g.lineTo(200,0);
In this case, because you want the line to begin from the
relative origin point of (0, 0), you can omit the moveTo()
method call.
To demonstrate the ease with which you can now manipulate the drawn assets as a whole, the following line will rotate the container sprite 45 degrees, thus rotating the line inside.
sp.rotation = 45;
12.6 Defining a Fill Style for a demonstration of
using multiple lineTo()
commands to
create a triangle.
Drawing a curve is similar to drawing a line, in that a curve is
drawn from the current drawing point to a new point. However, the
curveTo()
method adds a third point,
called a control point or handle, to shape the curve. ActionScript
creates curves that use one control point for two points. This trait is
in contrast to many drawing applications, such as Adobe Illustrator,
which uses one or more control points for every point.
Placing the control point is an important part of determining the
shape of your curve. For example, consider turning the line from 12.4 Drawing a Line into a concave arc, resembling a smile. To
pull the curve down in the middle, you might select a control point
halfway between and below the two end points. Therefore, if the line
spans from (0, 0) to (200, 0), one possible choice for a control point
is (100, 100). These x and y coordinates are passed to the curveTo()
method as the first and second
arguments, while the destination or end point x and y coordinates are
the last two arguments of the method.
var sp:Sprite = new Sprite(); addChild(sp); sp.x = sp.y = 100; var g:Graphics = sp.graphics; g.lineStyle(1, 0x0000FF); g.curveTo(100, 100, 200, 0);
Omitting moveTo()
in the code
sets the first point at (0, 0), or the sprites registration point. The
final result is a curve that starts at (0, 0), ends at (200, 0), but is
curved through a control point of (100, 100).
In 12.3 Defining a Line Style, you learned how to
define a line style, but even a closed line has no fill without
specifying a fill style. The beginFill()
method specifies a color and
opacity to fill any shapes drawn until you call the endFill()
method.
When you use a fill, if drawn shapes aren’t closed (meaning the
line does not end at its starting point), the shapes are closed for you.
To demonstrate this effect, the following code draws a complete triangle
even though the instruction that draws the last side of the triangle is
commented out (to prevent it from drawing). If you remove the beginFill()
and endFill()
instructions from this code, then
you see only the two lines specified, because the fill process is no
longer auto-closing the shape.
var sp:Sprite = new Sprite(); addChild(sp); sp.x = sp.y = 100; var g:Graphics = sp.graphics; g.lineStyle(2, 0xFF0000); g.beginFill(0x0000FF, 1); g.moveTo(0, -50); g.lineTo(50, 50); g.lineTo(-50, 50); //g.lineTo(0, -50); g.endFill();
ActionScript 3.0 has added a few methods to draw geometric shapes,
removing the need to build them with multiple line segments. Creating a
rectangle is as easy as calling drawRect()
, passing in the rectangle’s x and y
location, followed by height and width.
var sp:Sprite = new Sprite(); addChild(sp); sp.x = sp.y = 100; var g:Graphics = sp.graphics; g.lineStyle(1, 0x0000FF); g.drawRect(0, 0, 100, 60);
All primitives in this chapter, except for a circle, are drawn
down and to the right of the x and y coordinates specified. When drawing
into a parent container, as in this example, this trait results in a
registration point at the upper-left corner of the shape. To draw this
rectangle around its center point, offset the x and y values passed into
drawRect()
by half the width and half
the height, respectively. The following substitute line makes the
rectangle center-aligned within its parent container, sp
.
g.drawRect(−50, −30, 100, 60);
This variance on drawRect()
requires a fifth parameter that represents the diameter of a circle used
to round off the rectangle’s corners. The last line of the following
code creates a 100 × 60 pixel rectangle with rounded corners that have a
radius of 15.
var sp:Sprite = new Sprite();
addChild(sp);
sp.x = sp.y = 100;
var g:Graphics = sp.graphics;
g.lineStyle(1, 0x0000FF);
g.drawRoundRect(0, 0, 100, 60, 15);
The fifth parameter requires a diameter, rather than a potentially more intuitive radius, to easily support the optional sixth parameter. Instead of specifying only a diameter to build the rectangle’s corners, you can also specify a height and width, constructing your corner from an ellipse. This method gives you more granular control over the corner shapes. The following substitute line, for example, uses an ellipse with a width of 30 and a height of 50 to create its corners.
g.drawRoundRect(0, 0, 100, 60, 30, 50);
You can also use the under-documented method drawRoundRectComplex()
that requires eight
parameters. The first four are, again, the x, y, width, and height of
the rectangle. The last four, however, are the diameters of each corner
circle, in the order of upper-left, upper-right, lower-left,
lower-right. The following substitute line for the previous example
would create a tab shape, with a straight bottom edge, and two rounded
top corners:
g.drawRoundRectComplex(0, 0, 100, 40, 20, 20, 0, 0);
12.7 Drawing a Rectangle for drawing a rectangle and 12.9 Drawing a Circle for drawing a circle.
In addition to x and y starting coordinates, drawing a circle requires only one additional argument value: the radius of the circle (half its diameter, or width/height). The following code creates a circle that is 40 × 40 pixels by using a radius of 20.
var sp:Sprite = new Sprite();
addChild(sp);
sp.x = sp.y = 100;
var g:Graphics = sp.graphics;
g.lineStyle(1, 0x0000FF);
g.drawCircle(0, 0, 20);
The circle is drawn differently. Instead of drawing with its origin at the upper-left corner of the shape, as is true with other primitives, the circle’s origin is its center. To draw a circle that’s below and to the right of its parent container’s registration point, you must offset the x and y coordinates by the amount used for the circle’s radius. The following substitute line aligns the circle to the top-left corner of the sprite.
g.drawCircle(20, 20, 20);
You can also draw an ellipse using the drawEllipse()
method. Rather than accepting a
radius as its third parameter, it accepts a width and height as
parameters three and four, just like drawRect()
. Also like drawing a rectangle, the
default registration point of the ellipse is the upper-left corner (in
contrast to the drawCircle()
method’s
default center registration point). Substituting the following for the
drawCircle()
instruction in the prior
example draws an ellipse that is 100 pixels wide and 50 pixels
tall.
g.drawEllipse(0, 0, 100, 50);
12.7 Drawing a Rectangle for drawing a rectangle and 12.8 Drawing a Rectangle with Rounded Corners for drawing rectangle with rounded corners.
To create a gradient fill, you must first understand its component parts. First is the type of gradient: linear or radial. Next is a set of parallel arrays (arrays with an equal number of objects in the same order) that represent up to 15 colors in the gradient. These arrays contain the color values themselves, their alpha values, and the amount of the total gradient each color is meant to occupy, respectively. Finally, an optional matrix can be used to rotate, scale, skew, or offset the location of the gradient. Here is the gradient fill creation segment:
var gradType:String = GradientType.LINEAR; var colors:Array = [0x000000, 0x000000]; var alphas:Array = [1, 0]; var ratios:Array = [0, 255]; var matrix:Matrix = new Matrix(); matrix.createGradientBox(100, 100, 0, 0, 0);
The color values use standard hexadecimal notation. The alpha values are expressed in decimal notation of percent values between 0 and 1. However, the ratio of color quantity, is expressed as an increasing set of numbers between 0 and 255, representing their position along the gradient.
An equally spaced two-color gradient, such as this example uses, is created using the two extreme values of 0 and 255. Both colors are full at the ends of the gradient, and mix together in the middle. Adding another equally spaced third color would require a ratio array of 0, 127, and 255. Finally, to skew a gradient toward one dominant color, you might use a ratio array of 0 and 127. This would show the first color in one third of the gradient and mix over to the second color that occupied two thirds of the gradient.
The last part of creating a gradient is the optional matrix to
manipulate its position, scale, and angle. You create the matrix with
the standard use of the new
keyword,
but editing it can get complex. Fortunately, you have a special method
of the Matrix
class specifically for
this purpose, createGradientBox()
.
The eponymous method accepts a width, height, rotation, horizontal
translation, and vertical translation to manipulate the gradient for
you.
This example uses a width and height that matches the size of the rectangle itself, and no horizontal or vertical translation is specified, so the full gradient is visible. With a rotation of 0, the gradient moves from first color to last color, in a left to right direction.
Finally, these values are passed into the beginGradientFill()
method (used in place of
the beginFill()
method of prior
recipes) in the order discussed: gradient type, color array, alpha
array, ratio array, and matrix. The result is a smooth gradient from
opaque black to transparent.
var sp:Sprite = new Sprite(); addChild(spMask); sp.x = sp.y = 100; var g:Graphics = sp.graphics; g.beginGradientFill(gradType, colors, alphas, ratios, matrix); g.drawRect(0, 0, 100, 100); g.endFill();
Draw the mask using the Graphics
class, and then use the mask
property of shape, sprite, and movie clip
display objects.
This recipe adds only one significant new line to material covered in previous recipes. However, due to subtle changes (variable names, size, and fill style), the complete code has been collected here. This example combines two sprites, one with a rectangle with a solid fill, and another with a rectangle with a gradient fill. The solid rectangle is on the bottom and is 300 pixels wide by 300 pixels tall. The top rectangle contains a gradient of 100 percent alpha black to 0 percent alpha black (transparent), and measures only 100 × 100 pixels.
The last line of this recipe is the important one. To use one
display object to dynamically mask another, set the mask
property of the
maskee, to the mask.
var sp:Sprite = new Sprite(); addChild(sp); var g:Graphics = sp.graphics; g.lineStyle(); g.beginFill(0x000099, 1); g.drawRect(0, 0, 300, 300); g.endFill(); var gradType:String = GradientType.LINEAR; var colors:Array = [0x000000, 0x000000]; var alphas:Array = [1, 0]; var ratios:Array = [0, 255]; var matrix:Matrix = new Matrix(); matrix.createGradientBox(100, 100, 0, 0, 0); var spMask:Sprite = new Sprite(); addChild(spMask); spMask.x = spMask.y = 100; var gMask:Graphics = spMask.graphics; gMask.beginGradientFill(gradType, colors, alphas, ratios, matrix); gMask.drawRect(0, 0, 100, 100); gMask.endFill(); sp.mask = spMask;
Note that this minimal approach supports only 1-bit masks. That is, any non-transparent pixel, no matter what the alpha value of that pixel, is considered opaque and part of the mask. This example uses a gradient that changes from 100 percent alpha to 0 percent alpha to emphasize that the alpha data has no effect, by default, on the mask.
However, 8-bit masks, or masks with varying degrees of alpha values, are supported when using bitmap caching, as seen in the next recipe.
12.6 Defining a Fill Style and 12.10 Creating a Gradient Fill for how to create a solid or gradient fill, and see 12.12 Caching Vector as Bitmap for how to cache a bitmap.
You want to use pixel-based effects on vector assets and/or attempt to increase performance of vector rendering.
Temporarily work with a visual asset as a bitmap, by temporarily storing bitmap representation of the asset.
You need significant CPU processing power to composite and render moving vectors many times a second. In some cases, you can improve performance by treating the vector asset as a bitmap, behind the scenes. In simple terms, Flash Player takes a screenshot of the vector any time a major transformation, such as changes to rotation, alpha, or scale, occurs and composites the bitmap version rather than the vector asset itself.
Because the bitmap version is cached, you have no degradation of visual quality any time a major transformation occurs. However, the need to maintain an equivalent bitmap also means performance can actually worsen if you use this feature injudiciously. Therefore, bitmap caching is not recommended if the asset is rotating, scaling, or changing opacity frequently.
More directly, sometimes you need to temporarily convert a vector to bitmap to apply pixel-based effects, like filters and 8-bit masks. In some cases, this process is automatic, such as when you’re applying a simple filter like a drop shadow. In other cases, you must explicitly enable the feature.
This recipe adds only two lines of code to the very end of the prior 12.11 Using a Drawn Shape as a Dynamic Mask to use a drawn shape as a dynamic mask. The repeated code has been omitted.
sp.cacheAsBitmap = true; spMask.cacheAsBitmap = true;
By enabling bitmap caching for both the mask and maskee, both can be composited as bitmaps. Then you can use the gradient alpha values in the mask when displaying the underlying content.
12.10 Creating a Gradient Fill for creating a gradient fill and 12.11 Using a Drawn Shape as a Dynamic Mask to use a drawn shape as a dynamic mask.
You want to apply simple bitmap filter effects to a display object, such as drop shadow, bevel, or blur.
Use one of the simple filter classes, including DropShadowFilter()
, BevelFilter()
or BlurFilter()
.
Applying simple filters to display objects is very easy for two
reasons. First, the filter classes are easy to use and work similarly to
the way the same filters are applied in the Flash authoring environment.
Second, there is a built-in filters
property in any display
object that can have a filter applied, making it straightforward to set
the property to the filters you wish to add.
Creating a filter follows the same pattern as most other
instantiations in ActionScript 3.0, using the new
keyword. Conveniently, the parameters for
setting the values of the filter are all optional, and use default
values if none are specified. This means that, at minimum, you can
create filters using the following example format, which demonstrates
the drop shadow and bevel filters:
var dsFilter:DropShadowFilter = new DropShadowFilter(); var bvFilter:BevelFilter = new BevelFilter();
You can apply the filters just as easily by setting the filters
property of your display object. The
filters
property requires an array to
let multiple filters be applied at the same time (such as applying both
a bevel and a drop shadow). When applying only one filter, you need only
pass a single-item array to the property. The following new script
creates a filled rectangle, and applies a drop shadow effect:
var sp:Sprite = new Sprite(); addChild(sp); sp.x = sp.y = 100; var g:Graphics = sp.graphics; g.lineStyle(1, 0x000099); g.beginFill(0x0000FF, 1); g.drawRect(0, 0, 100, 60); g.endFill(); var ds:DropShadowFilter = new DropShadowFilter(); sp.filters = [ds];
If you wanted to apply both a drop shadow filter and a bevel filter, the last block of code would read:
var ds:DropShadowFilter = new DropShadowFilter(); var bv:BevelFilter = new BevelFilter(); sp.filters = [ds, bv];
To manipulate a filter’s settings, you can use different optional settings for each filter. The first batch of settings for the drop shadow filter are relatively intuitive numerical values, in this order: distance offset (pixels), angle (degrees), color (in hexadecimal notation), alpha (decimal percent range, 0–1), extent of blur in the x direction, extent of blur in the y direction, strength (the amount of color applied and degree of spread), and quality (how many times the filter’s applied). The last batch of options are three Booleans that represent the special setting of this filter—whether or not the shadow is cast inside the shape (to represent a “hole” in the canvas, for example), whether or not the underlying shadow is knocked out (revealing the canvas), and whether or not the object casting the shadow is hidden (leaving only the shadow).
So, if you want to create a lighter, softer shadow, cast down and to the left, you might substitute the previous filter instantiation with the following:
var ds:DropShadowFilter = new DropShadowFilter(5, 135, 0x000099, .5, 10, 10);
You can also change values after creating a setting by manipulating the desired property directly. You just have to remember to reapply the filters to the display object after any change. For example, revisit the filter setup at the beginning of this recipe:
var ds:DropShadowFilter = new DropShadowFilter(); sp.filters = [ds];
You could then change a specific setting, such as the angle, in the filter, and then reapply the filter to see your change in action. The following code changes the angle from the default 45 to 135:
ds.angle = 135; sp.filters = [ds];