Chapter 12. Objects

CONTENTS
  •  Formal Rules of Objects
  •  Attaching Sounds
  •  Attaching Colors to Clips
  •  The Date Object
  •  Implications for Movie Clips
  •  Summary

If you've gotten this far in the book, you've seen objects in several places. Instances of Movie Clips are the most basic type of object in Flash, and the best to learn from because you can see them onscreen. You've also seen several of the scripting objects (namely, Math, String, Array, Selection, TextField, TextFormat, and the Key object). You've even seen "generic objects" (or "associative arrays"). Although you've already learned a lot about objects, there's more!

The objects introduced in this chapter are not only particularly practical, they all require the formal rules of objects (such as instantiation). In this way, they make some of the previously explored objects seem very forgiving in comparison, because you'll be doing things that were not required in the other objects. Luckily, these objects are well worth the additional effort.

In this chapter, you will:

  • Learn the rules of these formal objects.

  • Use the Sound object to "attach" sounds that can be manipulated using scripts.

  • Use the Color object to color clips on-the-fly.

  • Use the Date object to perform any imaginable calculation involving calendars or time.

  • Use the attachMovie method to effectively drag clips from the Library using ActionScript.

  • Create lines, fills, and gradients entirely in script.

Formal Rules of Objects

Most of these concepts will appear familiar. For example, by now you know that objects have properties, which are basically just variables that contain data. Some properties have visual representations, such as a clip's _alpha property. Although the value for this property can be ascertained (as in theClip._alpha) and changed (as in theClip._alpha=10), some properties can be ascertained only. The set of properties for any object type is specific to the object. For instance, only the Movie Clip object has an _alpha property. Other objects have other properties, but they're all the same in that they contain values that can sometimes be modified.

In addition to having properties, objects can have methods, which are functions that are applied to unique instances of an object. Methods are processes, whereas properties are just static attributes. So far, this should be a review. The concept that's a little bit new is that formal objects must be instantiated. In the case of clip instances (that is, Movie Clip objects), you can instantiate them by dragging them from the Library. After objects have been instantiated, they have their own unique set of properties and the potential to have methods, such as nextFrame(), applied to them individually. The formal objects require that you instantiate them using a constructor function. All constructor functions follow this pattern:

new Object();

This chapter discusses the following constructor functions, each of which instantiates a different formal object type (Sound, Color, and Date, respectively):

new Sound();  new Color();  new Date();

(By the way, instantiating a new Movie Clip using ActionScript during runtime uses a different technique, which we'll discuss in the "Implications for Movie Clips" section later in this chapter.)

The key to remember with instantiating objects is that you must store the object in a variable; therefore, saying new Sound() doesn't really do anything. However, mySound=new Sound() creates a new instance (in the form of the Sound object) and places it into the variable mySound, whose value is of the Object data type. From this point forward, you can treat mySound like any object by referring to properties (such as mySound.someProperty) or by using methods (such as mySound.someMethod()) in the same way that you would treat a Movie Clip instance.

Until you build your own objects, that's about all there is to it. The trick is that you must instantiate an object and place it in a variable before you can start doing stuff with it or doing stuff to it. Now we can see the nitty-gritty details of Sound Date, Color, and Movie Clip.

Attaching Sounds

This section probably could be called "The Sound Object." However, if there's one step that's easy to forget when using the Sound object, it's the "attach sound" step, so maybe the section title will help you remember. Here's the process you take in order to use the Sound object.

Sound Object Basics

The idea is that by using ActionScript, you will effectively drag a sound out of the Library and start using it in your movie. Items in the Library that aren't used anywhere in your movie normally don't export when you publish your movie (which is a good thing when you consider that unnecessary sounds will add to the filesize). After you import the sound you intend to "attach" into the Library, you'll need to override the "no-export" feature by setting the Linkage option in the Library's options menu (see Figure 12.1).

Figure 12.1. A sound item is set to "Export for ActionScript" (and given an identifier name) through the Library's Linkage option.

graphics/12fig01.gif

All you need to do is select Export for ActionScript and give it a unique identifier name. (I'll use "soundID" for my examples.) In addition to causing your exported movie's filesize to grow, this sound will now download before the first frame loads.

Now that you have a sound identified, you can start coding. You first instantiate a Sound object and place it in a variable. I'm using the variable name my_sound to trigger code hints. ("_sound" is the specified suffix in the ActionScript code hints configuration file, which is described in Chapter 2, "What's New in Flash MX.")

my_sound=new Sound();

Before you can start using the Sound object methods, properties, and events (see Figure 12.2), you need to attach the sound item to this object. Events are simply a way for you to automatically trigger a homemade function any time the specified event occurs. By the way, you'll find a lot more detail about events in Chapter 14, "Extending ActionScript."

Figure 12.2. The list of Sound object methods and properties is short, but powerful.

graphics/12fig02.gif

The attachSound method enables you to specify which sound (in the Library) you want to associate with this object.

my_sound.attachSound("soundID");

Notice that "soundID" matches the name you gave the imported sound in the linkage settings. At this point, you can now start playing with the other methods. You'll likely want to start the sound so that you can hear all the changes you might make to the sound later.

my_sound.start();

This will start the sound playing from the beginning of the sound file. (I always try to use my_sound.play(), which will not work because play() is a method of the Movie Clip object.) Two optional parameters are included in the start() method. If you want to cut in and start the sound (not at the beginning), you can specify the number of seconds into the sound that you want to begin. That is, my_sound.start(10) will start the sound 10 seconds in from the start. (You shouldn't use this to skip past silence at the beginning of your sound; any silence should have been removed before import as it adds unnecessarily to the filesize.) The second optional parameter enables you to specify how many times the sound should loop. To make a sound play almost continuously, use my_sound.start(0,9999999). Notice that you still need something in the first parameter that specifies the delay until start time in order to use the second parameter.

graphics/icon02.gif

The opposite of start() is obviously stop(). Use my_sound.stop() to stop the sound. This actually "pauses" the sound, although if you later use my_sound.start(), it will start over from the beginning. To create a pause/ resume feature, you need to know about a new property of the Sound object: position. My initial thought was that you could (when pausing) save the value of the position property in a variable and then use that as a parameter for the start() method in the resume button. It's actually easier than that. Just use my_sound.start(my_sound.position/1000) for a resume button. The reason position is divided by 1000 is that its value is represented in milliseconds, whereas the start() method accepts seconds. Realize that it doesn't hurt to precede the start() command with a line that performs a stop; otherwise, you can have several sounds playing at once, resulting in a layered effect.

There are only a few other methods and properties (refer to Figure 12.2), so let's look at them all.

Advanced Sound Controls

Although starting or stopping a sound isn't really fancy, you'll likely need to at least start a sound before using the other methods. While a sound is playing, you can easily adjust its volume by using my_sound.setVolume(level), where level is an integer between 0 and 100. Effectively, this is a percent of the volume to which the user has her computer system and speakers set. That is, my_sound.setVolume(100) will play the sound at the full 100 percent of the user's computer settings. Conversely, my_sound.setVolume(0) will make the sound silent. Keep in mind that setVolume(0) is different from stop() because the sound continues to play but at a volume of 0. The default sound level is 100, which you can lower by using setVolume(). If you ever need to ascertain the current level, just use the getVolume() method (for example, theVolume=my_sound.getVolume()).

In addition to changing the volume, you can use pan to affect the balance between the left and right channels. Similar to the way a camera can pan from left to right, you can cause the sound to seem to originate from either the left speaker or the right speaker. The setPan() method accepts a parameter ranging from -100 (to pan all the way to the left) to 100 (to pan all the way to the right). When the sound is sent to both speakers equally, the pan is 0. So, when a sound is playing, you can use my_sound.setPan(-50) to make it sound as though your audio has moved to the left. You can actually set the pan (as you can set the volume) even when a sound isn't playing, but you'll always need a Sound object on which to use the setPan() method. If you ever need to ascertain the current pan, use getPan().

Finally, the last Sound object method is called setTransform() (and its sister, getTransform()). On the surface, this method appears to be very similar to setPan() because it controls how much sound is going to each channel. But it's actually a combination of setting the volume, setting the pan, and determining exactly which portion of the audio goes to each speaker. You specify four factors when using the setTransform() method: how much left-channel sound should go to the left speaker (referred to as ll and ranging from -100 to 100), how much left-channel sound should go to the right speaker (lr), how much right-channel sound should go to the right speaker (rr), and how much right-channel sound should go to the left speaker (rl). The setTransform() method provides you with very fine control. By the way, the changes that you make to volume and pan override the settings that you made previously through the setVolume() and setPan() methods.

Now for the funky part: Specifying the four settings (ll, lr, rr,and rl) would probably be easiest if you simply provided four parameters when invoking the setTransform() method. It doesn't work that way, however. Instead, setTranform() accepts a single parameter in the form of a generic object that has four properties (of pre-designated names). The process involves first creating a generic object in a variable, setting the four properties (ll, lr, rr, and rl, because those are the ones Flash expects), and finally passing that variable as the parameter when calling setTransform(). Here's how you might do it:

transObj=new Object();  transObj.ll=100;  transObj.lr=0;  transObj.rr=100;  transObj.rl=0;  my_sound.setTransform(transObj);

This script effectively sets the balance to be equal. (The left-channel sound going to left speaker and the right-channel sound going to the right speaker are both 100.)

This assumes that my_sound is already instantiated (and playing if you want to hear anything). After you populate the variable (transObj in this case), you can change any of its four properties and then invoke the last line (my_sound.setTransform(transObj)) to hear the change. Assuming that the transObj exists, you can send all the left channel's audio to the right speaker (and vice versa) by using the following code:

transObj.ll=0;  transObj.lr=100;  transObj.rr=0;  transObj.rl=100;  my_sound.setTransform(transObj);

To make a stereo sound play as though it were mono, use the following code:

transObj.ll=50;  transObj.lr=50;  transObj.rr=50;  transObj.rl=50;  my_sound.setTransform(transObj);

Translated, this code says, "Send half of the left-channel's sound to the left speaker, and the other half to the right speaker. Then send half of the right-channel's sound to the right speaker and the other half to the left speaker." The result is that all sounds are distributed evenly to both speakers, and it sounds mono.

Finally, if you need to ascertain the current transform, use getTransform(). The only tricky thing is that this returns another object. If you want to specifically address one of the four properties, you can do so by using the dot-syntax techniques with which you're so familiar. For example, to find out what percent of the left channel is going to the left speaker, use my_sound.getTransform().ll. If you want to access several properties, but don't want to keep calling the getTransform() method, you can store the transform you get in a variable, as follows:

curTrans=my_sound.getTransform();  trace("Left speaker is playing "+ curTrans.ll + "% of left channel");  trace("Right speaker is playing "+ curTrans.lr + "% of left channel");  trace("Right speaker is playing "+ curTrans.rr + "% of right channel");  trace("Left speaker is playing "+ curTrans.rl + "% of right channel");

Controlling Multiple Sounds

I left out an optional parameter when first introducing the Sound object constructor function (new Sound()). Think of the parameter as the way to attach a sound to a Movie Clip instance. Then that instance and attached sound is independently controllable just like any other property of that clip. The way it works is that you need to provide a reference to a Movie Clip as the parameter, and then the sound's properties will be independently controllable. Otherwise, the volume of all Sound objects will be the same. The following code shows how you can start playing two sounds and then control their respective volume levels:

music_sound=new Sound(clip1);  music_sound.attachSound("music");  music_sound.start();  voice_sound=new Sound(clip2);  voice_sound.attachSound("narration");  voice_sound.start();  music_sound.setVolume(50);  voice_sound.setVolume(80);

You'll need two clips on the Stage (clip1 and clip2) and two sounds in the Library with linkage set and identifiers ("music" and "narration"). When the sounds start, you'll hear their respective sounds change when calling music_sound.setVolume(toWhat) and voice_sound.setVolume(toWhat). It's weird because you'd think by having the two sound objects stored in two separate variables (music_sound and voice_sound), you'd have independent control. Just remember, though, that you need to attach the sound to a specific Movie Clip instance (by providing the clip as a parameter) to have independent control. Lastly, variables (as always) are indeed part of the timeline where they're created (so you'll need to apply all that you know about addressing if you want to refer to them from other timelines). But interestingly, including a clip reference in the new Sound() constructor has no impact on addressing (so you don't need to worry about it).

Determining a Sound's Position and Duration

graphics/icon02.gif

The Sound object was pretty awesome when introduced in Flash 5, but it lacked the position and duration properties that are now included. You saw how the position property can help you resume a previously paused sound. The duration property is also quite useful. For example, to create a clip that acts like a progress bar, use the following script in a keyframe:

my_sound=new Sound();  my_sound.attachSound("soundID");  my_sound.start();

Then attach the following script to a Movie Clip instance that contains a wide rectangle:

onClipEvent(enterFrame){    this._xscale=_parent.my_sound.position/_parent.my_sound.duration*100;  }

Let me explain: After you get the sound started in the keyframe, the Movie Clip instance's enterFrame script will execute repeatedly. That script keeps reassigning a value to its own _xscale property. The value is simply the result of the expression "position/duration*100." Because we want to ascertain the position and duration of the sound (which are stored in the variable my_sound in the main timeline), we begin with _parent.my_sound. You can try some "what-if" numbers to see that the scale will go from 0 to 100. (That is, when both the position property and the duration property are the same, the expression will evaluate to 100.) Finally, to make the progress bar appear to grow to the right (rather than evenly in both directions), select the left-center default Registration setting when you first convert the rectangle shape to a Movie Clip (see Figure 12.3).

Figure 12.3. When converting a shape to a symbol, you can select the left-center default Registration setting so that scaling will make it appear to grow to the right.

graphics/icon02.gif

graphics/12fig03.gif

To use the Sound object, you just need to remember these steps:

  1. Import a sound, and then, in the Linkage Properties dialog box, set the Linkage option to "Export for ActionScript." Also, give the sound a unique identifier.

  2. Instantiate the Sound object, and then store it in a variable by using the new constructor, my_sound=new Sound().

  3. Attach a sound by referring to the identifier name that you provided in Step 1, my_sound.attachSound("identifier").

  4. Start the sound with my_sound.start() and then use any of the other methods or properties as you wish.

  5. Finally, when you're sure that you will no longer need the sound, you can delete the variable containing the object with my_sound.start(). Although I don't believe that a few unused sound objects will bring your movie's performance to a crawl, just as with any variables, there's no reason to have more than you're using. (By the way, be sure to stop() the sound before you delete the variable, or you'll lose control of the sound.)

Loading Sounds

graphics/icon02.gif

One of the most exciting new features in Flash MX is the fact that you can dynamically load native MP3 sounds (and .jpg images) into your movies at runtime. That is, a tiny .swf can load huge image or sound files upon the user's request. For example, you could create a jukebox that enables users to select to play any song, or a photo gallery that enables them to browse through a collection of images.

Although the capability to load sounds at runtime is a powerful new feature, the process happens to be really simple. After you've instantiated a Sound object (for example, my_sound=new Sound()), you can commence downloading by using the following code:

my_sound.loadSound("music.mp3",true);

The first parameter is simply the MP3's filename. (Relative paths also work fine.) The second parameter is set to true (when you want the sound to stream) or to false (when you want it to perform like an Event sound). In the case of streaming, the sound will begin playing as soon as it is able. You must start event sounds by using the start() method.

The dilemma, however, is that you can't start an Event sound until it's fully downloaded. You can solve this issue in a couple of ways. First, you can (perhaps continually) check whether the sound is fully downloaded by comparing getBytesLoaded() to getBytesTotal(). When they're the same, you know the sound is downloaded. Another way is to link the onLoad event to a homemade function that contains the start() method.

Using the first technique, we can display a progress bar (just like the earlier example that showed position). In a keyframe, start the download with:

my_sound=new Sound();  my_sound.loadSound("music.mp3",false);  waiting=true;

Then, on a clip with the instance name clip, place the following code:

onClipEvent(enterFrame){   if(_root.waiting){     this._xscale=_root.my_sound.getBytesLoaded()/_root.my_sound.getBytesTotal()*100;      if(_root.my_sound.getBytesLoaded()==_root.my_sound.getBytesTotal()){      _root.my_sound.start();       _root.waiting=false;      }    }  }

This example is nearly the same as the position indicator from earlier. An additional if statement keeps checking to see if the number of bytes loaded is equal to the total number of bytes; if it is, the start() method is called. The reason I included the waiting variable (which is behaving like a "flag") is that I didn't want the progress bar to start checking and resizing until the downloading had begun.

Instead of continually checking to see whether an Event sound has fully downloaded (before invoking start()), you can link a homemade function to the onLoad event. I'll show you one way to do it here, but realize there's more to learn about extending ActionScript in Chapter 14.

my_sound=new Sound();  my_sound.onLoad=beginSound;  my_sound.loadSound("music.mp3",false);  function beginSound(){   my_sound.start();  }

This example is intentionally wordy, as you'll see when you learn more about linking events to functions. The first and third lines should look familiar. In the second line, I'm saying that when the my_sound object triggers its onLoad event (that is, when it fully loads), I want it to trigger the homemade beginSound() function. You can see the beginSound() code in the last three lines, where it simply starts the sound. There's one important thing to note: Unlike many places in ActionScript, the order of the preceding code is very important. That is, you must specify which function should link with onLoad before the loading begins! You're telling Flash what to do when it finishes an operation, but it has to know everything before it starts (including what to do when it's done).

By the way, there's a hidden benefit of using the Sound object with loaded sounds as opposed to imported sounds that have their linkage set. By setting the linkage on any symbol, you force Flash to download that entire symbol before the first frame of the movie displays even before any graphic progress bar appears! Although you have to manage the download process when using loadSound(), it can make the user experience better because you can give him something to look at during the download.

graphics/icon02.gif

Just so you know, there's another such event that you can link to a function: onSoundComplete. It can be used like the onLoad function, but to trigger a function when a particular sound finishes playing.

Attaching Colors to Clips

Through scripting, you can use the Color object to apply color styles on any named instances the same way you can manually use the Properties panel. That is, you can color anything that you can give an instance name to clips, buttons, or dynamic or input text. (To color video objects, however, just place them inside a clip). The process is analogous to using the Sound object. The Color object requires that you first instantiate an object through new Color(instance), where instance is the clip (or other instance type) that you want to affect. Then use one of the two methods setRGB() or setTransform() to cause the instance to change. It really is that simple. It's just that when you want to perform elaborate effects, there are additional details as you'll see in the following section, "Using the Color Transform Method."

Simple Coloring

Here's the simple version of coloring a clip using the Color object. First, instantiate the Color object and specify a target instance, as follows:

my_color=new Color(theClip);

The variable my_color now contains the object, so the clip instance named theClip will be affected when we do the next step. (Notice that even though the clip is referenced between quotes, you can still use absolute or relative paths as long as you remember the quotes.) At this point, you can color the clip using the setRGB() method. To tint it pure red, use

my_color.setRGB(0xFF0000);

For green, use

my_color.setRGB(0x00FF00);

Notice that the parameter used for the setRGB() method is in the form of a hexadecimal color reference. The first two characters (0x) act as a warning to Flash that what follows is in the hexadecimal format. That's it! As long as you know the hex value for the color you intend to use, this works great. By the way, if you want to learn more about hexadecimal color references, the easiest way is by exploring Flash's Color Mixer panel when fully expanded (see Figure 12.4).

Figure 12.4. Flash's Color Mixer panel will display hex values when fully expanded.

graphics/12fig04.gif

Using RGB Values

In addition to providing a hex value (after 0x) as the setRGB() method's parameter, you can provide a number between 0 and 16,777,215. The following three paragraphs include a detailed explanation of how you can specify colors in an intuitive (RGB) manner. 24-bit color includes 8 bits for each of the three colors: red, green, and blue. This means that there are 256 shades for each color (0 255) because each binary digit is either "on" or "off." Eight binary digits 1, 2, 4, 8, 16, 32, 64, and 128 all "on" adds up to 255. If they're all "off," it adds up to zero. The highest number you can represent with twenty-four binary digits (three 8-bit colors) is 16,777,215.

An interesting method is used to relate 16 million different values to three colors. Blue always gets the first 8 bits (1 through 8 bits, or 0 255). A value of 0 is no blue; a value of 255 is 100 percent blue. A value such as 128 is only 50 percent blue. To add green to the equation, 8 bits are still used, but they start at 9 and go through 16. So instead of ranging from 0 to 255 in one-step increments, green is defined with numbers between 256 and 65,280, which is 256 steps of 256 each. So every notch of green is 256: 256 is one notch of green; 512 is two notches of green; and so on. A number such as 522 is two notches of green and 10 notches of blue. The way to see the breakdown of blue and green is to first extract the round 256 increments. (256 goes into 522 twice; then the left over 10 is used for blue.) It might be easier to see if you imagine that blue went from 0 to 99 (in steps of 1), and that green went from 100 to 1000 (in steps of 100). If you extract the largest steps first and then the remainder, a number such as 600 would be a shade of green 6 units deep (and no blue), but a number such as 630 would be 6 units green and 30 units of blue. It actually works just like this except that instead of being based on 1s and 100s, it's based on 8 bits and 16 bits. Red gets the last 8 bits, of course, which means that it steps from 65,280 to 16,711,680 in 256 steps of 65,536 each. All this means is that it's next to impossible to intuitively specify colors using RGB but we'll find a way.

The reason the previous concept is so difficult to understand is that we like to think of digits going from 0 to 9 (that is, in base 10). In our base-10 system, the far-right digit is for "ones" (0 9), the second digit is for "hundreds" (0 9 again, but representing how many "hundreds"), and so on. Hexadecimal values do it in three pairs of characters: RRBBGG. For example, the first two characters "RR" represent a number between 0 and 255 for red. The three 256-shade values for R, G, B are in the 24-bit system; they're just hard to derive. If you think in binary, though, it's probably easiest. Using 8 digits (for 8 bits), you can represent any number from 0 to 255. For example, 00000001 is 1, 00000010 is 2, and 00000011 is three. Each position in the 8-digit number represents a bit. To read the previous binary numbers, consider the far-right digit as the "ones" (0 1), the second digit as the "twos" (0 1 representing how many "twos"), the third digit as the "fours," and so on. Therefore, you can count (in binary) 001, 010, 011, 100, 101, 111. Check it out 1, 2, 3, 4, 5, 6 in binary!

For a 24-bit color, you need to have only 24 binary digits. The eight at the far right represent 0 255 for blue, the middle eight represent 0 255 for green, and the leftmost eight digits represent 0 255 for red (see Figure 12.5).

Figure 12.5. A binary representation of a 24-bit number includes eight digits for each color.

graphics/12fig05.gif

Finally, I can show you a quick way to convert RGB values (of 0 255 each) into binary at the same time that they can be used in the setRGB() method. That is, how do you turn r=255, g=255, and b=255 (which is white) into a binary series of 24 ones or zeroes (that can, in turn, be used as the parameter passed when invoking setRGB())? Assuming that r, g, and b are variables containing a number between 0 and 255, you can use a bitwise shift operator to specify how many digits to the left you want the binary number to shift. That is, 5<<8 takes the binary version of 5 (101) and shifts it eight spots to the left (10100000000 that's 101 with eight zeros). This is exactly how to shift the value for green up eight places (or g<<8). Red needs to be shifted 16 places, so r<<16 is used. Finally, the combined form appears in the following handy-dandy formula:

graphics/icon01.gif

To supply an RGB value into a single value (as the Color object requires), replace r, g, and b in the formula,

my_color.setRGB(r<<16 | g<<8 | b);

Notice that b (the value for blue) doesn't need to be shifted. The result of the entire expression in the parentheses is a binary number representing RGB by using eight digits for each color. In practice, you just need to make sure that your values for r, g, and b are between 0 and 255, and then use the previous method call as it appears in the formula.

Using the Color Transform Method

Naturally, you probably aren't satisfied with only 16 million different possible colors; you probably want to change the alpha of a color, too. After all, I said you can use scripting to achieve the same results that you can by using the Properties panel's Color Styles and just look at all the things you can do with the Advanced Effect dialog box (see Figure 12.6). The setTransform() method enables you to modify any instance associated with the Color object in the same way the Advanced Effect dialog box does. Of course, you could always just use the familiar theClip._alpha=70 if you ever need to change the alpha of a tinted clip but setTransform() can do even more than that.

Figure 12.6. The Properties panel's Advanced Effect dialog box gives you fine control over tinting (especially with bitmaps).

graphics/12fig06.gif

Actually, if you understand the interface of the Advanced Effect dialog box (which is easiest when applying an effect to a clip containing a raster graphic), you'll better understand how to use setTransform(). Just like using the setTransform() on the Sound object, you need to pass a generic object as a parameter. You first create a generic object, set its properties according to the effect you want, and then pass it when invoking setTransform(). The generic object has eight properties that correlate directly to the eight settings in the Advanced Effect dialog box. Of course we're not using the Properties panel (the manual way) we're doing this with scripting but it helps to consider these eight properties in relation to the Advanced Effect dialog box (see Figure 12.7).

Figure 12.7. The settings in the Advanced Effect dialog box are the same for the generic object passed to the setTransform() method.

graphics/12fig07.gif

Here's a code sequence that you might use to tint a clip 50 percent red and 30 percent alpha:

transObj=new Object()  transObj.ra = 50;  transObj.rb = 255;  transObj.ga = 100;  transObj.gb = 0;  transObj.ba = 100;  transObj.bb = 0;  transObj.aa = 30;  transObj.ab = 0;  my_color=new Color(theClip);  my_color.setTransform(transObj);

I see setTransform() as having two main benefits: You can change the alpha of a clip, and you can control subtle color shifts that are most apparent when the clip being colored contains a raster graphic (such as a .bmp, .png, or .jpg). Although there are in fact other ways to control the alpha, setTransform() is nice because you can tint and change the alpha in one line (instead of using both setRGB() and the _alpha property). The kinds of effects that you can make on raster graphics are pretty cool, however, and they go way beyond just tinting and changing the alpha.

Before we move on, let me just mention the two other methods: getRGB() and getTransform(). The getRGB() method returns (in base 10) the last color value used on the object referenced. That is, my_color.getRGB() will return the color for the instance associated with the object stored in the variable my_color specifically, a number between 0 and 16,777,215.

By the way, a handy way to translate that number to binary is by using the toString() method, but by providing a parameter (called a radix). That is, my_color.getRGB().toString(2) will return (in the form of a string) the color value represented in binary. You can even use toString(16) to convert the number being operated on to hexadecimal. Check it out by using trace(my_color.getRGB().toString(16)).

Finally, realize that the value returned when you use getTransform() is an object with eight properties. For example, if you want to ascertain the current alpha percentage, use my_color.getTransform().aa, because aa is the property containing the alpha percentage.

The Date Object

The Date object gives you an easy way to store specific dates, ascertain the current date (and time), and find out details about any date (such as its day of the week). For example, I know that I was born on a Wednesday not because I remember, but because I can check it with the Date object. Basically, I created a new instance of the Date object with my birthday as the initial value, and then I used a method that returns the day of the week. Another interesting application is to repeatedly reassign a variable a new Date object (and use the current date and time for the initial value). Then you can display all the details of the current time (using a clock or calendar). It's even possible to accurately find the difference (in number of days) between two dates, and you don't need to know which are leap years or how many days any particular month "hath." (You know, "Thirty days hath September .")

Instantiating a Date

Similar to the Color and Sound objects, you always start by instantiating the Date object, and then you can use methods on it. The variable you use to hold a Date object contains a snapshot of a moment in time. That is, a variable that contains the Date data type is holding only one moment in time. When you create an instance of the Date object, you can optionally specify that moment (year, month, hour, second, and even millisecond, if you want); or, if you don't specify any date, you're given a date that matches the setting of your user's computer clock. Here's the form to create an instance with the current time:

now=new Date();

The variable now contains a Date object with the current time. You can provide up to seven optional parameters (to specify year, month, day, hour, minute, second, and millisecond). For example, this is how you create an instance that contains the U.S. Independence Day (July 4, 1776):

indyDay=new Date(1776, 6, 4);

That is, the year 1776, the month July (counting January as 0, February as 1), the fourth date in the month (which surprisingly starts counting with 1). I left out the rest of the optional parameters: hour, which counts from 0 (midnight) to 23 (11 PM); minutes (0 59 for every hour); seconds (0 59 per minute); and, milliseconds (of which there are 1,000 per second). Because the seven parameters are optional, you can leave them off if you want (although the order is important: the first parameter always referring to year, the second to month, and so on).

Manipulating Dates

After you've created a variable that holds your Date object, you can manipulate and view it through the various methods. Although quite a few methods are available (see Figure 12.8), there are only two general types: methods that "get" information from a date, and methods that change (or "set") elements within a date. Let's walk through some operations to get a handle on both types of methods.

Figure 12.8. Although there are many methods for the Date object, they fall into two general categories: those that get values and those that set values.

graphics/12fig08.gif

Getting Information from Dates

Several methods "get" specific information from a date. For example, the getDay() method returns the day of the week. However, because it returns a number between 0 (for Sunday) and 6 (for Saturday), you might first create an array with all the days of the week, as follows:

dayNames=["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"];

Then you can easily determine the day of the week that the U.S. Constitution was signed:

trace("Signatures were made on a "+dayNames[indyDay.getDay()]);

Because indyDay.getDay() returns a 4, the expression dayNames[4] would return "Thursday". It's almost as though it doesn't matter that getDay() starts counting with Sunday as 0, because when grabbing data from an array, we count the same way. (This isn't to say that it will never mess you up.)

Other methods are similar to getDay() such as getYear() (and its better-half getFullYear()), getMonth(), and getDate() (which returns the number of the day in the month). It's unlikely that you'd really need these to ascertain the year, month, or date for a Date object that you created by specifying the date. However, they can be particularly useful when you're not sure of the date. For example, let's say that you want your Flash movie to display information about the current date in a Dynamic Text field. You can start with:

today_date=new Date();

Then, if you have a Dynamic Text field with the instance name message, you can use the following code:

monthNum=today_date.getMonth()+1;  dateNum=today_date.getDate();  yearNum=today_date.getFullYear();  message.text=monthNum+"/"+dateNum+"/"+yearNum;

By the way, getYear() returns the number of years since 1900 (so if you do a getYear() on a date in the year 2001, you'll get 101). The method getFullYear() returns a 4-digit number (which naturally renders your Flash movie "non-Y10K-compliant" but I wouldn't worry about it).

If you want to display the date in a format that's a little more wordy than 3/31/2002, you can use a quick-and-dirty technique involving the toString() method. When used on a Date object, toString() returns the full date and time in the form:

Sun Mar 31 00:00:00 GMT-0800 2002

Although this is kind of nice, if you want something more readable, you could use a function such as:

function getNiceDate(dateObj){   var dayNames=["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"];    var monthNames=["January","February","March","April","May","June","July","August", graphics/ccc.gif"September","October","November","December"];    var day=dayNames[dateObj.getDay()];    var month=monthNames[dateObj.getMonth()];    var date=dateObj.getDate();    var year=dateObj.getFullYear();    return (day+" "+month+" "+date+", "+year);  }

Just pass an actual Date object for a parameter, and you'll get a string back that follows a more traditional form than what you get with toString(). That is, trace(getNiceDate(indyDay)) will result in "Thursday July 4, 1776" displayed in the Output window.

The data maintained in a Date object is detailed down to the millisecond. However, because instantiating a new Date object (with new Date()) takes a snapshot only of the current time, in order to make a clock (that doesn't appear frozen in time), you'll need to repeatedly re-instantiate the Date object. The ideal place to do this is inside an enterFrame clip event. So, if you have a Dynamic Text field (with an instance name theTime) in a Movie Clip and you attach the following code to the clip instance, you'll have a nice digital clock (see Figure 12.9).

Figure 12.9. You can easily make this digital clock display with a Dynamic Text field and in a Movie Clip.

graphics/12fig09.gif

onClipEvent (enterFrame) {   now=new Date();    seconds=now.getSeconds();    if (seconds<10){     seconds="0"+seconds;    }    minutes=now.getMinutes();    if (minutes<10){     minutes="0"+minutes;    }    hours=now.getHours();    amPm="AM";    if (hours<10){     hours="0"+hours;    }else if (hours>12){     amPm="PM"      hours=hours-12;      if (hours<10){       hours="0"+hours;      }    }    theTime.text=hours+":"+minutes+":"+seconds+" "+amPm;  }

In the preceding code, the now variable is reassigned a new instance of the Date object and then used in most of the subsequent calculations. The seconds variable is set by applying the getSeconds() function to now. If seconds is less than 10, we just add a "0" in front of it so that it still displays using two characters. The minutes variable is similar to seconds. In the case of hours, determining the actual hour is straightforward enough: hours=now.getHours(). I first assume it's AM (by setting amPm to "AM"), but if hours is not less than 10, I check to see whether it's greater than 12, in which case I say amPm is "PM" and then take 12 off; that is, if it's 14:00, you subtract 12 to get 2 PM. (Kind of makes you like the 24-hour system used in most areas of the world.) Finally, I build the string to replace theTime instance's text property.

Naturally, the ugly part of this code is the error checking. That is, I go through extra work to make sure that numbers less than 10 appear with a zero to their left. The important part to remember in this example is that the variable now is continually reassigned a new Date object (12 times a second if the frame rate is set to 12 fps). I don't think you'll see a performance hit from this code executing so frequently but even if you did, it's a clock, so you'll want it to update frequently.

Setting Values in Dates

So far, we've looked at using the Date object to store a moment in time and then use methods to peek inside. Although we haven't explored all these methods that "get" values returned, there's another set of methods that "set" values. These methods enable you to change any attribute of a date stored in a variable. For example, if you want to take today's date and find out the month and date for a day exactly two weeks from now, you can use the setDate() method. The setDate() method will change the date in the attached object to whichever number you provide as a parameter. If you provide "today plus 14 days" (that is, getDate()+14), you'll find the answer. Here's the code:

now=new Date();  fortNight=new Date();  fortNight.setDate(now.getDate()+14);  trace("Two weeks from now is: "+ fortNight.toString());

Notice that I could have just used the setDate() method on my original Date object (now) that is, now.setDate(now.getDate()+14). Instead of getting confused with a variable called "now" that actually contained a date in the future, I came up with another variable name (fortNight). But it's important that before I try setting fortNight's date, I have to instantiate the variable as a Date object (in the second line of code). Simply put, you can only use methods on objects. Another important point is the setDate() method actually changes the object being operated on. This is performing an assignment without the equal sign. Finally, the cool part about setDate() (and all the other "set" methods) is that other elements in the object being operated on automatically update accordingly. That is, if you setDate to today's date plus 40 days (now.setDate(now.getDate()+40)), you'll find that the object's month (found through getMonth()) has changed. Similarly, you'll find that the year changes when you setMonth to the current month plus 13.

There's one last method that I want to describe. When you use the method getTime() on any Date object, the elapsed milliseconds between January 1, 1970 and the object being operated on will be returned. This might seem like a useless piece of trivia, but it might come up on a quiz show some time. It also happens to be the most direct way to determine the difference between two dates. For example, if you knew one person was born five days after January 1, 1970 and another person was born 200 days after January 1, 1970, it's simple to calculate the difference in their two ages as 195 days. It's not that you care how many days apart from January 1, 1970 each birthday is it's just a common reference point. In the following code sample, you see that we never really take much note as to how many milliseconds have past since 1970, we just find the difference between two dates. In fact, one of the dates used occurred before the magic 1970 date.

birthday = new Date(1968, 1, 29);  bicentennial = new Date(1976, 6, 4);  difference = Math.abs(birthday.getTime()-bicentennial.getTime());  millisecondsPerDay = 1000*60*60*24;  difference = Math.floor(difference/millisecondsPerDay);  trace ("Birthday was "+difference+" days before or after the  bicentennial");

Notice that no one really cares how many milliseconds have elapsed since January 1, 1970 (or, even that getTime() could result in a negative number if the date being operated on was earlier). Instead of calculating whether a birthday was before or after the bicentennial, I just calculated the absolute value of the difference. The absolute value (Math.abs()) always returns a non-negative number. Finally, to convert milliseconds into days, I divided by 1000*60*60*24 (which is based on the fact that there are 1000 milliseconds every second, 60 seconds every minute, 60 minutes every hour, and 24 hours each day). Instead of just dividing the difference by millisecondsPerDay, I use Math.floor() to make sure to just extract the integer portion of the number. That is, I don't want to know that it's been 3047.95833333333 days 3047 is plenty. Using this same basic technique, you can accurately calculate the difference between any two days. (By the way, leap-day of 1968 isn't really my birthday.)

Implications for Movie Clips

To be perfectly accurate, we've already discussed the Movie Clip object. However, by using the techniques that follow, you can effectively drag instances of clips onto the Stage entirely through scripting. You can even draw lines and fills into Movie Clips using script. In the rest of this chapter, you'll see how to instantiate clip instances at runtime, how to write scripts that get attached to those new clips, and how to draw lines and fills dynamically. Don't think this is the end of what you can do with Movie Clips, however. You'll see Movie Clips make appearances in all the following chapters, too.

Instantiating Clips at Runtime

If you want to use scripting to cause a Movie Clip instance to appear on the Stage during runtime, there are two basic methods: duplicateMovieClip() and attachMovie(). The older method (duplicateMovieClip()) requires that you already have a clip on the Stage; when it gets duplicated, everything on it (that is, scripts) is also duplicated. Personally, I always use the attachMovie() method because I don't have to remember to first place a clip (to be duplicated) on the Stage. However, attachMovie() requires that you first set the linkage for the clip in the Library (as we did for sounds) and come up with a unique identifier. Then, all you do is call the attachMovie() function by using:

targetPath.attachMovie("identifier", "newInstanceName", depth);

targetPath is a timeline where you want the new clip to reside (for example, _root); "identifier" is the name you gave the clip through its linkage; "newInstanceName" assigns it an instance name (as though you typed it in manually through the Properties panel); and depth is the level number. (Most clips are on level 0, but you can specify higher numbers when loading movies and the clips will appear on top of others.) For example, if I have a clip who's identifier is "box", I can use:

_root.attachMovie("box", "box_1",1);

This will place an instance of the clip in the Library whose identifier name is set to "box" on the Stage. The clip's instance name will be box_1. The following code will position and change the _alpha property of the clip:

_root.box_1._x=190;  _root.box_1._y=33;  _root.box_1._alpha=50;

Looks pretty familiar, eh? Well, to explain this any further would probably insult your intelligence. We covered all the bases of Movie Clips in Chapter 7, "The Movie Clip Object." The trick to remember here is the identifier that's set through the Library item's Linkage options.

graphics/icon02.gif

There are a couple new things to realize. First, the preceding example of first attaching the clip (that is instantiating it) and then setting properties was the only way to create and then modify clips before Flash MX. A fourth optional parameter for attachMovie() is called "init object." Here, you can provide a generic object that contains the properties you want to give to your newly attached clip. For example, instead of following the attachMovie() code with the preceding three lines of code (that set _x, _y, and _alpha), you can do this:

myObj = new Object();  myObj._x=190;  myObj._y=33;  myObj._alpha=50;  _root.attachMovie("box","box_1",1,myObj);

This code simply constructs the myObj variable with three named properties (that just happen to be built-in to Flash), and that variable is provided as the fourth parameter. By the way, if you want to initialize your attached movie with homemade properties (that is, variables), you can add to the object variable myObj additional homemade properties that don't share a name with Flash's built-in ones. For example, to initialize the clip with an age property, insert the following line before the attachMovie line:

myObj.age=36

Arguably, providing this fourth parameter is just as much work as setting properties after the clip is instantiated. (It's actually one extra line of code.) However, in Chapter 13, "Homemade Objects," you'll learn about "constructor functions," which simply construct generic objects but, like all functions, they can do it repeatedly. Just so you can see it here, consider the following code:

function constructProps(x,y){   this._x=x;    this._y=y;  }  _root.attachMovie("box", "box_1", 1, new constructProps(100,100));  _root.attachMovie("box", "box_2", 2, new constructProps(200,400));

Without fully explaining all the ramifications, think of the constructProps() function as simply a way to both create a generic object and set two properties (_x and _y) based on the parameters received. Then, when the two attachMovie() methods are invoked, they in turn invoke our constructProps() function in order to receive back a generic object for that fourth parameter. In the end, two clips are instantiated: box_1 (who's _x and _y are both 100) and box_2 (who's _x is 200 and _y is 400).

Finally, just to show you an application for both loops and the dynamic clip referencing formula you first saw in Chapter 7 (that is path[string clip name]), here's an example that creates 7 clips staggered visually and stacked onto separate levels:

for (i=1; i<8;i++){     _root.attachMovie("box","box_"+i,i);      _root["box_"+i]._x = i*50;      _root["box_"+i]._y = i*50;  }

Notice that I could have used that init object (fourth parameter) and eliminated the two lines that follow the attachMovie() method. This loop simply takes the iterant i from 1 through 7 and uses i for the clip names ("box_"+i), as well as the level number. Then, when it comes to referring to the current box, a string name is generated ("box_"+i) but since it's in brackets and follows a path, the clip of that name is addressed. Finally, I just multiply i by 50 to stagger the position of the new clips.

Here are a couple of more things to know about instantiating clips. You can't put more than one clip in the same level. If you attach a clip and specify level 1, you can't put any other clips in that same level (nor can you load movies into that same level). Also, if you want to remove a clip that's been created using the attachMovie() function, you can use removeMovieClip() which is a method of the clip, so the form is:

targetPath.instanceName.removeMovieClip();

Notice that you apply the removeMovieClip() method on a clip reference the same way that you use any method not on the identifier. For example, to remove the clip created previously, use the following:

_root.box1.removeMovieClip();

Finally, take a quick look at the duplicateMovieClip() method, because it's pretty similar. All you need to specify is the new instance name for the clip and the level number. For example, you can duplicate the instance called original to create one called copy by using:

_root.original.duplicateMovieClip("copy", 1 );

This method requires that an instance has already been instantiated; otherwise, you'd have no object to apply this method to. The good news, however, is that the duplicateMovieClip() method doesn't require that you've previously specified the linkage and given the Library item an identifier. Also, any scripts attached to the clip that's duplicated are contained in the duplicate. By the way, removeMovieClip() works the same way with clips created through the duplicateMovieClip() method.

Runtime Drawing Methods

If the attachMovie() and duplicateMovieClip() methods just aren't enough for you, you'll be happy to see that Flash MX has a new method called createEmptyMovieClip(). This was added mainly to work with the new set of runtime drawing methods. However, I can think of ways to use it on its own. For example, I might want to create a single clip on the Stage that contains (has nested inside) two instances of other clips. I can first create an empty clip, and then use attachMovie() to place several other clips inside my new clip. For example:

//create an empty clip called " myInstance " in the main timeline  _root.createEmptyMovieClip("myInstance",1);  myInstance.attachMovie("box", "boxClip",1);  myInstance.attachMovie("circle", "circleClip",2);

The last two lines attach symbols (with linkage "box" and "circle") into the newly created clip. In this way, you can generate clips with dynamic contents.

Like I said, the createEmptyMovieClip() method enables you to first create a clip and then draw into that clip. Flash MX's drawing methods let you dynamically create lines, fills, and gradients into any clip. Although you can draw into a clip that contains graphics, the shapes you draw are just that shapes. Therefore, they are placed underneath everything else. Anyway, the drawing methods appear to be very simple (and they are); however, applying them to do amazing things is more work.

The basic approach is first to create an empty clip, and then start drawing into it. When drawing lines, it's as though you first tell Flash what thickness and color pen you want, where to place it on the canvas, and then where to draw to. The methods are: lineStyle() or moveTo(), and then either lineTo() or curveTo() (depending on whether you want a straight line). Consider the following simple example in which I first create an empty clip, set the lineStyle(), tell Flash where to place the pen, and then draw a line down 100 pixels:

_root.createEmptyMovieClip("clip",1);  _root.clip.lineStyle(3,0x0000FF,100);  _root.clip.moveTo(0,0);  _root.clip.lineTo(0,100);

The first line creates an instance called clip into level 1. The second line sets the line thickness to 3, the color to blue, and the alpha to 100 percent. The third line tells the pen to start at 0,0, and the fourth line where to draw to. The only tricky part is the moveTo() method. Think of this as saying, "Pick up the pen and place it down at this point."

Let me complete a drawing of an arrow:

_root.clip.lineTo(100,50);  _root.clip.lineTo(0,0);  _root.clip.moveTo(0,50)  _root.clip.lineTo(-100,50)

It's easiest to see this code if you look at Figure 12.10.

Figure 12.10. This sequence shows each step of the code to draw an arrow.

graphics/12fig10.gif

In addition to lineTo() (which accepts the x and y coordinates of the destination) there's curveTo(), which also accepts the destination coordinates (called the "anchor" points), but also the control points that influence the shape of the curve. Control points are easiest to understand when you consider how the Pen tool and the Subselection tool draw Bezier curves. You can also check out the sample file "drawing.fla," which is located in the Samples folder ("FLA" subfolder) adjacent to your installed version of Flash MX. Figure 12.11 shows how control points affect a curve.

Figure 12.11. A control point is easy to see when using the Subselection tool to modify a hand-drawn line.

graphics/12fig11.gif

Finally, the last few methods are beginFill(), beginGradientFill(), and endFill(). Basically, you start by saying beginFill(), draw a few lines, and then finish with endFill(). (It's as though Flash took the Paint Bucket tool and filled the shape you drew.) Naturally, your lines have to be enclosed. Also, if you want something that's only fill (that is, no lines), just specify your line style with a zero alpha (lineStyle(0,0x000000,0)). Here's a modified version of the arrow I drew earlier:

_root.createEmptyMovieClip("clip",1);  with(_root.clip){    lineStyle(3, 0x0000FF, 100);     beginFill(0xFF0000, 100);     moveTo(0,0);     lineTo(0,100);     lineTo(100,50);     lineTo(0,0);     endFill();     moveTo(0,50);     lineTo(-100,50);  }

There are actually two differences between this code and the earlier example. The main thing is that before drawing the triangle, I say beginFill(0xFF0000,100) to indicate that I'm going to be filling with red (0xFF0000) of alpha 100, and then when I'm done with the triangle, I say endFill(). It's important not to forget endFill(); if you do, you might see odd results. In addition, because I was getting tired of typing "_root.clip." before each line (and so that you can see a great usage for the with() command), I just preceded the bulk of code with the line reading with(_root.clip){, and then placed all the code before the ending curly brace. It just means, "with this object do the following".

Finally, beginGradientFill() is similar to beginFill() in that you call this method before drawing lines and finish with endFill(). However, whereas beginFill()accepts only the color and alpha, beginGradientFill() accepts five parameters three of which are arrays full of values and one of which is a generic object that has your choice of either six or nine properties. So, really, the number of parameters is unlimited. Without fully explaining complex matrix transformations, let me give you a several pointers to get you started. The form is always:

my_mc.beginGradientFill (fillType, colors, alphas, ratios, matrix)

Always replace the first parameter (fillType) with either "linear" or "radial" (verbatim, including quotes), depending on the type of fill you want. The following three parameters (colors, alphas, and ratios) are always in the form of an array. That is, you can either type a literal array right in their slots (such as [1,2,3]) or refer to a variable previously populated with an array. The arrays for colors and alphas are relatively straightforward. Consider that your gradient blends from (at least) one color to another. Those two colors need to be identified (by hex value). (Of course, you can use the handy-dandy formula from earlier in this chapter if you want to convert RGB to hex.) To make a gradient go from red to blue, use [0xFF0000,0x0000FF]. In addition, the alpha level for each color is specified in the alphas array. For example, [100,100] will have no transparency at either step of the gradient; [100,50] will reach 50 percent at the second color. The ratios array is not quite as intuitive. Here, you specify what percentage (of the entire blend) you want completed when a point is reached. You're really affecting the sharpness or smoothness of the falloff. For a two-step gradation (for example, from red to blue), it's pretty simple. You'll often want it 0 percent complete at the first color and then 100 percent complete at the second color. However, for a gradation with three steps, you may or may not want to have a consistent 0 percent, 50 percent, 100 percent progression. You can adjust it to make it look less balanced, perhaps 0 percent, 75 percent, and then 100 percent (see Figure 12.12). The closer in value the percentages are, the less gradual the gradation. Now for the really funky part: where alpha values logically go from 0 to 100, the ratios (that is, the percentage values in the ratios array) go from 0 to 255. Therefore, to represent 50 percent, you use 127.5 (half of 255). For example, a consistent three-step blend could have the following values for the first three arrays:

colors=[0xFF0000, 0x00FF00, 0x0000FF];  alphas=[100,100,100];  ratios=[0, 127.5, 255];
Figure 12.12. These three gradients are identical except for the ratios's array values. Notice how the smoothness of the blend is affected.

graphics/12fig12.gif

That is, by the time the blend got to green (the second step) it would be 50 percent complete. Naturally, you would have to place the three variables in the proper parameter positions of the beginGradientFill() method. Also, these three arrays must all have the same length.

graphics/icon01.gif

Many examples, including those in the Flash Help files, use "0x7F" rather than providing 127.5 for 50 percent of 255. That's just the hex equivalent for 127.5. I find myself constantly trying to convert normal numbers (that is, base-10 numbers) to or from hex values (base-16). The following two formulas will simply help you translate:

To express a base-10 number as a hex string, use

myNumber.toString(16);

For example, 127.5.toString(16) becomes 7F (and you just have to remember to add 0x).

To express a hex string as a base-10 number, use:

parseInt(hexString,10);

For example, parseInt(0x7F,10) will return 127.

At this point, it's necessary to explain the last parameter, matrix, because you can't do much without it. It is always a generic object, but you have a choice as to exactly which pre-designated properties that object will have. One format is a, b, c, d, e, f, g, i the nine values necessary to represent a 3x3 matrix transformation, as visualized here:

a     b     c  d     e     f  g     h     i

If this looks familiar, you must be a real math-head. It's not so much that these nine values describe how the gradient will appear, but rather you begin with a so called "identity matrix" (zeros in each slot except a, e, and i) and then transform the matrix by scaling, displacing (called translating), rotating, skewing, or otherwise "torqueing" it. For example, if you start with a single pixel dot on the edge of the fill and then scale it, the blend will cover more area. If you then translate it, you can move it to the center of the fill area. It's sort of like how you manually use the Fill Transform tool, except it's all done with code. The only problem is that matrix transformations use relatively complex mathematics. (Well, it looks really complex.) Therefore, people tend to write a library of useful functions so that they don't have to do the nitty-gritty work every time. For now, I'll just show you a couple of simple transformations (and then I'll show you where it gets really hairy).

First, recall the quick way to both create a generic object and populate it in one line of code: myObj={prop1:"val1", prop2:"val2"}. That's the form I'll use for the matrix parameters that follow.

1  _root.createEmptyMovieClip( "grad", 1 );  2  with (_root.grad){ 3     colors = [0xFF0000, 0x00FF00];  4     alphas = [100, 100];  5     ratios = [0,255];  6     matrix = {a:1, b:0, c:0, d:0, e:1, f:0, g:0, h:0, i:1};  7     beginGradientFill( "radial", colors, alphas, ratios, matrix );  8     moveto(0,0);  9     lineto(0,600);  10    lineto(600,600);  11    lineto(600,0);  12    lineto(0,0);  13    endFill();  14 }

The preceding code will draw a 600x600 box that appears to be all green but is really a tiny 1-pixel dot of red that quickly blends to green. The identity matrix is not much to look at. However, if you want to scale it much larger, replace the sixth line with the following three lines:

var xScale=500;  var yScale=500;  matrix = {a:xScale, b:0, c:0, d:0, e:yScale, f:0, g:0, h:0, i:1};

Check out Figure 12.13, which shows that the gradation has grown.

Figure 12.13. After a 500 percent scale transformation, the tiny dot of a gradient looks more substantial.

graphics/12fig13.gif

The reason this works is that, as everyone knows, the matrix transformation for scaling is described by

xScale     0          0  0          yScale     0  0          0          1

Interestingly, the same brains that brought us the scaling description have found that translation (moving) is performed by the following:

1          0          0  0          1          0  xAmount    yAmount    1

Without whipping out your slide rule, it (seriously) should be easy to see that this doesn't conflict with the two slots for xScale and yScale. So, if you want to both scale and translate the gradation, you could use this (in place of the original line 6 of code):

var xScale=500;  var yScale=500;  var xAmount=300;  var yAmount=300;  matrix = {a:xScale, b:0, c:0, d:0, e:yScale, f:0, g: xAmount, h:yAmount,  i:1};

This should make the blend appear to grow from the center of the square (see Figure 12.14). Go ahead and try replacing the temporary variables (xScale, yScale, xAmount, and yAmount).

Figure 12.14. After both a scale and translate transformation, the radial blend now appears in the center of our box.

graphics/12fig14.gif

We got sort of lucky when combining both scale and translate because none of the 9 slots were shared by both formulas. But in the case of the formula to rotate a transformation, we aren't so lucky. At this point, rather than walking through matrix multiplication, let me first say that it's definitely possible to scale, rotate, and translate a transformation. Second, it's much easier to learn to use the "TransformMatrix.as" library of useful functions. This ships on the Flash MX CD-ROM so that you don't have to "reinvent the wheel." To fully understand how to use this file, you'll want to study the subject of "overriding," which is covered in Chapter 14.

There's still another way to create the matrix parameter for the beginGradientFill() method which you may find easier. Specifically, if the matrix object can have five properties (x, y, w, h, r) plus one that's named matrixType and always hard-wired as "box". The values for x and y define the horizontal and vertical position of the gradient (relative to the clip's registration point). The w and h are for width and height, respectively. Finally, r is for rotation (in radians). You'll see that since you can't scale x and y differently, the rotation option only makes sense for linear gradients. (That is, you can't make an ellipse, so there's no sense in rotating a circle.) Take the following example (which can just replace the matrix definition in the earlier code):

matrix={matrixType:"box", x:150, y:150, w:300, h:300, r:(45/180)*Math.PI };

You'll also want to change the gradient to linear, as follows:

beginGradientFill( "linear", colors, alphas, ratios, matrix );

First, notice that matrixType:"box" appears verbatim. In this case, we offset the gradient by 150x and 150y, and set both its width and height to 300. Finally, the gradient is rotated by 45 degrees. Because we can't just say 45 for the r property, we use the handy formula to covert degrees to radians. In retrospect, the "box" matrix type is a lot more manageable for simple gradients.

In practice, you might find drawing anything more than very primitive shapes and simple blends to be quite difficult. For example, drawing a box or arrow is not too challenging, but suppose you want to rotate or skew the shape you draw? You can apply the same (complex) matrix math used for the gradient transformations to drawing lines. The approach most people take is to first write a few general-purpose functions that accept parameters that are easier to define, and then mix and match these general functions for a particular application. For example, you might write a function that draws rectangles based on four points provided as parameters. Then you could use this rectangle-drawing function in an application that creates a bar graph based on dynamic data. When you learn to use the TransformMatrix.as library, you'll probably get some ideas how to write your own general-purpose functions. Don't feel like you're cheating by starting with the building blocks such libraries provide. Believe me, you'll have plenty of opportunities to come up with creative solutions to challenging problems even with this code!

Summary

We've looked at three traditional objects made for Flash: Sound, Color, and Date. In each of these, you first need to create an instance of the object (by putting it in a variable), and then you can use any of the object's methods. The three objects introduced in this chapter are a good representation of "formal" objects. So many other objects in Flash have special conditions that let you get away without instantiating them (the Math and String objects, in particular). In addition to Sound, Color, and Date, you saw three ways to instantiate Movie Clips at runtime: (attachMovie(), duplicateMovieClip(), and createEmptyMovieClip()). The important thing to remember when attaching or creating clips is that the master symbol needs to have its linkage set (as does a sound when using the Sound object). On top of all that, you got to play with Flash MX's new drawing methods.

Another key concept you should take away from this chapter is the way a generic object can be populated with values for several named properties and then passed as a parameter. You even saw how a special constructor function can produce such generic objects and even set initial properties. The main thing we did was to populate a variable with values. Both the Sound and Color object's setTransform() method requires a generic object with specific properties. (That is, the named properties need to match the arbitrary names designated in Flash's design.) The same basic technique was used in the drawing methods, although often the properties themselves contained multiple values in the form of arrays. We'll fully explore the process of creating generic objects in Chapter 13 when we create our own custom objects that do more than just store data.

If you ever have trouble grasping concepts about objects in general, remember that you can always think about what makes an instance of a clip an object. It has a set of properties that can be varied from instance to instance. And just like any object, there are a host of methods that you can apply to individual instances of clips. The other objects explored in this chapter are still objects; they're just not always visible.

CONTENTS


ActionScripting in MacromediaR FlashT MX
ActionScripting in MacromediaR FlashT MX
ISBN: N/A
EAN: N/A
Year: 2001
Pages: 41

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net